sofapython 0.0.1rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sofapython/deframer.py ADDED
@@ -0,0 +1,73 @@
1
+ """Stream-to-frame splitter using the opcode-hi length invariant.
2
+
3
+ Every wire frame is ``SYNC0 SYNC1 hi lo payload(hi bytes) checksum``,
4
+ so the total length is ``5 + buf[2]`` once the sync bytes are aligned
5
+ (see ``docs/protocol/frame-format.md``). This module exposes that as
6
+ a small reusable :class:`Deframer`; it lives here rather than inside
7
+ ``x1_proxy.py`` because the transport bridge also frames bytes and
8
+ the proxy stays a thin orchestrator.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import List, Optional, Tuple
14
+
15
+ from .protocol_const import SYNC0, SYNC1
16
+
17
+
18
+ def _sum8(b: bytes) -> int:
19
+ return sum(b) & 0xFF
20
+
21
+
22
+ class Deframer:
23
+ def __init__(self) -> None:
24
+ self.buf = bytearray()
25
+ self._cur_start_cid: Optional[int] = None
26
+
27
+ def feed(self, data: bytes, cid: int) -> List[Tuple[int, bytes, bytes, int, int]]:
28
+ out: List[Tuple[int, bytes, bytes, int, int]] = []
29
+ if not data:
30
+ return out
31
+ self.buf.extend(data)
32
+ if len(self.buf) > 1_000_000:
33
+ del self.buf[:500_000]
34
+ self._cur_start_cid = None
35
+
36
+ while True:
37
+ if len(self.buf) < 2:
38
+ break
39
+ if self.buf[0] != SYNC0 or self.buf[1] != SYNC1:
40
+ idx = self.buf.find(bytes([SYNC0, SYNC1]))
41
+ if idx < 0:
42
+ # Preserve a lone trailing SYNC0 across reads.
43
+ if self.buf and self.buf[-1] == SYNC0:
44
+ del self.buf[:-1]
45
+ else:
46
+ self.buf.clear()
47
+ self._cur_start_cid = None
48
+ break
49
+ del self.buf[:idx]
50
+ self._cur_start_cid = None
51
+
52
+ if len(self.buf) < 5:
53
+ break
54
+ if self._cur_start_cid is None:
55
+ self._cur_start_cid = cid
56
+
57
+ frame_len = 5 + self.buf[2]
58
+ if len(self.buf) < frame_len:
59
+ break
60
+
61
+ cand = bytes(self.buf[:frame_len])
62
+ if cand[-1] == (_sum8(cand[:-1]) & 0xFF):
63
+ opcode = (cand[2] << 8) | cand[3]
64
+ out.append((opcode, cand, cand[4:-1], self._cur_start_cid, cid))
65
+ del self.buf[:frame_len]
66
+ self._cur_start_cid = None
67
+ continue
68
+
69
+ # Bad checksum at this sync: drop one byte and rescan.
70
+ del self.buf[0]
71
+ self._cur_start_cid = None
72
+
73
+ return out