Conversation
Buffer.writeUInt8(value, offset) のAPI仕様に対して引数が逆転していた。 offsetの数値(64,65,66)がvalueとして書き込まれ、バージョン情報が 正しく送信されていなかった。
TCNet仕様ではデフォルトユニキャストポートは65023だが、 65032が設定されていた。
Windows環境ではinterfaceAddress()がIPアドレスを返していたため、 OptInパケットがブロードキャストされなかった。 OS分岐を廃止しIPとサブネットマスクからブロードキャストアドレスを 自前計算するように変更。broadcast-address依存を削除。
同一マシン上のBridgeはユニキャストOptIn応答を返さない場合がある。 receiveBroadcastでもMasterのOptInを検出して接続を確立するように変更。 併せてtimestampSocketのbindアドレスをbrodcastListeningAddressに統一。
sendServerがunicastSocket(65023)を使用していたが、BridgeはbroadcastSocket(60000) からのパケットのみ受け付ける。broadcastSocketに変更。 また、requestDataでlayer+1変換が欠落していたため、0-based APIから1-based ワイヤフォーマットへの変換を追加。
15e2ed3でAPIが0-basedに統一されたが、exampleのpacket.layer - 1が そのまま残っていた。
Summary by CodeRabbitリリースノート
WalkthroughOptInパケットのバージョン書き込み順を修正、Windows依存のブロードキャスト算出をビット演算へ置換、デフォルトunicastポートを65023に変更、requestDataのオンワイヤ層を0→1ベースへ変換、送受信ハンドリングとソケットライフサイクルを調整。 Changes
Sequence Diagram(s)sequenceDiagram
participant BroadcastSocket
participant Client
participant Server
participant TimestampSocket
BroadcastSocket->>Client: UDP OptIn (TCNetOptInPacket) + rinfo
Client->>Client: this.server = {address: rinfo.address, port: packet.nodeListenerPort}
Client->>Client: this.connected = true
Client->>Server: announceApp() (immediate)
Client->>Client: invoke connectedHandler() and clear it
Client->>BroadcastSocket: sendServer() via broadcastSocket to Server
Client->>TimestampSocket: bind to brodcastListeningAddress (on connect)
Note over Client,Server: requestData(layer) encodes on-wire layer = layer + 1
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! このプルリクエストは、TCNetプロトコル実装における合計6つのバグを修正します。これには、仕様書に記載されていた3つの確定バグと、実機テスト中に発見された3つの潜在バグが含まれます。特に、Windows環境でのブロードキャストアドレス計算の修正は、他の関連するバグ修正(MasterのブロードキャストOptIn検出の復元や Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
このプルリクエストは、TCNetプロトコルの仕様準拠に関する複数のバグを修正するもので、非常に価値のある変更です。特に、writeUInt8の引数順序の修正や、Windows環境でのブロードキャストアドレス計算の修正は、プロトコルの安定性と互換性を大幅に向上させるものです。依存関係を削減した点も素晴らしいです。
コードレビューの結果、2点ほど軽微な改善点を提案させていただきました。
src/tcnet.tsで新たに追加されたプロパティ名にタイポが見つかりました。- 同じく
src/tcnet.tsで、エラーがサイレントに握りつぶされている箇所がありましたので、デバッグを容易にするためのログ出力を提案しました。
これらの修正は、コードの保守性と信頼性をさらに高めるものと期待されます。全体として、非常によく練られた修正であり、マージする価値が高いと判断します。
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/tcnet.ts (1)
386-390:⚠️ Potential issue | 🟠 Major
layerの境界チェックが抜けています。API を 0-based に寄せたので、ここで
-1や8以上を弾かないと無効な wire 値をそのまま送れてしまいます。加えて、1-based 変換は最終的にTCNetRequestPacket.write()側へ寄せた方が契約が一貫します。🔧 最低限の防御策
public requestData(dataType: number, layer: number): Promise<nw.TCNetDataPacket> { return new Promise((resolve, reject) => { + if (!Number.isInteger(layer) || layer < 0 || layer > 7) { + reject(new RangeError("layer must be an integer between 0 and 7")); + return; + } + const request = new nw.TCNetRequestPacket(); request.dataType = dataType; request.layer = layer + 1; // APIは0-based、仕様は1-based🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/tcnet.ts` around lines 386 - 390, The requestData method currently adjusts layer to 1-based and lacks bounds checking, so invalid values (e.g. -1 or >=8) can be sent; validate the incoming layer in requestData (ensure 0 <= layer <= 7) and reject/throw for out-of-range inputs, and move the +1 1-based conversion out of requestData into TCNetRequestPacket.write() so the conversion contract is centralized (update references to requestData and TCNetRequestPacket.write accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/fix-protocol-bugs.md:
- Around line 5-9: Update the changeset text to also mention the 0-based layer
fix and explicitly document that the change affects requestData() interpretation
and example/client behavior; add a concise bullet noting "0-based layer indexing
corrected" and a short note advising integrators that requestData() semantics
and example usages have changed and that they should verify/remove any prior
workarounds, so downstream users can track and adapt to the behavioral
difference.
In `@src/network.ts`:
- Around line 130-132: 追加した wire-format 書き込みの回帰テストを作成して、バッファのバイト 64–66 に
this.majorVersion/this.minorVersion/this.bugVersion
が正しく書き込まれていることを検証してください。具体的には Network クラス(または該当の write メソッドを含むクラス/関数、例:
writeUInt8 を呼んでいるメソッド)を使ってシリアライズした結果のバッファを取得し、buffer.readUInt8(64),
readUInt8(65), readUInt8(66) がそれぞれ期待値と一致するアサーションを追加してください。テスト名は「wire format
version bytes remain stable」など分かりやすくして regression テストスイートに含めてください。
---
Outside diff comments:
In `@src/tcnet.ts`:
- Around line 386-390: The requestData method currently adjusts layer to 1-based
and lacks bounds checking, so invalid values (e.g. -1 or >=8) can be sent;
validate the incoming layer in requestData (ensure 0 <= layer <= 7) and
reject/throw for out-of-range inputs, and move the +1 1-based conversion out of
requestData into TCNetRequestPacket.write() so the conversion contract is
centralized (update references to requestData and TCNetRequestPacket.write
accordingly).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 12c95e1f-2714-4b90-a424-5e308f64791c
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (6)
.changeset/fix-protocol-bugs.mdexamples/index.tspackage.jsonsrc/network.tssrc/tcnet.tssrc/utils.ts
📜 Review details
🧰 Additional context used
🪛 markdownlint-cli2 (0.21.0)
.changeset/fix-protocol-bugs.md
[warning] 5-5: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
- announceApp()のcatchでエラーをログ出力するよう改善 - requestData()にlayer境界チェック (0-7) を追加 - changesetにlayer修正とexample修正を追記
9c5s
left a comment
There was a problem hiding this comment.
レビュー対応を 99627bb でプッシュしました。
採用 (3件):
.catch(() => {})→ エラーログ出力に改善- changeset に layer 修正を追記
requestData()に layer 境界チェック (0-7) を追加
スコープ外 → issue化 (2件):
却下 (1件):
- layer+1 の
TCNetRequestPacket.write()への移動:layerプロパティがワイヤ値を保持するセマンティクスを変えることになり、本PRのスコープ外
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/tcnet.ts`:
- Around line 396-399: The caller-side 0→1 conversion (request.layer = layer + 1
in TCNetRequestPacket usage) must be removed and the responsibility moved into
the serializer: revert callers (e.g. where TCNetRequestPacket is constructed in
src/tcnet.ts) to assign the API's 0-based layer value directly, and update
TCNetDataPacket.write() in src/network.ts to perform the wire conversion when
serializing (write byte 25 as this.layer + 1), optionally adding a bounds
check/assert for valid layer range; this ensures all send paths use a single
canonical API representation and the wire format is applied only in write().
- Around line 100-101: disconnect() currently only closes broadcastSocket and
unicastSocket leaving timestampSocket bound; when timestampSocket is created via
createSocket(...) and bound with bindSocket(..., TCNET_TIMESTAMP_PORT) you must
also close it in disconnect(): add logic in the disconnect() function to check
if this.timestampSocket exists, call its close() (or equivalent) and
null/undefine the property so UDP/60001 is released and subsequent bindSocket
calls succeed; reference the timestampSocket property, the disconnect() method,
and the TCNET_TIMESTAMP_PORT usage when making the change.
- Around line 198-206: The current code calls announceApp() fire-and-forget and
then immediately sets this.connected = true and invokes this.connectedHandler(),
causing a race where requestData() may run before Master registration completes;
change the flow in the connect logic so you await this.announceApp() (e.g.,
await this.announceApp() inside a try/catch), and only after it resolves
successfully set this.connected = true and call/clear this.connectedHandler; on
failure handle/log the error and do not mark connected or call the handler until
announceApp() succeeds. Ensure you reference announceApp(), this.connected, and
this.connectedHandler when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7839c215-485d-464c-a52c-b10997a22e6e
📒 Files selected for processing (2)
.changeset/fix-protocol-bugs.mdsrc/tcnet.ts
📜 Review details
🧰 Additional context used
🪛 markdownlint-cli2 (0.21.0)
.changeset/fix-protocol-bugs.md
[warning] 5-5: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
timestampSocket (UDP/60001) がclose漏れしており、再接続時に EADDRINUSEになる可能性があった。
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/tcnet.ts (1)
401-407:⚠️ Potential issue | 🟠 Major成功応答後の pending request を掃除していないため、同じキーの再要求が壊れます。
Line 401-407 では timeout を登録していますが、成功パスは
resolve()するだけでthis.requestsと timer を残します。最初の要求が即応答したあとrequestTimeout以内に同じ(dataType, layer)を再度投げると、1 本目の stale timeout が 2 本目の entry を消して、2 本目の Promise が未解決のまま残ります。🧹 修正案
-type STORED_RESOLVE = (value?: nw.TCNetDataPacket | PromiseLike<nw.TCNetDataPacket> | undefined) => void; +type STORED_RESOLVE = { + resolve: (value?: nw.TCNetDataPacket | PromiseLike<nw.TCNetDataPacket> | undefined) => void; + timeout: NodeJS.Timeout; +};-const pendingRequest = this.requests.get(`${dataPacket.dataType}-${dataPacket.layer}`); -if (pendingRequest) { - pendingRequest(dataPacket); +const key = `${dataPacket.dataType}-${dataPacket.layer}`; +const pendingRequest = this.requests.get(key); +if (pendingRequest) { + this.requests.delete(key); + clearTimeout(pendingRequest.timeout); + pendingRequest.resolve(dataPacket); }- this.requests.set(`${dataType}-${layer}`, resolve); - - setTimeout(() => { - if (this.requests.delete(`${dataType}-${layer}`)) { + const key = `${dataType}-${layer}`; + const timeout = setTimeout(() => { + if (this.requests.delete(key)) { reject(new Error("Timeout while requesting data")); } }, this.config.requestTimeout); + this.requests.set(key, { resolve, timeout });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/tcnet.ts` around lines 401 - 407, 現在の実装は成功時に this.requests エントリとタイマーを残すため、最初のタイマーが後続リクエストのエントリを削除してしまいます。修正は、request を登録するときにタイマーIDを一緒に保持し(例: this.requests.set(key, {resolve, reject, timerId}))、成功パス(resolve を呼ぶ箇所)で必ず clearTimeout(timerId) を実行してから this.requests.delete(key) でエントリを掃除するようにすること、そして setTimeout のコールバック側では保存された timerId と一致することを確認してから reject かつ this.requests.delete(key) を行うようにしてください(参照シンボル: this.requests, setTimeout, clearTimeout, resolve, reject, this.config.requestTimeout)。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/tcnet.ts`:
- Around line 401-407: 現在の実装は成功時に this.requests
エントリとタイマーを残すため、最初のタイマーが後続リクエストのエントリを削除してしまいます。修正は、request
を登録するときにタイマーIDを一緒に保持し(例: this.requests.set(key, {resolve, reject,
timerId}))、成功パス(resolve を呼ぶ箇所)で必ず clearTimeout(timerId) を実行してから
this.requests.delete(key) でエントリを掃除するようにすること、そして setTimeout のコールバック側では保存された
timerId と一致することを確認してから reject かつ this.requests.delete(key) を行うようにしてください(参照シンボル:
this.requests, setTimeout, clearTimeout, resolve, reject,
this.config.requestTimeout)。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 02142081-4f45-4a67-a186-30cfdcce8909
📒 Files selected for processing (1)
src/tcnet.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: 9c5s
Repo: 9c5s/node-tcnet PR: 2
File: src/tcnet.ts:397-400
Timestamp: 2026-03-21T05:02:24.940Z
Learning: In the `node-tcnet` repository (`src/network.ts`, `src/tcnet.ts`), `TCNetDataPacket.write()` has no callers. `TCNetDataPacket` objects are only ever deserialized on receive paths (`read()`), never serialized and sent. The only outbound packet carrying a layer field is `TCNetRequestPacket`, constructed inside `requestData()` in `src/tcnet.ts`. Suggesting to move layer conversion logic into `TCNetDataPacket.write()` is inappropriate because that method is dead code.
📚 Learning: 2026-03-21T05:02:24.940Z
Learnt from: 9c5s
Repo: 9c5s/node-tcnet PR: 2
File: src/tcnet.ts:397-400
Timestamp: 2026-03-21T05:02:24.940Z
Learning: In the `node-tcnet` repository (`src/network.ts`, `src/tcnet.ts`), `TCNetDataPacket.write()` has no callers. `TCNetDataPacket` objects are only ever deserialized on receive paths (`read()`), never serialized and sent. The only outbound packet carrying a layer field is `TCNetRequestPacket`, constructed inside `requestData()` in `src/tcnet.ts`. Suggesting to move layer conversion logic into `TCNetDataPacket.write()` is inappropriate because that method is dead code.
Applied to files:
src/tcnet.ts
🔇 Additional comments (4)
src/tcnet.ts (4)
18-18: 既定のunicastPortを 65023 に合わせたのは良いです。設定未指定時の接続先が仕様値と揃います。
193-209: broadcast OptIn での server 発見とbroadcastSocket送信をセットで直したのは良いです。同一ホストの Bridge が unicast OptIn を返さないケースでも、発見と送信元ポート 60000 の要件を両立できています。
Also applies to: 346-346
392-400:layerの範囲チェックと 0-based API → 1-based wire 変換をrequestData()に閉じたのは妥当です。API 表現のまま受信側キーを維持しつつ、送信時だけ wire 形式へ補正できています。
Based on learnings:
TCNetDataPacket.write()には呼び出し元がなく、layerを送る outbound path はrequestData()内のTCNetRequestPacketだけです。
120-123:⚠️ Potential issue | 🔴 Critical
disconnect()がどのソケットも実際には閉じていません
promisifyBasicFunction()は Promise を返す関数を返すため、Promise.all()に渡された値は関数のままで、Promise ではありません。Line 120-123 はPromise.all([関数1, 関数2, 関数3])を実行しているため、これらの関数は呼び出されず、broadcastSocket.close()/unicastSocket.close()/timestampSocket.close()は一度も実行されません。ソケットが開いたままになり、再接続時にEADDRINUSEエラーが発生します。修正案
+const closeSocket = (socket: Socket): Promise<void> => + new Promise((resolve, reject) => { + try { + socket.close(() => resolve()); + } catch (err) { + reject(err); + } + }); + public disconnect(): Promise<void> { clearInterval(this.announcementInterval); this.removeAllListeners(); this.connected = false; return Promise.all([ - promisifyBasicFunction(() => this.broadcastSocket.close()), - promisifyBasicFunction(() => this.unicastSocket.close()), - promisifyBasicFunction(() => this.timestampSocket.close()), + closeSocket(this.broadcastSocket), + closeSocket(this.unicastSocket), + closeSocket(this.timestampSocket), ])> Likely an incorrect or invalid review comment.
promisifyBasicFunctionは関数を返すだけで呼ばないため、disconnect()で ソケットが実際にはcloseされていなかった。closeSocketヘルパーに置き換え。 また、requestDataの成功パスでrequestsエントリとtimerが残り、同一キーの 再リクエストで壊れる問題を修正。
9c5s
left a comment
There was a problem hiding this comment.
2248ebf で2件とも修正した。
- disconnect() close漏れ:
promisifyBasicFunctionは関数を返すだけで呼ばないためソケットがcloseされていなかった。closeSocketヘルパーに置き換え、promisifyBasicFunctionは削除。 - pending requestクリーンアップ: 成功パスで
this.requests.delete()とclearTimeout()を実行するよう修正。STORED_RESOLVE型をSTORED_REQUESTに変更し、timeoutを保持。
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/tcnet.ts`:
- Around line 249-255: The response handler can't find pending requests because
requestData() stores keys using 0-based layer but TCNetDataPacket.read()
currently reads a 1-based layer; update TCNetDataPacket.read() to convert the
wire value to 0-based by subtracting 1 when assigning this.layer (i.e., set
this.layer = this.buffer.readUInt8(25) - 1) so the keys `${dataType}-${layer}`
produced for requests match the keys used when resolving responses; this mirrors
the conversion already done in TCNetRequestPacket.read().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: e81e3e8a-af0c-4f24-9578-5576de0a23df
📒 Files selected for processing (1)
src/tcnet.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: 9c5s
Repo: 9c5s/node-tcnet PR: 2
File: src/tcnet.ts:397-400
Timestamp: 2026-03-21T05:02:24.940Z
Learning: In the `node-tcnet` repository (`src/network.ts`, `src/tcnet.ts`), `TCNetDataPacket.write()` has no callers. `TCNetDataPacket` objects are only ever deserialized on receive paths (`read()`), never serialized and sent. The only outbound packet carrying a layer field is `TCNetRequestPacket`, constructed inside `requestData()` in `src/tcnet.ts`. Suggesting to move layer conversion logic into `TCNetDataPacket.write()` is inappropriate because that method is dead code.
📚 Learning: 2026-03-21T05:02:24.940Z
Learnt from: 9c5s
Repo: 9c5s/node-tcnet PR: 2
File: src/tcnet.ts:397-400
Timestamp: 2026-03-21T05:02:24.940Z
Learning: In the `node-tcnet` repository (`src/network.ts`, `src/tcnet.ts`), `TCNetDataPacket.write()` has no callers. `TCNetDataPacket` objects are only ever deserialized on receive paths (`read()`), never serialized and sent. The only outbound packet carrying a layer field is `TCNetRequestPacket`, constructed inside `requestData()` in `src/tcnet.ts`. Suggesting to move layer conversion logic into `TCNetDataPacket.write()` is inappropriate because that method is dead code.
Applied to files:
src/tcnet.ts
🔇 Additional comments (9)
src/tcnet.ts (9)
9-12: LGTM!リクエストごとに
resolveとtimeoutをペアで管理する型定義は、タイムアウトのクリーンアップを確実に行うための適切なアプローチです。
21-21: LGTM!デフォルト
unicastPortを仕様準拠の 65023 に変更。
33-33: LGTM!
socket.close()のコールバックを適切に Promise 化しています。promisifyBasicFunctionの誤用を修正し、確実にソケットのクローズ完了を待機できるようになりました。
95-97: LGTM!
timestampSocketのバインドアドレスをbroadcastSocketと同じbrodcastListeningAddressに統一。一貫性が向上しています。
115-119: LGTM!
closeSocketヘルパーを使用して3つのソケット(broadcastSocket、unicastSocket、timestampSocket)を確実にクローズ。再接続時のEADDRINUSEエラーを防止します。
188-205: LGTM!Master のブロードキャスト OptIn からも接続を検出するロジックを復元。同一マシン上の Bridge がユニキャスト応答を返さないケースに対応しています。
announceApp()の fire-and-forget パターンは、1秒間隔の setInterval による再送で確実に登録されるため問題ありません。
344-344: LGTM!送信ソケットを
broadcastSocketに変更。Bridge はソースポート 60000 からのパケットのみを受け付けるため、この変更によりサーバーへのパケット送信が正しく動作します。
390-394: LGTM!
layerパラメータの検証を追加。0〜7 の整数範囲を強制することで、不正な入力による予期しない動作を防止しています。
397-413: LGTM!タイムアウト管理が改善されています:
request.layer = layer + 1で wire フォーマットの 1-based 変換(学習に基づき、ここでの変換が適切)- タイムアウトをリクエストと共に保存し、成功時・エラー時に確実にクリア
requests.delete(key)の戻り値で二重 reject を防止
- convergeToAdapter()でbroadcastAddress更新 (#1) - waitConnected()の二重close防止 (#2) - disconnectSockets()でserver=nullリセット (#3) - connect()失敗時のソケットクリーンアップ (#4) - waitConnected()タイムアウトをdetectionTimeoutに変更 (#5) - detectingAdapterフラグで経路識別を明示化 (#6) - listNetworkAdapters()の重複呼び出し削減 (#7) - findIPv4Address()ヘルパーで述語重複を解消 (#8) - privateフィールドの命名規則統一 (#9) - announceApp()の検出中送信を並列化 (#10) - announcementIntervalの型安全性改善 (#11) - announceApp()のエラーハンドリング追加 (#12) - selectedAdapterのセット順統一 (#13) - closeSocketをfunction宣言に変更 (#14)
* feat: listNetworkAdapters()の型定義と実装を追加 * feat: TCNetConfiguration拡張と基盤インスタンス変数を追加 * refactor: disconnectSockets()分離とdisconnect()/waitConnected()リファクタリング * feat: connect()をマルチソケット検出+即resolveに書き換え - connect()を全non-internal IPv4アダプタにソケット作成する方式に変更 - Master検出を待たず即座にresolveするように変更 - Master OptIn検出時にアダプタ収束(convergeToAdapter)を実装 - 検出タイムアウトでdetectionTimeoutイベントを発火 - connectToAdapter()内部メソッドを追加(switchAdapter用) - selectedAdapter/isConnectedプロパティを追加 - receiveBroadcastにadapterName引数を追加してマルチソケット対応 - announceApp()にbroadcastSocket nullガードを追加 * feat: announceApp/broadcastPacket/receiveTimestampのガード追加 - announceApp(): 検出中は全broadcastSocketsに送信するマルチソケット対応 - broadcastPacket(): エラーメッセージを "Adapter not yet selected" に統一 - sendServer(): _switchingフラグによるガード追加 - requestData(): _switchingフラグによるガード追加 - receiveTimestamp(): connected未確定時はtimeイベントを発火しないガード追加 * feat: switchAdapter()のバリデーションと切り替えロジックを実装 * test: switchAdapter()のリトライとエッジケーステストを追加 * docs: Wiki更新 - アダプタ自動検出・切り替えAPIの追加 * fix: コードレビュー指摘13件の修正 - convergeToAdapter()でbroadcastAddress更新 (#1) - waitConnected()の二重close防止 (#2) - disconnectSockets()でserver=nullリセット (#3) - connect()失敗時のソケットクリーンアップ (#4) - waitConnected()タイムアウトをdetectionTimeoutに変更 (#5) - detectingAdapterフラグで経路識別を明示化 (#6) - listNetworkAdapters()の重複呼び出し削減 (#7) - findIPv4Address()ヘルパーで述語重複を解消 (#8) - privateフィールドの命名規則統一 (#9) - announceApp()の検出中送信を並列化 (#10) - announcementIntervalの型安全性改善 (#11) - announceApp()のエラーハンドリング追加 (#12) - selectedAdapterのセット順統一 (#13) - closeSocketをfunction宣言に変更 (#14) * fix: CodeRabbitレビュー指摘5件の修正とchangeset追加 - waitConnected()のPromiseハング防止 (CR-3) - switchAdapter()最終リトライ失敗時のクリーンアップ (CR-4) - broadcastPacket()にswitchingガード追加 (CR-5) - connect()の再入防止ガード強化 (CR-2) - findIPv4Address()のAPI Reference記載 (CR-1) - changeset追加 (minor) * fix: announceApp()がswitchingガードに阻害される問題を修正 announceApp()が公開APIのbroadcastPacket()/sendServer()を経由していたため、 switchAdapter()中のconnectToAdapter()からの呼び出し時にswitchingガードに ブロックされていた。内部メソッドのsendPacket()を直接使用するよう変更。 * fix: CodeRabbit 2回目レビュー指摘2件の修正 - requestTimeoutの説明を旧挙動から修正 (CR-new-1) - waitConnected()にtimeoutMsパラメータ追加、detectionTimeout=0でもhangしない (CR-new-3)
概要
SPEC_ANALYSIS.mdに文書化されていた3件の確定バグに加え、実機テストで発見された3件の潜在バグを修正。
修正内容
仕様書記載の確定バグ
network.ts):writeUInt8(offset, value)→writeUInt8(value, offset)に修正。バージョン情報がバイト64-66に正しく書き込まれるようになったutils.ts): IPアドレスを返していたのをIP+netmaskからブロードキャスト計算に変更。broadcast-address依存を削除しproduction依存ゼロにtcnet.ts): 65032 → 仕様準拠の65023実機テストで発見した潜在バグ
tcnet.ts): MasterのブロードキャストOptInからも接続を検出するよう復元。初期コミットにあったがc2c1b7fで削除されていた。同一マシン上のBridgeはユニキャストOptIn応答を返さないため必要tcnet.ts): unicastSocket → broadcastSocket。BridgeはbroadcastSocket (ポート60000) からのパケットのみ受け付けるtcnet.ts):layer + 1を追加。15e2ed3でAPIを0-basedに統一した際に漏れていたexamples/index.ts):packet.layer - 1→packet.layer(APIは既に0-based)補い合うバグの構図
修正前のWindows環境では、broadcastAddress=IPアドレスの「バグ」がローカルユニキャストとして機能し、同一マシンのBridgeに到達していた。broadcastAddress修正に伴い、receiveBroadcast OptIn検出とsendServerソケット変更が連鎖的に必要になった。
テスト