| # Copyright 2024 The Pigweed Authors | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
| # use this file except in compliance with the License. You may obtain a copy of | |
| # the License at | |
| # | |
| # https://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| # License for the specific language governing permissions and limitations under | |
| # the License. | |
| """Tests for gonk binary data""" | |
| import unittest | |
| from gonk_tools.binary_handler import ( | |
| BIN_LOG_SYNC_START_BYTES, | |
| BIN_LOG_SYNC_START_STR, | |
| BytearrayLoop, | |
| decode_one_varint, | |
| ) | |
| from gonk_tools.gonk_log_stream import GonkLogStream | |
| # pylint: disable=line-too-long | |
| _SAMPLE_ENCODED_LOGS = [ | |
| b'~$llsrqA==E\xc4\xe2\x93~', | |
| b'~$w4ZyKAI=0\x8f\xbf!~', | |
| b'~$w4ZyKAQ=i\xa4\xa3~', | |
| b'~$w4ZyKAY=a\x9d}]k~', | |
| b'~$w4ZyKAg=\x86\xb8*~', | |
| b'~$YNjZuQ==X\xb2\xc1@~', | |
| b'~$J+09dYBA\xb2\xf8^~', | |
| ] | |
| _SAMPLE_PROTO_PACKETS = [ | |
| bytes.fromhex( | |
| f'{BIN_LOG_SYNC_START_STR}122310d380ef020a04080010030a04080010020a040800104e0a040845104f0a0408001002' | |
| ), | |
| bytes.fromhex( | |
| f'{BIN_LOG_SYNC_START_STR}122110bf010a04080010030a04080010020a040800104e0a04080010020a040800104f' | |
| ), | |
| bytes.fromhex( | |
| f'{BIN_LOG_SYNC_START_STR}122a10b9010a04080010030a04080010020a04080010030a04080010020a0d080010b6ffffffffffffffff01' | |
| ), | |
| bytes.fromhex( | |
| f'{BIN_LOG_SYNC_START_STR}122a10d1010a04080010030a04080010020a04084710030a0d080010b7ffffffffffffffff010a0408001002' | |
| ), | |
| bytes.fromhex( | |
| f'{BIN_LOG_SYNC_START_STR}123310df010a04080010030a0d080010b6ffffffffffffffff010a04080010030a0d080010b7ffffffffffffffff010a0408001002' | |
| ), | |
| bytes.fromhex( | |
| f'{BIN_LOG_SYNC_START_STR}127210ffffffff0f0a04080010030a04080010020a04080010030a04080010020a04080010020a0d080010b7ffffffffffffffff010a0d080010b7ffffffffffffffff010a04084710020a04084710030a04080010030a04080010020a1608ffffffffffffffffff0110ffffffffffffffffff01' | |
| ), | |
| bytes.fromhex( | |
| f'{BIN_LOG_SYNC_START_STR}127b10ffffffff0f0a04080010030a04080010020a04080010030a04080010020a0d080010b6ffffffffffffffff010a0d080010b7ffffffffffffffff010a0d080010b7ffffffffffffffff010a04080010020a04080010030a04080010030a04080010020a1608ffffffffffffffffff0110ffffffffffffffffff01' | |
| ), | |
| bytes.fromhex( | |
| f'{BIN_LOG_SYNC_START_STR}127210ffffffff0f0a04080010030a04080010020a04080010030a04084510020a04080010020a04080010020a0d080010b7ffffffffffffffff010a04084610020a04080010030a04080010030a0d084810b6ffffffffffffffff010a1608ffffffffffffffffff0110ffffffffffffffffff01' | |
| ), | |
| ] | |
| _SAMPLE_LONG_PROTO_PACKETS = [ | |
| # Artificially increased size > 127 (uses two bytes for the packet size varint) | |
| bytes.fromhex( | |
| f'{BIN_LOG_SYNC_START_STR}12F20110ffffffff0f0a04080010030a04080010020a04080010030a04084510020a04080010020a04080010020a0d080010b7ffffffffffffffff010a04084610020a04080010030a04080010030a0d084810b6ffffffffffffffff010a1608ffffffffffffffffff0110ffffffffffffffffff0110ffffffff0f0a04080010030a04080010020a04080010030a04084510020a04080010020a04080010020a0d080010b7ffffffffffffffff010a04084610020a04080010030a04080010030a0d084810b6ffffffffffffffff010a1608ffffffffffffffffff0110ffffffffffffffffff01ffffffffffffffffff01ffffffff' | |
| ), | |
| ] | |
| # pylint: enable=line-too-long | |
| class TestHandleBinaryData(unittest.TestCase): | |
| """Tests for binary log data.""" | |
| def setUp(self): | |
| # Show large diffs | |
| self.maxDiff = None # pylint: disable=invalid-name | |
| def test_gonk_stream_write(self) -> None: | |
| """Test GonkLogStream.write handles protos and logs correctly.""" | |
| log_data = b''.join( | |
| [ | |
| _SAMPLE_ENCODED_LOGS[1], | |
| ] | |
| ) | |
| proto_data1 = _SAMPLE_LONG_PROTO_PACKETS[0] | |
| gl = GonkLogStream() | |
| gl.write(log_data) | |
| # No proto bytes yet. | |
| self.assertEqual(None, gl.remaining_proto_bytes) | |
| gl.write(proto_data1 + log_data) | |
| # One log data section should be in the log_buffer | |
| self.assertEqual(log_data, gl.log_buffer.getbuffer()) | |
| # One proto packet should be consumed. | |
| self.assertEqual(None, gl.remaining_proto_bytes) | |
| self.assertEqual(log_data, gl.input_buffer.getbuffer()) | |
| # Write a third log | |
| gl.write(log_data) | |
| # Log buffer should have 3 logs | |
| self.assertEqual(log_data * 3, gl.log_buffer.getbuffer()) | |
| # Input buffer now empty | |
| self.assertEqual(b'', gl.input_buffer.getbuffer()) | |
| def test_read_until(self) -> None: | |
| """Test BytearrayLoop.read_until.""" | |
| log_data = b''.join( | |
| [ | |
| _SAMPLE_ENCODED_LOGS[1], | |
| _SAMPLE_ENCODED_LOGS[2], | |
| ] | |
| ) | |
| proto_data = b''.join( | |
| [ | |
| _SAMPLE_PROTO_PACKETS[0], | |
| _SAMPLE_PROTO_PACKETS[1], | |
| ] | |
| ) | |
| with self.subTest(msg="Matching sub sequence exists; read until that."): | |
| merged_data = log_data + proto_data | |
| bl = BytearrayLoop(merged_data) | |
| self.assertEqual(log_data, bl.read_until(BIN_LOG_SYNC_START_BYTES)) | |
| self.assertEqual(proto_data, bl.getbuffer()) | |
| with self.subTest( | |
| msg="No matching sub sequence exists; read all data." | |
| ): | |
| bl = BytearrayLoop(log_data) | |
| self.assertEqual(log_data, bl.read_until(BIN_LOG_SYNC_START_BYTES)) | |
| # Remaining buffer should be empty | |
| self.assertEqual(b'', bl.getbuffer()) | |
| with self.subTest( | |
| msg="Matching sub sequence at the start, read nothing." | |
| ): | |
| bl = BytearrayLoop(proto_data) | |
| self.assertEqual(b'', bl.read_until(BIN_LOG_SYNC_START_BYTES)) | |
| # Remaining buffer should be full | |
| self.assertEqual(proto_data, bl.getbuffer()) | |
| with self.subTest(msg="Subsequence is longer than the buffer."): | |
| # Searching for a subsequece longer than the buffer should return an | |
| # empty result. | |
| too_short_buffer = BytearrayLoop(bytes.fromhex('abc123')) | |
| self.assertEqual( | |
| b'', too_short_buffer.read_until(bytes.fromhex('abc123456789')) | |
| ) | |
| def test_proto_packet_length(self) -> None: | |
| """Test decode_one_varint on remaining protobuf lengths.""" | |
| for packet in _SAMPLE_PROTO_PACKETS: | |
| bl = BytearrayLoop() | |
| bl.write(packet) | |
| self.assertEqual(packet, bl.getbuffer()) | |
| original_size = len(packet) | |
| packet_size = original_size | |
| self.assertEqual(BIN_LOG_SYNC_START_BYTES, bl.peek(5)) | |
| # Read magic bytes fixed32 tag + 4 bytes. | |
| magic_start = bl.read(5) | |
| self.assertEqual(BIN_LOG_SYNC_START_BYTES, magic_start) | |
| packet_size -= 5 | |
| # Read the tag. | |
| _tag = bl.read(1) | |
| packet_size -= 1 | |
| # Read the varint with the remaining packet size. | |
| original_size = len(bl.getbuffer()) | |
| decoded_remaining_size = decode_one_varint(bl) | |
| # Subtract bytes used by the varint | |
| packet_size -= original_size - len(bl.getbuffer()) | |
| self.assertEqual(decoded_remaining_size, packet_size) | |
| if __name__ == '__main__': | |
| unittest.main() |