xref: /aosp_15_r20/external/pigweed/pw_hdlc/py/decode_test.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker#!/usr/bin/env python
2*61c4878aSAndroid Build Coastguard Worker# Copyright 2020 The Pigweed Authors
3*61c4878aSAndroid Build Coastguard Worker#
4*61c4878aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); you may not
5*61c4878aSAndroid Build Coastguard Worker# use this file except in compliance with the License. You may obtain a copy of
6*61c4878aSAndroid Build Coastguard Worker# the License at
7*61c4878aSAndroid Build Coastguard Worker#
8*61c4878aSAndroid Build Coastguard Worker#     https://www.apache.org/licenses/LICENSE-2.0
9*61c4878aSAndroid Build Coastguard Worker#
10*61c4878aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*61c4878aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12*61c4878aSAndroid Build Coastguard Worker# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13*61c4878aSAndroid Build Coastguard Worker# License for the specific language governing permissions and limitations under
14*61c4878aSAndroid Build Coastguard Worker# the License.
15*61c4878aSAndroid Build Coastguard Worker"""Contains the Python decoder tests and generates C++ decoder tests."""
16*61c4878aSAndroid Build Coastguard Worker
17*61c4878aSAndroid Build Coastguard Workerfrom __future__ import annotations
18*61c4878aSAndroid Build Coastguard Worker
19*61c4878aSAndroid Build Coastguard Workerimport queue
20*61c4878aSAndroid Build Coastguard Workerfrom typing import Iterator, NamedTuple
21*61c4878aSAndroid Build Coastguard Workerimport unittest
22*61c4878aSAndroid Build Coastguard Worker
23*61c4878aSAndroid Build Coastguard Workerfrom pw_build.generated_tests import Context, PyTest, TestGenerator, GroupOrTest
24*61c4878aSAndroid Build Coastguard Workerfrom pw_build.generated_tests import parse_test_generation_args
25*61c4878aSAndroid Build Coastguard Workerfrom pw_hdlc.decode import (
26*61c4878aSAndroid Build Coastguard Worker    Frame,
27*61c4878aSAndroid Build Coastguard Worker    FrameDecoder,
28*61c4878aSAndroid Build Coastguard Worker    FrameAndNonFrameDecoder,
29*61c4878aSAndroid Build Coastguard Worker    FrameStatus,
30*61c4878aSAndroid Build Coastguard Worker    NO_ADDRESS,
31*61c4878aSAndroid Build Coastguard Worker)
32*61c4878aSAndroid Build Coastguard Workerfrom pw_hdlc.protocol import frame_check_sequence as fcs
33*61c4878aSAndroid Build Coastguard Workerfrom pw_hdlc.protocol import encode_address
34*61c4878aSAndroid Build Coastguard Worker
35*61c4878aSAndroid Build Coastguard Worker
36*61c4878aSAndroid Build Coastguard Workerdef _encode(address: int, control: int, data: bytes) -> bytes:
37*61c4878aSAndroid Build Coastguard Worker    frame = encode_address(address) + bytes([control]) + data
38*61c4878aSAndroid Build Coastguard Worker    frame += fcs(frame)
39*61c4878aSAndroid Build Coastguard Worker    frame = frame.replace(b'}', b'}\x5d')
40*61c4878aSAndroid Build Coastguard Worker    frame = frame.replace(b'~', b'}\x5e')
41*61c4878aSAndroid Build Coastguard Worker    return b''.join([b'~', frame, b'~'])
42*61c4878aSAndroid Build Coastguard Worker
43*61c4878aSAndroid Build Coastguard Worker
44*61c4878aSAndroid Build Coastguard Workerclass Expected(NamedTuple):
45*61c4878aSAndroid Build Coastguard Worker    address: int
46*61c4878aSAndroid Build Coastguard Worker    control: bytes
47*61c4878aSAndroid Build Coastguard Worker    data: bytes
48*61c4878aSAndroid Build Coastguard Worker    status: FrameStatus = FrameStatus.OK
49*61c4878aSAndroid Build Coastguard Worker
50*61c4878aSAndroid Build Coastguard Worker    @classmethod
51*61c4878aSAndroid Build Coastguard Worker    def error(cls, status: FrameStatus):
52*61c4878aSAndroid Build Coastguard Worker        assert status is not FrameStatus.OK
53*61c4878aSAndroid Build Coastguard Worker        return cls(NO_ADDRESS, b'', b'', status)
54*61c4878aSAndroid Build Coastguard Worker
55*61c4878aSAndroid Build Coastguard Worker    def __eq__(self, other) -> bool:
56*61c4878aSAndroid Build Coastguard Worker        """Define == so an Expected and a Frame can be compared."""
57*61c4878aSAndroid Build Coastguard Worker        return (
58*61c4878aSAndroid Build Coastguard Worker            self.address == other.address
59*61c4878aSAndroid Build Coastguard Worker            and self.control == other.control
60*61c4878aSAndroid Build Coastguard Worker            and self.data == other.data
61*61c4878aSAndroid Build Coastguard Worker            and self.status is other.status
62*61c4878aSAndroid Build Coastguard Worker        )
63*61c4878aSAndroid Build Coastguard Worker
64*61c4878aSAndroid Build Coastguard Worker
65*61c4878aSAndroid Build Coastguard Workerclass ExpectedRaw(NamedTuple):
66*61c4878aSAndroid Build Coastguard Worker    raw_encoded: bytes
67*61c4878aSAndroid Build Coastguard Worker    status: FrameStatus
68*61c4878aSAndroid Build Coastguard Worker
69*61c4878aSAndroid Build Coastguard Worker    def __eq__(self, other) -> bool:
70*61c4878aSAndroid Build Coastguard Worker        """Define == so an ExpectedRaw and a Frame can be compared."""
71*61c4878aSAndroid Build Coastguard Worker        return (
72*61c4878aSAndroid Build Coastguard Worker            self.raw_encoded == other.raw_encoded
73*61c4878aSAndroid Build Coastguard Worker            and self.status is other.status
74*61c4878aSAndroid Build Coastguard Worker        )
75*61c4878aSAndroid Build Coastguard Worker
76*61c4878aSAndroid Build Coastguard Worker
77*61c4878aSAndroid Build Coastguard Workerclass TestCase(NamedTuple):
78*61c4878aSAndroid Build Coastguard Worker    data: bytes
79*61c4878aSAndroid Build Coastguard Worker    frames: list[Expected | ExpectedRaw]
80*61c4878aSAndroid Build Coastguard Worker    raw_data: bytes
81*61c4878aSAndroid Build Coastguard Worker
82*61c4878aSAndroid Build Coastguard Worker
83*61c4878aSAndroid Build Coastguard Workerdef case(data: bytes, frames: list, raw: bytes | None = None) -> TestCase:
84*61c4878aSAndroid Build Coastguard Worker    """Creates a TestCase, filling in the default value for the raw bytes."""
85*61c4878aSAndroid Build Coastguard Worker    if raw is not None:
86*61c4878aSAndroid Build Coastguard Worker        return TestCase(data, frames, raw)
87*61c4878aSAndroid Build Coastguard Worker    if not frames or all(f.status is not FrameStatus.OK for f in frames):
88*61c4878aSAndroid Build Coastguard Worker        return TestCase(data, frames, data)
89*61c4878aSAndroid Build Coastguard Worker    if all(f.status is FrameStatus.OK for f in frames):
90*61c4878aSAndroid Build Coastguard Worker        return TestCase(data, frames, b'')
91*61c4878aSAndroid Build Coastguard Worker    raise AssertionError(
92*61c4878aSAndroid Build Coastguard Worker        f'Must specify expected non-frame data for this test case ({data=})!'
93*61c4878aSAndroid Build Coastguard Worker    )
94*61c4878aSAndroid Build Coastguard Worker
95*61c4878aSAndroid Build Coastguard Worker
96*61c4878aSAndroid Build Coastguard Worker_PARTIAL = fcs(b'\x0ACmsg\x5e')
97*61c4878aSAndroid Build Coastguard Worker_ESCAPED_FLAG_TEST_CASE = case(
98*61c4878aSAndroid Build Coastguard Worker    b'~\x0ACmsg}~' + _PARTIAL + b'~',
99*61c4878aSAndroid Build Coastguard Worker    [
100*61c4878aSAndroid Build Coastguard Worker        Expected.error(FrameStatus.FRAMING_ERROR),
101*61c4878aSAndroid Build Coastguard Worker        Expected.error(FrameStatus.FRAMING_ERROR),
102*61c4878aSAndroid Build Coastguard Worker    ],
103*61c4878aSAndroid Build Coastguard Worker)
104*61c4878aSAndroid Build Coastguard Worker
105*61c4878aSAndroid Build Coastguard Worker# Test cases are a tuple with the following elements:
106*61c4878aSAndroid Build Coastguard Worker#
107*61c4878aSAndroid Build Coastguard Worker#   - raw data stream
108*61c4878aSAndroid Build Coastguard Worker#   - expected valid & invalid frames
109*61c4878aSAndroid Build Coastguard Worker#   - [optional] expected raw, non-HDLC data; defaults to the full raw data
110*61c4878aSAndroid Build Coastguard Worker#     stream if no valid frames are expected, or b'' if only valid frames are
111*61c4878aSAndroid Build Coastguard Worker#     expected
112*61c4878aSAndroid Build Coastguard Worker#
113*61c4878aSAndroid Build Coastguard Worker# These tests are executed twice: once for the standard HDLC decoder, and a
114*61c4878aSAndroid Build Coastguard Worker# second time for the FrameAndNonFrameDecoder. The FrameAndNonFrameDecoder tests
115*61c4878aSAndroid Build Coastguard Worker# flush the non-frame data to simulate a timeout or MTU overflow, so the
116*61c4878aSAndroid Build Coastguard Worker# expected raw data includes all bytes not in an HDLC frame.
117*61c4878aSAndroid Build Coastguard WorkerTEST_CASES: tuple[GroupOrTest[TestCase], ...] = (
118*61c4878aSAndroid Build Coastguard Worker    'Empty payload',
119*61c4878aSAndroid Build Coastguard Worker    case(_encode(0, 0, b''), [Expected(0, b'\0', b'')]),
120*61c4878aSAndroid Build Coastguard Worker    case(_encode(55, 0x99, b''), [Expected(55, b'\x99', b'')]),
121*61c4878aSAndroid Build Coastguard Worker    case(_encode(55, 0x99, b'') * 3, [Expected(55, b'\x99', b'')] * 3),
122*61c4878aSAndroid Build Coastguard Worker    'Simple one-byte payload',
123*61c4878aSAndroid Build Coastguard Worker    case(_encode(0, 0, b'\0'), [Expected(0, b'\0', b'\0')]),
124*61c4878aSAndroid Build Coastguard Worker    case(_encode(123, 0, b'A'), [Expected(123, b'\0', b'A')]),
125*61c4878aSAndroid Build Coastguard Worker    'Simple multi-byte payload',
126*61c4878aSAndroid Build Coastguard Worker    case(
127*61c4878aSAndroid Build Coastguard Worker        _encode(0, 0, b'Hello, world!'), [Expected(0, b'\0', b'Hello, world!')]
128*61c4878aSAndroid Build Coastguard Worker    ),
129*61c4878aSAndroid Build Coastguard Worker    case(_encode(123, 0, b'\0\0\1\0\0'), [Expected(123, b'\0', b'\0\0\1\0\0')]),
130*61c4878aSAndroid Build Coastguard Worker    'Escaped one-byte payload',
131*61c4878aSAndroid Build Coastguard Worker    case(_encode(1, 2, b'~'), [Expected(1, b'\2', b'~')]),
132*61c4878aSAndroid Build Coastguard Worker    case(_encode(1, 2, b'}'), [Expected(1, b'\2', b'}')]),
133*61c4878aSAndroid Build Coastguard Worker    case(
134*61c4878aSAndroid Build Coastguard Worker        _encode(1, 2, b'~') + _encode(1, 2, b'}'),
135*61c4878aSAndroid Build Coastguard Worker        [Expected(1, b'\2', b'~'), Expected(1, b'\2', b'}')],
136*61c4878aSAndroid Build Coastguard Worker    ),
137*61c4878aSAndroid Build Coastguard Worker    'Escaped address',
138*61c4878aSAndroid Build Coastguard Worker    case(_encode(0x7E, 0, b'A'), [Expected(0x7E, b'\0', b'A')]),
139*61c4878aSAndroid Build Coastguard Worker    case(_encode(0x7D, 0, b'B'), [Expected(0x7D, b'\0', b'B')]),
140*61c4878aSAndroid Build Coastguard Worker    'Escaped control',
141*61c4878aSAndroid Build Coastguard Worker    case(_encode(0, 0x7E, b'C'), [Expected(0, b'~', b'C')]),
142*61c4878aSAndroid Build Coastguard Worker    case(_encode(0, 0x7D, b'D'), [Expected(0, b'}', b'D')]),
143*61c4878aSAndroid Build Coastguard Worker    'Escaped address and control',
144*61c4878aSAndroid Build Coastguard Worker    case(_encode(0x7E, 0x7D, b'E'), [Expected(0x7E, b'}', b'E')]),
145*61c4878aSAndroid Build Coastguard Worker    case(_encode(0x7D, 0x7E, b'F'), [Expected(0x7D, b'~', b'F')]),
146*61c4878aSAndroid Build Coastguard Worker    case(_encode(0x7E, 0x7E, b'~'), [Expected(0x7E, b'~', b'~')]),
147*61c4878aSAndroid Build Coastguard Worker    'Multibyte address',
148*61c4878aSAndroid Build Coastguard Worker    case(
149*61c4878aSAndroid Build Coastguard Worker        _encode(128, 0, b'big address'), [Expected(128, b'\0', b'big address')]
150*61c4878aSAndroid Build Coastguard Worker    ),
151*61c4878aSAndroid Build Coastguard Worker    case(
152*61c4878aSAndroid Build Coastguard Worker        _encode(0xFFFFFFFF, 0, b'\0\0\1\0\0'),
153*61c4878aSAndroid Build Coastguard Worker        [Expected(0xFFFFFFFF, b'\0', b'\0\0\1\0\0')],
154*61c4878aSAndroid Build Coastguard Worker    ),
155*61c4878aSAndroid Build Coastguard Worker    'Multiple frames separated by single flag',
156*61c4878aSAndroid Build Coastguard Worker    case(
157*61c4878aSAndroid Build Coastguard Worker        _encode(0, 0, b'A')[:-1] + _encode(1, 2, b'123'),
158*61c4878aSAndroid Build Coastguard Worker        [Expected(0, b'\0', b'A'), Expected(1, b'\2', b'123')],
159*61c4878aSAndroid Build Coastguard Worker    ),
160*61c4878aSAndroid Build Coastguard Worker    case(
161*61c4878aSAndroid Build Coastguard Worker        _encode(0xFF, 0, b'Yo')[:-1] * 3 + b'~',
162*61c4878aSAndroid Build Coastguard Worker        [Expected(0xFF, b'\0', b'Yo')] * 3,
163*61c4878aSAndroid Build Coastguard Worker    ),
164*61c4878aSAndroid Build Coastguard Worker    'Empty frames produce framing errors with raw data',
165*61c4878aSAndroid Build Coastguard Worker    case(b'~~', [ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR)], b'~~'),
166*61c4878aSAndroid Build Coastguard Worker    case(
167*61c4878aSAndroid Build Coastguard Worker        b'~' * 10,
168*61c4878aSAndroid Build Coastguard Worker        [
169*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
170*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
171*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
172*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
173*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
174*61c4878aSAndroid Build Coastguard Worker        ],
175*61c4878aSAndroid Build Coastguard Worker    ),
176*61c4878aSAndroid Build Coastguard Worker    case(
177*61c4878aSAndroid Build Coastguard Worker        b'~~' + _encode(1, 2, b'3') + b'~' * 5,
178*61c4878aSAndroid Build Coastguard Worker        [
179*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
180*61c4878aSAndroid Build Coastguard Worker            Expected(1, b'\2', b'3'),
181*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
182*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
183*61c4878aSAndroid Build Coastguard Worker            # One flag byte remains in the decoding state machine.
184*61c4878aSAndroid Build Coastguard Worker        ],
185*61c4878aSAndroid Build Coastguard Worker        b'~~~~~~~',
186*61c4878aSAndroid Build Coastguard Worker    ),
187*61c4878aSAndroid Build Coastguard Worker    case(
188*61c4878aSAndroid Build Coastguard Worker        b'~' * 10 + _encode(1, 2, b':O') + b'~' * 3 + _encode(3, 4, b':P'),
189*61c4878aSAndroid Build Coastguard Worker        [
190*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
191*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
192*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
193*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
194*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
195*61c4878aSAndroid Build Coastguard Worker            Expected(1, b'\2', b':O'),
196*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
197*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
198*61c4878aSAndroid Build Coastguard Worker            Expected(3, b'\4', b':P'),
199*61c4878aSAndroid Build Coastguard Worker        ],
200*61c4878aSAndroid Build Coastguard Worker        b'~' * 13,
201*61c4878aSAndroid Build Coastguard Worker    ),
202*61c4878aSAndroid Build Coastguard Worker    'Cannot escape flag',
203*61c4878aSAndroid Build Coastguard Worker    case(
204*61c4878aSAndroid Build Coastguard Worker        b'~\xAA}~\xab\x00Hello' + fcs(b'\xab\0Hello') + b'~',
205*61c4878aSAndroid Build Coastguard Worker        [
206*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FRAMING_ERROR),
207*61c4878aSAndroid Build Coastguard Worker            Expected(0x55, b'\0', b'Hello'),
208*61c4878aSAndroid Build Coastguard Worker        ],
209*61c4878aSAndroid Build Coastguard Worker        b'~\xAA}',
210*61c4878aSAndroid Build Coastguard Worker    ),
211*61c4878aSAndroid Build Coastguard Worker    _ESCAPED_FLAG_TEST_CASE,
212*61c4878aSAndroid Build Coastguard Worker    'Frame too short',
213*61c4878aSAndroid Build Coastguard Worker    case(b'~1~', [Expected.error(FrameStatus.FRAMING_ERROR)]),
214*61c4878aSAndroid Build Coastguard Worker    case(b'~12~', [Expected.error(FrameStatus.FRAMING_ERROR)]),
215*61c4878aSAndroid Build Coastguard Worker    case(b'~12345~', [Expected.error(FrameStatus.FRAMING_ERROR)]),
216*61c4878aSAndroid Build Coastguard Worker    'Multibyte address too long',
217*61c4878aSAndroid Build Coastguard Worker    case(
218*61c4878aSAndroid Build Coastguard Worker        _encode(2**100, 0, b'too long'),
219*61c4878aSAndroid Build Coastguard Worker        [Expected.error(FrameStatus.BAD_ADDRESS)],
220*61c4878aSAndroid Build Coastguard Worker    ),
221*61c4878aSAndroid Build Coastguard Worker    'Incorrect frame check sequence',
222*61c4878aSAndroid Build Coastguard Worker    case(b'~123456~', [Expected.error(FrameStatus.FCS_MISMATCH)]),
223*61c4878aSAndroid Build Coastguard Worker    case(
224*61c4878aSAndroid Build Coastguard Worker        b'~\1\2msg\xff\xff\xff\xff~', [Expected.error(FrameStatus.FCS_MISMATCH)]
225*61c4878aSAndroid Build Coastguard Worker    ),
226*61c4878aSAndroid Build Coastguard Worker    case(
227*61c4878aSAndroid Build Coastguard Worker        _encode(0xA, 0xB, b'???')[:-2] + _encode(1, 2, b'def'),
228*61c4878aSAndroid Build Coastguard Worker        [
229*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FCS_MISMATCH),
230*61c4878aSAndroid Build Coastguard Worker            Expected(1, b'\2', b'def'),
231*61c4878aSAndroid Build Coastguard Worker        ],
232*61c4878aSAndroid Build Coastguard Worker        _encode(0xA, 0xB, b'???')[:-2],
233*61c4878aSAndroid Build Coastguard Worker    ),
234*61c4878aSAndroid Build Coastguard Worker    'Invalid escape in address',
235*61c4878aSAndroid Build Coastguard Worker    case(
236*61c4878aSAndroid Build Coastguard Worker        b'~}}\0' + fcs(b'\x5d\0') + b'~',
237*61c4878aSAndroid Build Coastguard Worker        [Expected.error(FrameStatus.FRAMING_ERROR)],
238*61c4878aSAndroid Build Coastguard Worker    ),
239*61c4878aSAndroid Build Coastguard Worker    'Invalid escape in control',
240*61c4878aSAndroid Build Coastguard Worker    case(
241*61c4878aSAndroid Build Coastguard Worker        b'~\0}}' + fcs(b'\0\x5d') + b'~',
242*61c4878aSAndroid Build Coastguard Worker        [Expected.error(FrameStatus.FRAMING_ERROR)],
243*61c4878aSAndroid Build Coastguard Worker    ),
244*61c4878aSAndroid Build Coastguard Worker    'Invalid escape in data',
245*61c4878aSAndroid Build Coastguard Worker    case(
246*61c4878aSAndroid Build Coastguard Worker        b'~\0\1}}' + fcs(b'\0\1\x5d') + b'~',
247*61c4878aSAndroid Build Coastguard Worker        [Expected.error(FrameStatus.FRAMING_ERROR)],
248*61c4878aSAndroid Build Coastguard Worker    ),
249*61c4878aSAndroid Build Coastguard Worker    'Frame ends with escape',
250*61c4878aSAndroid Build Coastguard Worker    case(b'~}~', [Expected.error(FrameStatus.FRAMING_ERROR)]),
251*61c4878aSAndroid Build Coastguard Worker    case(b'~\1}~', [Expected.error(FrameStatus.FRAMING_ERROR)]),
252*61c4878aSAndroid Build Coastguard Worker    case(b'~\1\2abc}~', [Expected.error(FrameStatus.FRAMING_ERROR)]),
253*61c4878aSAndroid Build Coastguard Worker    case(b'~\1\2abcd}~', [Expected.error(FrameStatus.FRAMING_ERROR)]),
254*61c4878aSAndroid Build Coastguard Worker    case(b'~\1\2abcd1234}~', [Expected.error(FrameStatus.FRAMING_ERROR)]),
255*61c4878aSAndroid Build Coastguard Worker    'Inter-frame data is only escapes',
256*61c4878aSAndroid Build Coastguard Worker    case(
257*61c4878aSAndroid Build Coastguard Worker        b'~}~}~',
258*61c4878aSAndroid Build Coastguard Worker        [
259*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FRAMING_ERROR),
260*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FRAMING_ERROR),
261*61c4878aSAndroid Build Coastguard Worker        ],
262*61c4878aSAndroid Build Coastguard Worker    ),
263*61c4878aSAndroid Build Coastguard Worker    case(
264*61c4878aSAndroid Build Coastguard Worker        b'~}}~}}~',
265*61c4878aSAndroid Build Coastguard Worker        [
266*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FRAMING_ERROR),
267*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FRAMING_ERROR),
268*61c4878aSAndroid Build Coastguard Worker        ],
269*61c4878aSAndroid Build Coastguard Worker    ),
270*61c4878aSAndroid Build Coastguard Worker    'Data before first flag',
271*61c4878aSAndroid Build Coastguard Worker    case(b'\0\1' + fcs(b'\0\1'), []),
272*61c4878aSAndroid Build Coastguard Worker    case(
273*61c4878aSAndroid Build Coastguard Worker        b'\0\1' + fcs(b'\0\1') + b'~',
274*61c4878aSAndroid Build Coastguard Worker        [Expected.error(FrameStatus.FRAMING_ERROR)],
275*61c4878aSAndroid Build Coastguard Worker    ),
276*61c4878aSAndroid Build Coastguard Worker    'No frames emitted until flag',
277*61c4878aSAndroid Build Coastguard Worker    case(_encode(1, 2, b'3')[:-1], []),
278*61c4878aSAndroid Build Coastguard Worker    case(b'~' + _encode(1, 2, b'3')[1:-1] * 2, []),
279*61c4878aSAndroid Build Coastguard Worker    'Only flag and escape characters can be escaped',
280*61c4878aSAndroid Build Coastguard Worker    case(
281*61c4878aSAndroid Build Coastguard Worker        b'~}\0' + _encode(1, 2, b'3'),
282*61c4878aSAndroid Build Coastguard Worker        [Expected.error(FrameStatus.FRAMING_ERROR), Expected(1, b'\2', b'3')],
283*61c4878aSAndroid Build Coastguard Worker        b'~}\0',
284*61c4878aSAndroid Build Coastguard Worker    ),
285*61c4878aSAndroid Build Coastguard Worker    case(
286*61c4878aSAndroid Build Coastguard Worker        b'~1234}a' + _encode(1, 2, b'3'),
287*61c4878aSAndroid Build Coastguard Worker        [Expected.error(FrameStatus.FRAMING_ERROR), Expected(1, b'\2', b'3')],
288*61c4878aSAndroid Build Coastguard Worker        b'~1234}a',
289*61c4878aSAndroid Build Coastguard Worker    ),
290*61c4878aSAndroid Build Coastguard Worker    'Invalid frame records raw data',
291*61c4878aSAndroid Build Coastguard Worker    case(b'Hello?~', [ExpectedRaw(b'Hello?~', FrameStatus.FRAMING_ERROR)]),
292*61c4878aSAndroid Build Coastguard Worker    case(
293*61c4878aSAndroid Build Coastguard Worker        b'~~Hel}}lo~',
294*61c4878aSAndroid Build Coastguard Worker        [
295*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FRAMING_ERROR),
296*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'Hel}}lo~', FrameStatus.FRAMING_ERROR),
297*61c4878aSAndroid Build Coastguard Worker        ],
298*61c4878aSAndroid Build Coastguard Worker    ),
299*61c4878aSAndroid Build Coastguard Worker    case(
300*61c4878aSAndroid Build Coastguard Worker        b'Hello?~~~~~',
301*61c4878aSAndroid Build Coastguard Worker        [
302*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'Hello?~', FrameStatus.FRAMING_ERROR),
303*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FRAMING_ERROR),
304*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FRAMING_ERROR),
305*61c4878aSAndroid Build Coastguard Worker        ],
306*61c4878aSAndroid Build Coastguard Worker    ),
307*61c4878aSAndroid Build Coastguard Worker    case(
308*61c4878aSAndroid Build Coastguard Worker        b'~~~~Hello?~~~~~',
309*61c4878aSAndroid Build Coastguard Worker        [
310*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
311*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
312*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'Hello?~', FrameStatus.FCS_MISMATCH),
313*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
314*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~~', FrameStatus.FRAMING_ERROR),
315*61c4878aSAndroid Build Coastguard Worker        ],
316*61c4878aSAndroid Build Coastguard Worker    ),
317*61c4878aSAndroid Build Coastguard Worker    case(
318*61c4878aSAndroid Build Coastguard Worker        b'Hello?~~Goodbye~',
319*61c4878aSAndroid Build Coastguard Worker        [
320*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'Hello?~', FrameStatus.FRAMING_ERROR),
321*61c4878aSAndroid Build Coastguard Worker            ExpectedRaw(b'~Goodbye~', FrameStatus.FCS_MISMATCH),
322*61c4878aSAndroid Build Coastguard Worker        ],
323*61c4878aSAndroid Build Coastguard Worker    ),
324*61c4878aSAndroid Build Coastguard Worker    'Valid data followed by frame followed by invalid',
325*61c4878aSAndroid Build Coastguard Worker    case(
326*61c4878aSAndroid Build Coastguard Worker        b'Hi~ this is a log message\r\n'
327*61c4878aSAndroid Build Coastguard Worker        + _encode(0, 0, b'')
328*61c4878aSAndroid Build Coastguard Worker        + b'More log messages!\r\n',
329*61c4878aSAndroid Build Coastguard Worker        [
330*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FRAMING_ERROR),
331*61c4878aSAndroid Build Coastguard Worker            Expected.error(FrameStatus.FCS_MISMATCH),
332*61c4878aSAndroid Build Coastguard Worker            Expected(0, b'\0', b''),
333*61c4878aSAndroid Build Coastguard Worker        ],
334*61c4878aSAndroid Build Coastguard Worker        b'Hi~ this is a log message\r\nMore log messages!\r\n',
335*61c4878aSAndroid Build Coastguard Worker    ),
336*61c4878aSAndroid Build Coastguard Worker    case(
337*61c4878aSAndroid Build Coastguard Worker        b'Hi~ this is a log message\r\n',
338*61c4878aSAndroid Build Coastguard Worker        [Expected.error(FrameStatus.FRAMING_ERROR)],
339*61c4878aSAndroid Build Coastguard Worker    ),
340*61c4878aSAndroid Build Coastguard Worker    case(
341*61c4878aSAndroid Build Coastguard Worker        b'~Hi~' + _encode(1, 2, b'def') + b' How are you?',
342*61c4878aSAndroid Build Coastguard Worker        [Expected.error(FrameStatus.FRAMING_ERROR), Expected(1, b'\2', b'def')],
343*61c4878aSAndroid Build Coastguard Worker        b'~Hi~ How are you?',
344*61c4878aSAndroid Build Coastguard Worker    ),
345*61c4878aSAndroid Build Coastguard Worker)
346*61c4878aSAndroid Build Coastguard Worker# Formatting for the above tuple is very slow, so disable yapf. Manually enable
347*61c4878aSAndroid Build Coastguard Worker# it as needed to format the test cases.
348*61c4878aSAndroid Build Coastguard Worker
349*61c4878aSAndroid Build Coastguard Worker_TESTS = TestGenerator(TEST_CASES)
350*61c4878aSAndroid Build Coastguard Worker
351*61c4878aSAndroid Build Coastguard Worker
352*61c4878aSAndroid Build Coastguard Workerdef _expected(frames: list[Frame]) -> Iterator[str]:
353*61c4878aSAndroid Build Coastguard Worker    for i, frame in enumerate(frames, 1):
354*61c4878aSAndroid Build Coastguard Worker        if frame.ok():
355*61c4878aSAndroid Build Coastguard Worker            yield f'      Frame::Parse(kDecodedFrame{i:02}).value(),'
356*61c4878aSAndroid Build Coastguard Worker        elif frame.status is FrameStatus.BAD_ADDRESS:
357*61c4878aSAndroid Build Coastguard Worker            yield f'      Frame::Parse(kDecodedFrame{i:02}).status(),'
358*61c4878aSAndroid Build Coastguard Worker        else:
359*61c4878aSAndroid Build Coastguard Worker            yield f'      Status::DataLoss(),  // Frame {i}'
360*61c4878aSAndroid Build Coastguard Worker
361*61c4878aSAndroid Build Coastguard Worker
362*61c4878aSAndroid Build Coastguard Worker_CPP_HEADER = """\
363*61c4878aSAndroid Build Coastguard Worker#include "pw_hdlc/decoder.h"
364*61c4878aSAndroid Build Coastguard Worker
365*61c4878aSAndroid Build Coastguard Worker#include <array>
366*61c4878aSAndroid Build Coastguard Worker#include <cstddef>
367*61c4878aSAndroid Build Coastguard Worker#include <variant>
368*61c4878aSAndroid Build Coastguard Worker
369*61c4878aSAndroid Build Coastguard Worker#include "pw_unit_test/framework.h"
370*61c4878aSAndroid Build Coastguard Worker#include "pw_bytes/array.h"
371*61c4878aSAndroid Build Coastguard Worker
372*61c4878aSAndroid Build Coastguard Workernamespace pw::hdlc {
373*61c4878aSAndroid Build Coastguard Workernamespace {
374*61c4878aSAndroid Build Coastguard Worker"""
375*61c4878aSAndroid Build Coastguard Worker
376*61c4878aSAndroid Build Coastguard Worker_CPP_FOOTER = """\
377*61c4878aSAndroid Build Coastguard Worker}  // namespace
378*61c4878aSAndroid Build Coastguard Worker}  // namespace pw::hdlc"""
379*61c4878aSAndroid Build Coastguard Worker
380*61c4878aSAndroid Build Coastguard Worker_TS_HEADER = """\
381*61c4878aSAndroid Build Coastguard Workerimport 'jasmine';
382*61c4878aSAndroid Build Coastguard Worker
383*61c4878aSAndroid Build Coastguard Workerimport {Buffer} from 'buffer';
384*61c4878aSAndroid Build Coastguard Worker
385*61c4878aSAndroid Build Coastguard Workerimport {Decoder, FrameStatus} from './decoder'
386*61c4878aSAndroid Build Coastguard Workerimport * as protocol from './protocol'
387*61c4878aSAndroid Build Coastguard Workerimport * as util from './util'
388*61c4878aSAndroid Build Coastguard Worker
389*61c4878aSAndroid Build Coastguard Workerclass Expected {
390*61c4878aSAndroid Build Coastguard Worker  address: number
391*61c4878aSAndroid Build Coastguard Worker  control: Uint8Array
392*61c4878aSAndroid Build Coastguard Worker  data: Uint8Array
393*61c4878aSAndroid Build Coastguard Worker  status: FrameStatus
394*61c4878aSAndroid Build Coastguard Worker
395*61c4878aSAndroid Build Coastguard Worker  constructor(
396*61c4878aSAndroid Build Coastguard Worker      address: number,
397*61c4878aSAndroid Build Coastguard Worker      control: Uint8Array,
398*61c4878aSAndroid Build Coastguard Worker      data: Uint8Array,
399*61c4878aSAndroid Build Coastguard Worker      status: FrameStatus) {
400*61c4878aSAndroid Build Coastguard Worker    this.address = address;
401*61c4878aSAndroid Build Coastguard Worker    this.control = control;
402*61c4878aSAndroid Build Coastguard Worker    this.data = data;
403*61c4878aSAndroid Build Coastguard Worker    this.status = status;
404*61c4878aSAndroid Build Coastguard Worker  }
405*61c4878aSAndroid Build Coastguard Worker}
406*61c4878aSAndroid Build Coastguard Worker
407*61c4878aSAndroid Build Coastguard Workerclass ExpectedRaw {
408*61c4878aSAndroid Build Coastguard Worker  raw: Uint8Array
409*61c4878aSAndroid Build Coastguard Worker  status: FrameStatus
410*61c4878aSAndroid Build Coastguard Worker
411*61c4878aSAndroid Build Coastguard Worker  constructor(raw: Uint8Array, status: FrameStatus) {
412*61c4878aSAndroid Build Coastguard Worker    this.status = status;
413*61c4878aSAndroid Build Coastguard Worker    this.raw = raw;
414*61c4878aSAndroid Build Coastguard Worker  }
415*61c4878aSAndroid Build Coastguard Worker}
416*61c4878aSAndroid Build Coastguard Worker
417*61c4878aSAndroid Build Coastguard Workerdescribe('Decoder', () => {
418*61c4878aSAndroid Build Coastguard Worker  let decoder: Decoder;
419*61c4878aSAndroid Build Coastguard Worker  let textEncoder: TextEncoder;
420*61c4878aSAndroid Build Coastguard Worker
421*61c4878aSAndroid Build Coastguard Worker  beforeEach(() => {
422*61c4878aSAndroid Build Coastguard Worker    decoder = new Decoder();
423*61c4878aSAndroid Build Coastguard Worker    textEncoder = new TextEncoder();
424*61c4878aSAndroid Build Coastguard Worker  });
425*61c4878aSAndroid Build Coastguard Worker
426*61c4878aSAndroid Build Coastguard Worker"""
427*61c4878aSAndroid Build Coastguard Worker_TS_FOOTER = """\
428*61c4878aSAndroid Build Coastguard Worker});
429*61c4878aSAndroid Build Coastguard Worker"""
430*61c4878aSAndroid Build Coastguard Worker
431*61c4878aSAndroid Build Coastguard Worker
432*61c4878aSAndroid Build Coastguard Workerdef _py_only_frame(frame: Frame) -> bool:
433*61c4878aSAndroid Build Coastguard Worker    """Returns true for frames only returned by the Python library"""
434*61c4878aSAndroid Build Coastguard Worker    return (
435*61c4878aSAndroid Build Coastguard Worker        frame.status is FrameStatus.FRAMING_ERROR and frame.raw_encoded == b'~~'
436*61c4878aSAndroid Build Coastguard Worker    )
437*61c4878aSAndroid Build Coastguard Worker
438*61c4878aSAndroid Build Coastguard Worker
439*61c4878aSAndroid Build Coastguard Workerdef _cpp_test(ctx: Context) -> Iterator[str]:
440*61c4878aSAndroid Build Coastguard Worker    """Generates a C++ test for the provided test data."""
441*61c4878aSAndroid Build Coastguard Worker    data, _, _ = ctx.test_case
442*61c4878aSAndroid Build Coastguard Worker    frames = [
443*61c4878aSAndroid Build Coastguard Worker        f for f in list(FrameDecoder().process(data)) if not _py_only_frame(f)
444*61c4878aSAndroid Build Coastguard Worker    ]
445*61c4878aSAndroid Build Coastguard Worker    data_bytes = ''.join(rf'\x{byte:02x}' for byte in data)
446*61c4878aSAndroid Build Coastguard Worker
447*61c4878aSAndroid Build Coastguard Worker    yield f'TEST(Decoder, {ctx.cc_name()}) {{'
448*61c4878aSAndroid Build Coastguard Worker    yield f'  static constexpr auto kData = bytes::String("{data_bytes}");\n'
449*61c4878aSAndroid Build Coastguard Worker
450*61c4878aSAndroid Build Coastguard Worker    for i, frame in enumerate(frames, 1):
451*61c4878aSAndroid Build Coastguard Worker        if frame.ok() or frame.status is FrameStatus.BAD_ADDRESS:
452*61c4878aSAndroid Build Coastguard Worker            frame_bytes = ''.join(
453*61c4878aSAndroid Build Coastguard Worker                rf'\x{byte:02x}' for byte in frame.raw_decoded
454*61c4878aSAndroid Build Coastguard Worker            )
455*61c4878aSAndroid Build Coastguard Worker            yield (
456*61c4878aSAndroid Build Coastguard Worker                f'  static constexpr auto kDecodedFrame{i:02} = '
457*61c4878aSAndroid Build Coastguard Worker                f'bytes::String("{frame_bytes}");'
458*61c4878aSAndroid Build Coastguard Worker            )
459*61c4878aSAndroid Build Coastguard Worker        else:
460*61c4878aSAndroid Build Coastguard Worker            yield f'  // Frame {i}: {frame.status.value}'
461*61c4878aSAndroid Build Coastguard Worker
462*61c4878aSAndroid Build Coastguard Worker    yield ''
463*61c4878aSAndroid Build Coastguard Worker
464*61c4878aSAndroid Build Coastguard Worker    expected = '\n'.join(_expected(frames)) or '      // No frames'
465*61c4878aSAndroid Build Coastguard Worker    decoder_size = max(len(data), 8)  # Make sure large enough for a frame
466*61c4878aSAndroid Build Coastguard Worker
467*61c4878aSAndroid Build Coastguard Worker    yield f"""\
468*61c4878aSAndroid Build Coastguard Worker  DecoderBuffer<{decoder_size}> decoder;
469*61c4878aSAndroid Build Coastguard Worker
470*61c4878aSAndroid Build Coastguard Worker  static std::array<std::variant<Frame, Status>, {len(frames)}> kExpected = {{
471*61c4878aSAndroid Build Coastguard Worker{expected}
472*61c4878aSAndroid Build Coastguard Worker  }};
473*61c4878aSAndroid Build Coastguard Worker
474*61c4878aSAndroid Build Coastguard Worker  size_t decoded_frames = 0;
475*61c4878aSAndroid Build Coastguard Worker
476*61c4878aSAndroid Build Coastguard Worker  decoder.Process(kData, [&](const Result<Frame>& result) {{
477*61c4878aSAndroid Build Coastguard Worker    ASSERT_LT(decoded_frames++, kExpected.size());
478*61c4878aSAndroid Build Coastguard Worker    auto& expected = kExpected[decoded_frames - 1];
479*61c4878aSAndroid Build Coastguard Worker
480*61c4878aSAndroid Build Coastguard Worker    if (std::holds_alternative<Status>(expected)) {{
481*61c4878aSAndroid Build Coastguard Worker      EXPECT_EQ(Status::DataLoss(), result.status());
482*61c4878aSAndroid Build Coastguard Worker    }} else {{
483*61c4878aSAndroid Build Coastguard Worker      ASSERT_EQ(OkStatus(), result.status());
484*61c4878aSAndroid Build Coastguard Worker
485*61c4878aSAndroid Build Coastguard Worker      const Frame& decoded_frame = result.value();
486*61c4878aSAndroid Build Coastguard Worker      const Frame& expected_frame = std::get<Frame>(expected);
487*61c4878aSAndroid Build Coastguard Worker      EXPECT_EQ(expected_frame.address(), decoded_frame.address());
488*61c4878aSAndroid Build Coastguard Worker      EXPECT_EQ(expected_frame.control(), decoded_frame.control());
489*61c4878aSAndroid Build Coastguard Worker      ASSERT_EQ(expected_frame.data().size(), decoded_frame.data().size());
490*61c4878aSAndroid Build Coastguard Worker      EXPECT_EQ(std::memcmp(expected_frame.data().data(),
491*61c4878aSAndroid Build Coastguard Worker                            decoded_frame.data().data(),
492*61c4878aSAndroid Build Coastguard Worker                            expected_frame.data().size()),
493*61c4878aSAndroid Build Coastguard Worker                0);
494*61c4878aSAndroid Build Coastguard Worker    }}
495*61c4878aSAndroid Build Coastguard Worker  }});
496*61c4878aSAndroid Build Coastguard Worker
497*61c4878aSAndroid Build Coastguard Worker  EXPECT_EQ(decoded_frames, kExpected.size());
498*61c4878aSAndroid Build Coastguard Worker}}"""
499*61c4878aSAndroid Build Coastguard Worker
500*61c4878aSAndroid Build Coastguard Worker
501*61c4878aSAndroid Build Coastguard Workerdef _define_py_decoder_test(ctx: Context) -> PyTest:
502*61c4878aSAndroid Build Coastguard Worker    data, expected_frames, _ = ctx.test_case
503*61c4878aSAndroid Build Coastguard Worker
504*61c4878aSAndroid Build Coastguard Worker    def test(self) -> None:
505*61c4878aSAndroid Build Coastguard Worker        self.maxDiff = None
506*61c4878aSAndroid Build Coastguard Worker        # Decode in one call
507*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
508*61c4878aSAndroid Build Coastguard Worker            expected_frames,
509*61c4878aSAndroid Build Coastguard Worker            list(FrameDecoder().process(data)),
510*61c4878aSAndroid Build Coastguard Worker            msg=f'{ctx.group}: {data!r}',
511*61c4878aSAndroid Build Coastguard Worker        )
512*61c4878aSAndroid Build Coastguard Worker        # Decode byte-by-byte
513*61c4878aSAndroid Build Coastguard Worker        decoder = FrameDecoder()
514*61c4878aSAndroid Build Coastguard Worker        decoded_frames: list[Frame] = []
515*61c4878aSAndroid Build Coastguard Worker        for i in range(len(data)):
516*61c4878aSAndroid Build Coastguard Worker            decoded_frames += decoder.process(data[i : i + 1])
517*61c4878aSAndroid Build Coastguard Worker
518*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
519*61c4878aSAndroid Build Coastguard Worker            expected_frames,
520*61c4878aSAndroid Build Coastguard Worker            decoded_frames,
521*61c4878aSAndroid Build Coastguard Worker            msg=f'{ctx.group} (byte-by-byte): {data!r}',
522*61c4878aSAndroid Build Coastguard Worker        )
523*61c4878aSAndroid Build Coastguard Worker
524*61c4878aSAndroid Build Coastguard Worker    return test
525*61c4878aSAndroid Build Coastguard Worker
526*61c4878aSAndroid Build Coastguard Worker
527*61c4878aSAndroid Build Coastguard Workerdef _define_raw_decoder_py_test(ctx: Context) -> PyTest:
528*61c4878aSAndroid Build Coastguard Worker    raw_data, expected_frames, expected_non_frame_data = ctx.test_case
529*61c4878aSAndroid Build Coastguard Worker
530*61c4878aSAndroid Build Coastguard Worker    # The non-frame data decoder only yields valid frames.
531*61c4878aSAndroid Build Coastguard Worker    expected_frames = [f for f in expected_frames if f.status is FrameStatus.OK]
532*61c4878aSAndroid Build Coastguard Worker
533*61c4878aSAndroid Build Coastguard Worker    def test(self) -> None:
534*61c4878aSAndroid Build Coastguard Worker        self.maxDiff = None
535*61c4878aSAndroid Build Coastguard Worker
536*61c4878aSAndroid Build Coastguard Worker        non_frame_data = bytearray()
537*61c4878aSAndroid Build Coastguard Worker
538*61c4878aSAndroid Build Coastguard Worker        # Decode in one call
539*61c4878aSAndroid Build Coastguard Worker        decoder = FrameAndNonFrameDecoder(
540*61c4878aSAndroid Build Coastguard Worker            non_frame_data_handler=non_frame_data.extend
541*61c4878aSAndroid Build Coastguard Worker        )
542*61c4878aSAndroid Build Coastguard Worker
543*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
544*61c4878aSAndroid Build Coastguard Worker            expected_frames,
545*61c4878aSAndroid Build Coastguard Worker            list(decoder.process(raw_data)),
546*61c4878aSAndroid Build Coastguard Worker            msg=f'{ctx.group}: {raw_data!r}',
547*61c4878aSAndroid Build Coastguard Worker        )
548*61c4878aSAndroid Build Coastguard Worker
549*61c4878aSAndroid Build Coastguard Worker        decoder.flush_non_frame_data()
550*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(expected_non_frame_data, bytes(non_frame_data))
551*61c4878aSAndroid Build Coastguard Worker
552*61c4878aSAndroid Build Coastguard Worker        # Decode byte-by-byte
553*61c4878aSAndroid Build Coastguard Worker        non_frame_data.clear()
554*61c4878aSAndroid Build Coastguard Worker        decoder = FrameAndNonFrameDecoder(
555*61c4878aSAndroid Build Coastguard Worker            non_frame_data_handler=non_frame_data.extend
556*61c4878aSAndroid Build Coastguard Worker        )
557*61c4878aSAndroid Build Coastguard Worker        decoded_frames: list[Frame] = []
558*61c4878aSAndroid Build Coastguard Worker        for i in range(len(raw_data)):
559*61c4878aSAndroid Build Coastguard Worker            decoded_frames += decoder.process(raw_data[i : i + 1])
560*61c4878aSAndroid Build Coastguard Worker
561*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
562*61c4878aSAndroid Build Coastguard Worker            expected_frames,
563*61c4878aSAndroid Build Coastguard Worker            decoded_frames,
564*61c4878aSAndroid Build Coastguard Worker            msg=f'{ctx.group} (byte-by-byte): {raw_data!r}',
565*61c4878aSAndroid Build Coastguard Worker        )
566*61c4878aSAndroid Build Coastguard Worker        decoder.flush_non_frame_data()
567*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(expected_non_frame_data, bytes(non_frame_data))
568*61c4878aSAndroid Build Coastguard Worker
569*61c4878aSAndroid Build Coastguard Worker    return test
570*61c4878aSAndroid Build Coastguard Worker
571*61c4878aSAndroid Build Coastguard Worker
572*61c4878aSAndroid Build Coastguard Workerdef _ts_byte_array(data: bytes) -> str:
573*61c4878aSAndroid Build Coastguard Worker    return '[' + ', '.join(rf'0x{byte:02x}' for byte in data) + ']'
574*61c4878aSAndroid Build Coastguard Worker
575*61c4878aSAndroid Build Coastguard Worker
576*61c4878aSAndroid Build Coastguard Workerdef _ts_test(ctx: Context) -> Iterator[str]:
577*61c4878aSAndroid Build Coastguard Worker    """Generates a TS test for the provided test data."""
578*61c4878aSAndroid Build Coastguard Worker    data, _, _ = ctx.test_case
579*61c4878aSAndroid Build Coastguard Worker    frames = [
580*61c4878aSAndroid Build Coastguard Worker        f for f in list(FrameDecoder().process(data)) if not _py_only_frame(f)
581*61c4878aSAndroid Build Coastguard Worker    ]
582*61c4878aSAndroid Build Coastguard Worker    data_bytes = _ts_byte_array(data)
583*61c4878aSAndroid Build Coastguard Worker
584*61c4878aSAndroid Build Coastguard Worker    yield f'  it(\'{ctx.ts_name()}\', () => {{'
585*61c4878aSAndroid Build Coastguard Worker    yield f'    const data = new Uint8Array({data_bytes});'
586*61c4878aSAndroid Build Coastguard Worker
587*61c4878aSAndroid Build Coastguard Worker    yield '    const expectedFrames = ['
588*61c4878aSAndroid Build Coastguard Worker    for frame in frames:
589*61c4878aSAndroid Build Coastguard Worker        control_bytes = _ts_byte_array(frame.control)
590*61c4878aSAndroid Build Coastguard Worker        frame_bytes = _ts_byte_array(frame.data)
591*61c4878aSAndroid Build Coastguard Worker
592*61c4878aSAndroid Build Coastguard Worker        if frame is Expected:
593*61c4878aSAndroid Build Coastguard Worker            yield (
594*61c4878aSAndroid Build Coastguard Worker                f'      new Expected({frame.address}, '
595*61c4878aSAndroid Build Coastguard Worker                f'new Uint8Array({control_bytes}), '
596*61c4878aSAndroid Build Coastguard Worker                f'new Uint8Array({frame_bytes}), {frame.status}),'
597*61c4878aSAndroid Build Coastguard Worker            )
598*61c4878aSAndroid Build Coastguard Worker        else:
599*61c4878aSAndroid Build Coastguard Worker            raw = _ts_byte_array(frame.raw_encoded)
600*61c4878aSAndroid Build Coastguard Worker            yield (
601*61c4878aSAndroid Build Coastguard Worker                f'      new ExpectedRaw(new Uint8Array({raw}), {frame.status}),'
602*61c4878aSAndroid Build Coastguard Worker            )
603*61c4878aSAndroid Build Coastguard Worker
604*61c4878aSAndroid Build Coastguard Worker    yield '    ].values();\n'
605*61c4878aSAndroid Build Coastguard Worker
606*61c4878aSAndroid Build Coastguard Worker    yield """\
607*61c4878aSAndroid Build Coastguard Worker    const result = decoder.process(data);
608*61c4878aSAndroid Build Coastguard Worker
609*61c4878aSAndroid Build Coastguard Worker    while (true) {
610*61c4878aSAndroid Build Coastguard Worker      const expectedFrame = expectedFrames.next();
611*61c4878aSAndroid Build Coastguard Worker      const actualFrame = result.next();
612*61c4878aSAndroid Build Coastguard Worker      if (expectedFrame.done && actualFrame.done) {
613*61c4878aSAndroid Build Coastguard Worker        break;
614*61c4878aSAndroid Build Coastguard Worker      }
615*61c4878aSAndroid Build Coastguard Worker      expect(expectedFrame.done).toBeFalse();
616*61c4878aSAndroid Build Coastguard Worker      expect(actualFrame.done).toBeFalse();
617*61c4878aSAndroid Build Coastguard Worker
618*61c4878aSAndroid Build Coastguard Worker      const expected = expectedFrame.value;
619*61c4878aSAndroid Build Coastguard Worker      const actual = actualFrame.value;
620*61c4878aSAndroid Build Coastguard Worker      if (expected instanceof Expected) {
621*61c4878aSAndroid Build Coastguard Worker        expect(actual.address).toEqual(expected.address);
622*61c4878aSAndroid Build Coastguard Worker        expect(actual.control).toEqual(expected.control);
623*61c4878aSAndroid Build Coastguard Worker        expect(actual.data).toEqual(expected.data);
624*61c4878aSAndroid Build Coastguard Worker        expect(actual.status).toEqual(expected.status);
625*61c4878aSAndroid Build Coastguard Worker      } else {
626*61c4878aSAndroid Build Coastguard Worker        // Expected Raw
627*61c4878aSAndroid Build Coastguard Worker        expect(actual.rawEncoded).toEqual(expected.raw);
628*61c4878aSAndroid Build Coastguard Worker        expect(actual.status).toEqual(expected.status);
629*61c4878aSAndroid Build Coastguard Worker      }
630*61c4878aSAndroid Build Coastguard Worker    }
631*61c4878aSAndroid Build Coastguard Worker  });
632*61c4878aSAndroid Build Coastguard Worker"""
633*61c4878aSAndroid Build Coastguard Worker
634*61c4878aSAndroid Build Coastguard Worker
635*61c4878aSAndroid Build Coastguard Worker# Class that tests all cases in TEST_CASES.
636*61c4878aSAndroid Build Coastguard WorkerDecoderTest = _TESTS.python_tests('DecoderTest', _define_py_decoder_test)
637*61c4878aSAndroid Build Coastguard WorkerNonFrameDecoderTest = _TESTS.python_tests(
638*61c4878aSAndroid Build Coastguard Worker    'NonFrameDecoderTest', _define_raw_decoder_py_test
639*61c4878aSAndroid Build Coastguard Worker)
640*61c4878aSAndroid Build Coastguard Worker
641*61c4878aSAndroid Build Coastguard Worker
642*61c4878aSAndroid Build Coastguard Workerclass AdditionalNonFrameDecoderTests(unittest.TestCase):
643*61c4878aSAndroid Build Coastguard Worker    """Additional tests for the non-frame decoder."""
644*61c4878aSAndroid Build Coastguard Worker
645*61c4878aSAndroid Build Coastguard Worker    def test_shared_flags_waits_for_tilde_to_emit_data(self) -> None:
646*61c4878aSAndroid Build Coastguard Worker        non_frame_data = bytearray()
647*61c4878aSAndroid Build Coastguard Worker        decoder = FrameAndNonFrameDecoder(non_frame_data.extend)
648*61c4878aSAndroid Build Coastguard Worker
649*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
650*61c4878aSAndroid Build Coastguard Worker            [Expected(0, b'\0', b'')], list(decoder.process(_encode(0, 0, b'')))
651*61c4878aSAndroid Build Coastguard Worker        )
652*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(non_frame_data, b'')
653*61c4878aSAndroid Build Coastguard Worker
654*61c4878aSAndroid Build Coastguard Worker        self.assertEqual([], list(decoder.process(b'uh oh, no tilde!')))
655*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(non_frame_data, b'')
656*61c4878aSAndroid Build Coastguard Worker
657*61c4878aSAndroid Build Coastguard Worker        self.assertEqual([], list(decoder.process(b'~')))
658*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(non_frame_data, b'uh oh, no tilde!')
659*61c4878aSAndroid Build Coastguard Worker
660*61c4878aSAndroid Build Coastguard Worker    def test_no_shared_flags_immediately_emits_data(self) -> None:
661*61c4878aSAndroid Build Coastguard Worker        non_frame_data = bytearray()
662*61c4878aSAndroid Build Coastguard Worker        decoder = FrameAndNonFrameDecoder(
663*61c4878aSAndroid Build Coastguard Worker            non_frame_data.extend, handle_shared_flags=False
664*61c4878aSAndroid Build Coastguard Worker        )
665*61c4878aSAndroid Build Coastguard Worker
666*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
667*61c4878aSAndroid Build Coastguard Worker            [Expected(0, b'\0', b'')], list(decoder.process(_encode(0, 0, b'')))
668*61c4878aSAndroid Build Coastguard Worker        )
669*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(non_frame_data, b'')
670*61c4878aSAndroid Build Coastguard Worker
671*61c4878aSAndroid Build Coastguard Worker        self.assertEqual([], list(decoder.process(b'uh oh, no tilde!')))
672*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(non_frame_data, b'uh oh, no tilde!')
673*61c4878aSAndroid Build Coastguard Worker
674*61c4878aSAndroid Build Coastguard Worker    def test_emits_data_if_mtu_is_exceeded(self) -> None:
675*61c4878aSAndroid Build Coastguard Worker        frame_start = b'~this looks like a real frame'
676*61c4878aSAndroid Build Coastguard Worker
677*61c4878aSAndroid Build Coastguard Worker        non_frame_data = bytearray()
678*61c4878aSAndroid Build Coastguard Worker        decoder = FrameAndNonFrameDecoder(
679*61c4878aSAndroid Build Coastguard Worker            non_frame_data.extend, mtu=len(frame_start)
680*61c4878aSAndroid Build Coastguard Worker        )
681*61c4878aSAndroid Build Coastguard Worker
682*61c4878aSAndroid Build Coastguard Worker        self.assertEqual([], list(decoder.process(frame_start)))
683*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(non_frame_data, b'')
684*61c4878aSAndroid Build Coastguard Worker
685*61c4878aSAndroid Build Coastguard Worker        self.assertEqual([], list(decoder.process(b'!')))
686*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(non_frame_data, frame_start + b'!')
687*61c4878aSAndroid Build Coastguard Worker
688*61c4878aSAndroid Build Coastguard Worker    def test_emits_data_if_timeout_expires(self) -> None:
689*61c4878aSAndroid Build Coastguard Worker        frame_start = b'~this looks like a real frame'
690*61c4878aSAndroid Build Coastguard Worker
691*61c4878aSAndroid Build Coastguard Worker        non_frame_data: queue.Queue[bytes] = queue.Queue()
692*61c4878aSAndroid Build Coastguard Worker        decoder = FrameAndNonFrameDecoder(non_frame_data.put, timeout_s=0.001)
693*61c4878aSAndroid Build Coastguard Worker
694*61c4878aSAndroid Build Coastguard Worker        self.assertEqual([], list(decoder.process(frame_start)))
695*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(non_frame_data.get(timeout=2), frame_start)
696*61c4878aSAndroid Build Coastguard Worker
697*61c4878aSAndroid Build Coastguard Worker    def test_emits_raw_data_and_valid_frame_if_flushed_partway(self) -> None:
698*61c4878aSAndroid Build Coastguard Worker        payload = b'Do you wanna ride in my blimp?'
699*61c4878aSAndroid Build Coastguard Worker        frame = _encode(1, 2, payload)
700*61c4878aSAndroid Build Coastguard Worker
701*61c4878aSAndroid Build Coastguard Worker        non_frame_data = bytearray()
702*61c4878aSAndroid Build Coastguard Worker        decoder = FrameAndNonFrameDecoder(non_frame_data.extend)
703*61c4878aSAndroid Build Coastguard Worker
704*61c4878aSAndroid Build Coastguard Worker        self.assertEqual([], list(decoder.process(frame[:5])))
705*61c4878aSAndroid Build Coastguard Worker        decoder.flush_non_frame_data()
706*61c4878aSAndroid Build Coastguard Worker
707*61c4878aSAndroid Build Coastguard Worker        self.assertEqual(
708*61c4878aSAndroid Build Coastguard Worker            [Expected(1, b'\2', payload)], list(decoder.process(frame[5:]))
709*61c4878aSAndroid Build Coastguard Worker        )
710*61c4878aSAndroid Build Coastguard Worker
711*61c4878aSAndroid Build Coastguard Worker
712*61c4878aSAndroid Build Coastguard Workerif __name__ == '__main__':
713*61c4878aSAndroid Build Coastguard Worker    args = parse_test_generation_args()
714*61c4878aSAndroid Build Coastguard Worker    if args.generate_cc_test:
715*61c4878aSAndroid Build Coastguard Worker        _TESTS.cc_tests(
716*61c4878aSAndroid Build Coastguard Worker            args.generate_cc_test, _cpp_test, _CPP_HEADER, _CPP_FOOTER
717*61c4878aSAndroid Build Coastguard Worker        )
718*61c4878aSAndroid Build Coastguard Worker    elif args.generate_ts_test:
719*61c4878aSAndroid Build Coastguard Worker        _TESTS.ts_tests(args.generate_ts_test, _ts_test, _TS_HEADER, _TS_FOOTER)
720*61c4878aSAndroid Build Coastguard Worker    else:
721*61c4878aSAndroid Build Coastguard Worker        unittest.main()
722