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