Преглед на файлове

docs: add TCP wire codec implementation plan

wuzilong преди 1 месец
родител
ревизия
5b6b805774
променени са 1 файла, в които са добавени 125 реда и са изтрити 0 реда
  1. 125 0
      docs/superpowers/plans/2026-09-18-real-tcp-wire-codec.md

+ 125 - 0
docs/superpowers/plans/2026-09-18-real-tcp-wire-codec.md

@@ -0,0 +1,125 @@
+# Real TCP Wire Codec Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Replace the speculative 24-byte/XXTEA TCP transport with the verified 36-byte header encoded as a 48-byte wire header.
+
+**Architecture:** A pure `encode6` module embeds the verified 256-byte client substitution table. `framing` packs nine signed little-endian words, encodes only that 36-byte header, then appends the plaintext body. `TcpClient` reads the 48-byte wire boundary and owns a monotonically increasing diagnostic sequence.
+
+**Tech Stack:** Python 3.12+, `asyncio`, `struct`, Pydantic, pytest, pytest-asyncio.
+
+**Spec:** `docs/superpowers/specs/2026-09-18-cq2-real-tcp-wire-codec-design.md`
+
+## Global Constraints
+
+- The wire shape is `encode6(header36) + body`; do not apply XXTEA to TCP frames.
+- The real header is `<9i`: `pre, msg_id, recog, param1, param2, param3, codec, body_len, sequence`.
+- Export `PLAIN_HEADER_SIZE = 36`, `WIRE_HEADER_SIZE = 48`, and compatibility `HEADER_SIZE = WIRE_HEADER_SIZE`.
+- `codec != 0` raises `ValueError('unsupported body codec: N')`; it must never return compressed bytes as a body.
+- `initial_sequence` defaults to `0`; that value is not evidence of a production login initial sequence.
+- Do not add live game login, credentials, captures, inferred CS2001 payloads, or body decompression.
+
+### Task 1: Establish a runnable baseline
+
+**Files:** no repository source changes.
+
+- [ ] Install the local test runner:
+
+```bash
+../../.venv/bin/python -m pip install 'pytest>=9.1.1' 'pytest-asyncio>=1.4.0' 'pydantic>=2.13.5'
+```
+
+- [ ] Run and record the pre-change baseline:
+
+```bash
+PYTHONPATH=cq2_sdk/src ../../.venv/bin/python -m pytest cq2_sdk/tests/unit/test_framing.py cq2_sdk/tests/unit/test_tcp_client.py -q
+```
+
+- [ ] Confirm `.venv` and cache files are still ignored with `git status --short --ignored`.
+
+### Task 2: Implement the verified table-driven 6-bit codec
+
+**Files:**
+- Create: `cq2_sdk/src/cq2_sdk/transport/encode6.py`
+- Modify: `cq2_sdk/tests/unit/test_framing.py`
+
+**Interfaces:** `encode6(src: bytes) -> bytes`; `decode6(data: bytes, *, expected_size: int | None = None) -> bytes`.
+
+- [ ] Write tests first using literal, hand-verified heartbeat values:
+
+```python
+plain = bytes.fromhex(
+    '0000000060ea0000b02daf6a000000000000000095000000000000000000000070000000'
+)
+wire = bytes.fromhex(
+    '5d5452415d4c67445d545078495e55525d5452415d545241'
+    '5d54507a5d5452415d5452415d5452415d54527b5d545241'
+)
+assert encode6(plain) == wire
+assert decode6(wire, expected_size=36) == plain
+with pytest.raises(ValueError, match='outside 6-bit wire range'):
+    decode6(b'<~')
+```
+
+- [ ] Run `pytest test_framing.py -k 'encode6 or decode6'` and confirm collection fails because the module does not exist.
+- [ ] Add the pure codec. Embed the first 256 bytes of `docs/逆向成果-0918/Encode6BitBuf_替换表.bin` (verified SHA-256 `bd8e0d6b48cff81a2e25e497a86e5e5ef33fedab7cb4af6a1e0db05ddd59fe1e`) as `ENC_TABLE`; build the inverse table in memory. Reject bytes outside `0x3c..0x7b` and an incorrect `expected_size`.
+- [ ] Re-run the selected tests until green, then commit:
+
+```bash
+git add cq2_sdk/src/cq2_sdk/transport/encode6.py cq2_sdk/tests/unit/test_framing.py
+git commit -m 'feat: add verified 6-bit header codec'
+```
+
+### Task 3: Convert framing to the nine-word wire contract
+
+**Files:**
+- Modify: `cq2_sdk/src/cq2_sdk/transport/framing.py`
+- Modify: `cq2_sdk/src/cq2_sdk/transport/__init__.py`
+- Modify: `cq2_sdk/tests/unit/test_framing.py`
+- Modify: `cq2_sdk/tests/unit/test_recorder.py`
+
+**Interfaces:** `FrameHeader(pre, msg_id, recog, param1, param2, param3, codec, body_len, sequence)`, `encode_frame`, `decode_header`, `decode_frame`.
+
+- [ ] Replace the old 24-byte tests with failing behavior tests asserting: a body-bearing frame has `WIRE_HEADER_SIZE + len(body)` bytes; a round trip preserves `pre`, `sequence`, and body; a truncated body raises; and `codec=1` raises `unsupported body codec: 1`.
+- [ ] Update the recorder server test to read 48 bytes, use `decode_header`, and send its response through `encode_frame`; do not hand-unpack `<6i`.
+- [ ] Run `PYTHONPATH=cq2_sdk/src ../../.venv/bin/python -m pytest cq2_sdk/tests/unit/test_framing.py cq2_sdk/tests/unit/test_recorder.py -q` and confirm RED because old framing lacks fields and retains a 24-byte header.
+- [ ] Implement `PLAIN_HEADER_FORMAT = '<9i'`, `PLAIN_HEADER_SIZE = 36`, `WIRE_HEADER_SIZE = 48`; pack all nine words, encode exactly the plain header, append the body, and decode the 48-byte header with `decode6(..., expected_size=36)`.
+- [ ] Re-export new constants, run the affected tests until green, then commit:
+
+```bash
+git add cq2_sdk/src/cq2_sdk/transport/framing.py cq2_sdk/src/cq2_sdk/transport/__init__.py cq2_sdk/tests/unit/test_framing.py cq2_sdk/tests/unit/test_recorder.py
+git commit -m 'feat: encode verified TCP wire frames'
+```
+
+### Task 4: Update TCP buffering and sequence allocation
+
+**Files:**
+- Modify: `cq2_sdk/src/cq2_sdk/transport/tcp.py`
+- Modify: `cq2_sdk/tests/unit/test_tcp_client.py`
+- Modify: `cq2_sdk/src/cq2_sdk/client.py`
+
+**Interfaces:** `TcpClient(host, port, *, initial_sequence: int = 0)`; existing six-argument `send` remains unchanged.
+
+- [ ] Write failing loopback tests that: (1) send an encoded frame in slices `[:11]`, `[11:48]`, `[48:]` and assert the client receives the full header/body; (2) decode two outgoing 48-byte headers server-side and assert a client with `initial_sequence=9` emits sequences `9` then `10`.
+- [ ] Run `PYTHONPATH=cq2_sdk/src ../../.venv/bin/python -m pytest cq2_sdk/tests/unit/test_tcp_client.py -q` and confirm the new tests are RED because the client reads 24 bytes and has no sequence counter.
+- [ ] Remove the `encryption_key` argument and `xxtea` call. Buffer until `WIRE_HEADER_SIZE + body_len`; use `_next_sequence` in `encode_frame`, then increment only after frame construction. Update `client.py` to say frame layout is implemented while CS2001 body, true initial sequence, body codec, and state machine remain unverified.
+- [ ] Re-run TCP tests until green, then commit:
+
+```bash
+git add cq2_sdk/src/cq2_sdk/transport/tcp.py cq2_sdk/src/cq2_sdk/client.py cq2_sdk/tests/unit/test_tcp_client.py
+git commit -m 'feat: read real TCP wire frames'
+```
+
+### Task 5: Regression verification and review
+
+**Files:** no new source files unless a test finds a defect.
+
+- [ ] Run:
+
+```bash
+PYTHONPATH=cq2_sdk/src ../../.venv/bin/python -m pytest cq2_sdk/tests/unit/test_framing.py cq2_sdk/tests/unit/test_tcp_client.py cq2_sdk/tests/unit/test_recorder.py cq2_sdk/tests/unit/test_game_login.py -q
+git diff master...HEAD --check
+git status --short
+```
+
+- [ ] Confirm no token/capture/`.env` file is staged and request review with the test output and the explicit non-goals above.