"""Protocol definitions and packet structures for MiniProfiler. This module defines the binary protocol used for communication between the embedded device and the host application. """ import struct from enum import IntEnum from dataclasses import dataclass from typing import List, Optional import crc class Command(IntEnum): """Commands sent from host to embedded device.""" START_PROFILING = 0x01 STOP_PROFILING = 0x02 GET_STATUS = 0x03 RESET_BUFFERS = 0x04 GET_METADATA = 0x05 SET_CONFIG = 0x06 class ResponseType(IntEnum): """Response types from embedded device to host.""" ACK = 0x01 NACK = 0x02 METADATA = 0x03 STATUS = 0x04 PROFILE_DATA = 0x05 # Protocol constants COMMAND_HEADER = 0x55 RESPONSE_HEADER = 0xAA55 RESPONSE_END = 0x0A PROTOCOL_VERSION = 0x01 @dataclass class ProfileRecord: """A single profiling record from the embedded device. Attributes: func_addr: Function address (from instrumentation) entry_time: Entry timestamp in microseconds duration_us: Function duration in microseconds depth: Call stack depth """ func_addr: int entry_time: int duration_us: int depth: int @classmethod def from_bytes(cls, data: bytes) -> 'ProfileRecord': """Parse a ProfileRecord from binary data. Format: bytes: """Serialize ProfileRecord to binary format.""" return struct.pack(' 'Metadata': """Parse Metadata from binary data. Format: bytes: """Serialize Metadata to binary format.""" fw_version_bytes = self.fw_version.encode('utf-8')[:16].ljust(16, b'\x00') return struct.pack(' 'StatusInfo': """Parse StatusInfo from binary data. Format: 8: raise ValueError("Command payload cannot exceed 8 bytes") self.cmd = cmd self.payload = payload.ljust(8, b'\x00') def to_bytes(self) -> bytes: """Serialize command packet to binary format. Format: int: """Calculate CRC16-CCITT for data validation.""" calculator = crc.Calculator(crc.Crc16.CCITT) return calculator.checksum(data) def to_bytes(self) -> bytes: """Serialize response packet to binary format. Format: Optional['ResponsePacket']: """Parse response packet from binary data. Returns: ResponsePacket if valid, None otherwise """ if len(data) < 8: # Minimum packet size return None # Parse header header, response_type, payload_len = struct.unpack(' object: """Parse the payload based on response type. Returns: Parsed payload object (Metadata, StatusInfo, List[ProfileRecord], etc.) """ if self.response_type == ResponseType.METADATA: return Metadata.from_bytes(self.payload) elif self.response_type == ResponseType.STATUS: return StatusInfo.from_bytes(self.payload) elif self.response_type == ResponseType.PROFILE_DATA: # First byte is protocol version if len(self.payload) < 3: return [] version = self.payload[0] if version != PROTOCOL_VERSION: raise ValueError(f"Unsupported protocol version: {version}") record_count = struct.unpack(' len(self.payload): break record = ProfileRecord.from_bytes(self.payload[offset:offset+14]) records.append(record) offset += 14 return records elif self.response_type == ResponseType.ACK: return True elif self.response_type == ResponseType.NACK: return False return self.payload