Files
revkit/docs/instructions/pxa_esn_llm.md
T

116 lines
3.9 KiB
Markdown

# PXA ESN — LLM 向けリファレンス
## PXA ESN とは
Netflix Android アプリがサーバーから取得するデバイス識別子。ローカル生成の Base ESN (PRV) に対し、サーバーが fingerprint を付与して発行する。API リクエストの `X-Netflix.esn` ヘッダーで使用される。
## 取得プロトコル
- **経路:** MSL (Message Security Layer) over HTTPS
- **エンドポイント:** `/getProxyEsn`
- **取得方法:** Cookie ではなく、MSL レスポンスの JSON ボディで返される
### リクエスト
```json
{"url": "/getProxyEsn"}
```
MSL VolleyRequest のヘッダーに `router: getProxyEsn` が設定される。HTTP レベルのヘッダーは MSL 暗号化されるため外部からは見えない。
### レスポンス
```json
{
"id": 1,
"version": 2,
"serverTime": 1773478742949,
"result": {
"esn": "NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-02028KVLM5OU1MSB..."
},
"common": {},
"from": "playapi"
}
```
PXA ESN は `result.esn` に格納されている。
## 有効期限
- 設定値: `EsnHendrixConfig.refreshProxyEsnTimeInMs = 0`
- TTL が 0 のため **無期限キャッシュ**
- 一度取得したら SharedPreferences に永続化され、以下の場合のみ再取得:
- 初回インストール
- アプリデータクリア
- SharedPreferences の消失
- `masterTokenSerialNumber` の変更
## fingerprint の特性
- サーバー側で生成され、ローカルでは再現不可
- 同一デバイスでも取得ごとに異なる fingerprint が返される
- 実測で 3 回取得し、毎回異なることを確認
## ESN 構造
```
NFANDROID1-PXA-P-L3-GOOGLPIXEL=4A==5G=-22594-0202{fingerprint}
```
| セグメント | 値 | 説明 |
|---|---|---|
| Platform | `NFANDROID1-` | Android プレフィックス |
| Type | `PXA` | Proxy (サーバー発行) |
| Category | `P` | Phone (T=Tablet, B=TV, C=ChromeOS, E=Display) |
| Security | `L3` | Widevine Security Level |
| Model | `GOOGLPIXEL=4A==5G=` | サニタイズ済みデバイスモデル |
| systemId | `22594` | Widevine systemId |
| fingerprint | `0202...` | サーバー発行 (毎回異なる) |
## 保存先
SharedPreferences に 2 つのキーで保存:
| キー | 型 | 内容 |
|---|---|---|
| `nf_drm_esn` | String | PXA ESN 文字列 |
| `nf_drm_proxy_esn` | String (JSON) | `{"esn":"...","ts":epoch_ms,"sn":serial_number}` |
## 使用箇所
| ヘッダー | 使用先 |
|---|---|
| `X-Netflix.esn` | GraphQL / Cronet リクエスト |
| `X-Netflix-ProxyEsn` | WebSocket / MSL リクエスト |
## コードパス
```
WidevineEntityAuthEsnProviderImpl.c(serialNumber) -- 再取得が必要か判定
→ ProxyEsnMslRequest -- MSL リクエスト送信
→ body: {"url": "/getProxyEsn"}
→ Netflix playapi サーバー
← onSuccess(JSONObject) -- レスポンス受信
→ result.esn を抽出
→ ProxyEsn.onKnown(serialNumber, esn) -- SharedPreferences に保存
```
## 関連クラス
| クラス | ファイルパス | 役割 |
|---|---|---|
| `ProxyEsnMslRequest` | `mslagent/impl/ProxyEsnMslRequest.java` | MSL リクエスト発行・レスポンス処理 |
| `ProxyEsn` | `esn/impl/ProxyEsn.java` | キャッシュ管理・永続化 |
| `WidevineEntityAuthEsnProviderImpl` | `esn/impl/WidevineEntityAuthEsnProviderImpl.java` | ESN 統合管理 |
| `EsnHendrixConfig` (`o.fkQ`) | `o/C13212fkQ.java` | TTL 設定 (`refreshProxyEsnTimeInMs`) |
## キャプチャ方法
`hook_msl.js` を使用。`ProxyEsn.$init` で expired フラグを強制 true にすることで、キャッシュの有無に関係なく `getProxyEsn` を発火させられる。
```bash
./run_android.sh hook_msl.js
```
出力イベント: `proxyEsn.forceExpired`, `proxyEsn.request`, `proxyEsn.requestHeaders`, `proxyEsn.response`, `proxyEsn.onKnown`, `proxyEsn.error`