| # 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. | |
| """Binary log data functions.""" | |
| import io | |
| import threading | |
| from typing import Union | |
| BIN_LOG_SYNC_START_STR = '0d99bbaa7d' | |
| BIN_LOG_SYNC_START_BYTES = bytes.fromhex(BIN_LOG_SYNC_START_STR) | |
| class BytearrayLoop: | |
| """A bytearray reader writer. | |
| Reads occur from the front of the buffer and remove data. Writes append to | |
| the end of the buffer. | |
| """ | |
| def __init__(self, data: bytes = b''): | |
| self.buffer = bytearray(data) | |
| self.lock = threading.Lock() | |
| def read(self, size: int = -1) -> bytearray: | |
| """Read bytes from the start of the buffer.""" | |
| data = b'' | |
| with self.lock: | |
| if size < 0: | |
| size = len(self.buffer) | |
| data = self.buffer[:size] | |
| # Delete the data we just fetched | |
| self.buffer[:size] = b'' | |
| return data | |
| def write(self, data: bytes) -> None: | |
| """Write bytes to the end of the buffer.""" | |
| with self.lock: | |
| self.buffer.extend(data) | |
| def peek(self, size: int = 0) -> bytearray: | |
| """Peek into bytes from the start.""" | |
| return self.buffer[:size] | |
| def __len__(self) -> int: | |
| return len(self.buffer) | |
| def getbuffer(self) -> bytearray: | |
| """Return the whole buffer.""" | |
| return self.buffer | |
| def read_until(self, sub_sequece: bytes) -> bytearray: | |
| """Pop all data from the buffer until a subsequence is found.""" | |
| # Return nothing if the buffer is smaller than the provided subsequence. | |
| if len(sub_sequece) > len(self.buffer): | |
| return bytearray() | |
| position = self.buffer.find(sub_sequece) | |
| # If no subsequece is found, read all data. | |
| if position < 0: | |
| return self.read() | |
| return self.read(position) | |
| _RawIo = Union[io.RawIOBase, BytearrayLoop] | |
| def decode_one_varint(stream: _RawIo) -> int: | |
| """Read a single varint from a byte stream.""" | |
| # Track the amount shifted. | |
| shift = 0 | |
| # Resulting varint. | |
| result = 0 | |
| while True: | |
| # Get one byte | |
| b = stream.read(1) | |
| if not b: | |
| break | |
| # Read as an integer. Note: byte order is little endian but we are only | |
| # reading one byte at a time. | |
| i = int.from_bytes(b, byteorder='little') | |
| # Shift first 7 bits from this byte into the result. | |
| result |= (i & 0x7F) << shift | |
| shift += 7 | |
| # Stop if the highest bit is not set. | |
| if not i & 0x80: | |
| break | |
| return result |