def _compute_crc(self) -> bytes: """Computes CRC for the payload. Raises: exceptions.ProtocolDataError: Could not compute crc: TypeError. exceptions.ProtocolDataError: Could not pack header fields: crc=0xXX. """ try: # raises TypeError for invalid input type. crc_key = int(CRC_FUNC(self.payload)) except TypeError as terr: raise exceptions.ProtocolDataError( 'Could not compute crc: {0}' .format(terr)) parsed_key = None try: parsed_key = self._HEADER_PARSER.pack(crc_key) except struct.error as exc: raise exceptions.ProtocolDataError( 'Could not pack header fields: crc=0x{:x}' .format(crc_key) ) from exc return parsed_key
def output(self) -> Optional[bytes]: """Produce the next COBS-decoded payload. Returns: An optional bytestring of the COBS-decoded payload from the next available COBS-encoded body in the internal buffer. Returns None if there are no bodies available in the internal buffer. Raises: exceptions.ProtocolDataError: the body is obviously incorrect for COBS decoding; note that it is possible that a body may have have accumulated byte errors which would silently cause an incorrect COBS decoding but would not be detected by the decoder and thus would not raise an exception. """ frame = self._buffer.output() if frame is None: return None try: decoded: bytes = cobs.decode(frame) except cobs.DecodeError as exc: raise exceptions.ProtocolDataError( 'Could not COBS-decode body: {!r}'.format(frame) ) from exc except TypeError as err: raise exceptions.ProtocolDataError( 'Could not COBS-encode body: required type bytes found type {}'. format(type(frame)) ) from err self._logger.debug(decoded) return decoded
def output(self) -> Optional[bytes]: """Produce the next COBS-encoded frame body. Returns: An optional bytestring of the COBS-encoded body from the next available payload in the internal buffer. Returns None if there are no payloads available in the internal buffer. Raises: exceptions.ProtocolDataError: the payload is too long for COBS encoding. """ payload = self._buffer.output() if payload is None: return None if len(payload) > FRAME_PAYLOAD_MAX_SIZE: raise exceptions.ProtocolDataError( 'Frame payload for COBS encoding is too long ({} bytes)!' .format(len(payload)) ) try: encoded: bytes = cobs.encode(payload) except TypeError as err: raise exceptions.ProtocolDataError( 'Frames payload for COBS encoding should be bytes not {}' .format(type(payload)) ) from err self._logger.debug(encoded) return encoded
def output(self) -> Optional[UpperEvent]: """Emit the next output event.""" event = self._buffer.output() if not event: return None # Add data integrity to the state crc_message = None self._crc_receiver.input(event.data) try: crc_message = self._crc_receiver.output() except exceptions.ProtocolDataError as err: raise exceptions.ProtocolDataError('CRCSender({}):{}'.format( event.state_type, str(err))) if not crc_message: return None message = None self._message_receiver.input(crc_message) try: message = self._message_receiver.output() except exceptions.ProtocolDataError: self._logger.exception('MessageSender:') # check whether file name and data type are same if event.state_type != type(message).__name__: raise exceptions.ProtocolDataError( f"The state type {type(message).__name__} data in the " f"file does not match the filename {event.state_type}.") return message
def output(self) -> Optional[bytes]: """Produce the next datagram body. Returns: An optional bytestring of the datagram body from the next available datagram payload in the internal buffer. Returns None if there are no payloads available in the internal buffer. Raises: exceptions.ProtocolDataError: The payload is too long to fit in a datagram; specifically, it is longer than can be represented by the length field of the datagram header. """ payload = self._buffer.output() if payload is None: return None if len(payload) > 255: raise exceptions.ProtocolDataError( 'Datagram payload may not be {} bytes long!' .format(len(payload)) ) datagram = Datagram(seq=self._seq, payload=payload) datagram.update_from_payload() self._logger.debug(datagram) self._seq = (self._seq + 1) % SEQ_NUM_SPACE return datagram.compute_body()
def output(self) -> Optional[ListSegment[_ListElement]]: """Emit the next output event.""" event = self._buffer.output() if event is None: return None if event.next_expected is None: return None if event.next_expected < self._next_expected: raise exceptions.ProtocolDataError( 'Next expected event id cannot decrease from {} to {}!'.format( self._next_expected, event.next_expected)) self._next_expected = event.next_expected self._advance_next_expected_index() output_event = self.segment_type() output_event.next_expected = self._next_expected output_event.total = len(self._elements) output_event.elements = list( itertools.islice(self._elements, self._next_expected_index, self._next_expected_index + self.max_segment_len)) output_event.remaining = len( self._elements) - self._next_expected_index self._logger.debug('Sending: %s', output_event) return output_event
def input(self, event: Optional[LowerEvent]) -> None: """Handle input events.""" if not event: return if not event.has_data(): raise exceptions.ProtocolDataError("Empty file: {0}".format( event.state_type)) self._buffer.input(event)
def update_from_payload( self, message_types: Mapping[Type[betterproto.Message], int]) -> None: """Update header fields from the payload.""" message_type = type(self.payload) try: self.type = message_types[message_type] except KeyError as exc: raise exceptions.ProtocolDataError( 'Message type does not have a code: {}'.format( message_type)) from exc
def parse( self, buffer: bytes, message_classes: Mapping[int, Type[betterproto.Message]]) -> None: """Parse message contents from a buffer. Args: buffer: The message body bytestring from which header values and the payload are to be parsed and stored in the message's own attributes. message_classes: The mapping to look up Protocol Buffer message classes from the message type code. Raises: exceptions.ProtocolDataError: The header cannot be parsed. """ body_header = buffer[:self.HEADER_SIZE] try: results = self._HEADER_PARSER.unpack(body_header) except struct.error as exc: raise exceptions.ProtocolDataError( 'Unparseable header: {!r}'.format(body_header)) from exc (self.type, ) = results try: message_object = message_classes[self.type]() except KeyError as exc: raise exceptions.ProtocolDataError( 'Unknown message type code: {}'.format(self.type)) from exc body_payload = buffer[self.HEADER_SIZE:] try: self.payload = message_object.parse(body_payload) except Exception as exc: # Wrap and re-raise any betterproto error as a ProtocolDataError raise exceptions.ProtocolDataError( 'Unparseable payload: {!r}'.format(body_payload)) from exc
def check_integrity(self) -> None: """Matches computed CRC with that extracted from message. Raises: exceptions.ProtocolDataError: The specified CRC of the datagram\'s protected section, 0xXX, is inconsistent with the actual computed CRC of the received protected section, 0xXX. """ computed_crc = self._compute_crc() if self.crc != computed_crc: raise exceptions.ProtocolDataError( 'The specified CRC of the datagram\'s protected section, ' '{!r}, is inconsistent with the actual computed CRC ' 'of the received protected section, {!r}' .format(self.crc, computed_crc) )
def parse(self, body: bytes) -> None: """Extracts CRC and payload from incoming message. Args: body:data for which crc has to be computed Raises: exceptions.ProtocolDataError: The size of the packet received should be between 4 bytes and 256 bytes but found to be n bytes. """ if len(body) > 256 or len(body) < 4: raise exceptions.ProtocolDataError( 'The size of the packet received should be between 4 bytes ' 'and 256 bytes but found to be {0} bytes.'.format(len(body)) ) self.crc = body[:self.HEADER_SIZE] self.payload = body[self.HEADER_SIZE:]
def compute_body(self) -> bytes: """Return the body of the datagram, including the header and payload. Raises: exceptions.ProtocolDataError: the header fields of the datagram could not be packed together into the header section of the datagram body. """ try: header = self._HEADER_PARSER.pack(self.seq, self.length) except struct.error as exc: raise exceptions.ProtocolDataError( 'Could not pack header fields: seq={}, len={}' .format(self.seq, self.length) ) from exc return header + self.payload
def pack_protected(self) -> bytes: """Return the protected section of the datagram body. Raises: exceptions.ProtocolDataError: the protected header fields of the datagram could not be packed together. """ try: header_protected = self._HEADER_PARSER.pack( self.seq, self.length ) except struct.error as exc: raise exceptions.ProtocolDataError( 'Could not pack protected header fields: seq={}, len={}' .format(self.seq, self.length) ) from exc return header_protected + self.payload
def parse(self, buffer: bytes) -> None: """Parse datagram contents from a buffer. Args: buffer: The datagram body bytestring from which header field values and the payload are to be parsed and stored in the datagram's own attributes. Raises: exceptions.ProtocolDataError: The header cannot be parsed. """ try: results = self._HEADER_PARSER.unpack(buffer[:self.HEADER_SIZE]) except struct.error as exc: raise exceptions.ProtocolDataError( 'Unparseable header: {!r}'.format(buffer[:self.HEADER_SIZE]) ) from exc (self.seq, self.length) = results self.payload = buffer[self.HEADER_SIZE:]
def input(self, event: Optional[UpdateEvent]) -> None: """Handle input events.""" if event is None or not event.has_data(): return if event.time is not None: self._logger.debug('Time: %f', event.time) self.current_time = event.time if self.output_deadline is None: self.output_deadline = (self.current_time + self.output_schedule[0].time) if event.pb_message is None: return self._logger.debug('Received: %s', event.pb_message) message_type = type(event.pb_message) try: self.all_states[message_type] = event.pb_message except KeyError as exc: raise exceptions.ProtocolDataError( 'Received message type is not a synchronizable state: {}'. format(message_type)) from exc
def output(self) -> Optional[bytes]: """Produce the next datagram payload. Returns: An optional bytestring of the datagram payload from the next available datagram body in the internal buffer. Returns None if there are no bodies available in the internal buffer. Raises: exceptions.ProtocolDataError: the header fields of the datagram body are inconsistent with the payload, or the header cannot be parsed from the datagram body. """ body = self._buffer.output() if body is None: return None datagram = Datagram() datagram.parse(body) # may raise ProtocolDataError self._logger.debug(datagram) if datagram.length != len(datagram.payload): raise exceptions.ProtocolDataError( 'The specified length of the datagram payload, {}, is ' 'inconsistent with the actual received length, {}' .format(datagram.length, len(datagram.payload)) ) if self.expected_seq is None: self._logger.info('Initialized expected seq num from: %s', datagram) self.expected_seq = datagram.seq elif self.expected_seq != datagram.seq: self._logger.warning( 'Expected datagram with seq num %d, but received: %s', self.expected_seq, datagram ) self.expected_seq = datagram.seq self.expected_seq = (self.expected_seq + 1) % SEQ_NUM_SPACE return datagram.payload
def output(self) -> Optional[betterproto.Message]: """Emit the next output event.""" if self.output_deadline is None: return None if self.current_time < self.output_deadline: return None output_type = self.output_schedule[0].type try: output_event = self.all_states[output_type] except KeyError as exc: raise exceptions.ProtocolDataError( 'Scheduled message type is not a synchronizable state: {}'. format(output_type)) from exc self.output_schedule.rotate(-1) self.output_deadline = (self.current_time + self.output_schedule[0].time) if output_event is None: return None self._logger.debug('Sending: %s', output_event) return output_event