def __init__( self, specifier: pyuavcan.transport.InputSessionSpecifier, payload_metadata: pyuavcan.transport.PayloadMetadata, finalizer: typing.Callable[[], None], ): """ Do not call this directly, use the factory method instead. """ self._statistics_impl = SelectiveUDPInputSessionStatistics() source_node_id = specifier.remote_node_id assert source_node_id is not None, "Internal protocol violation" def on_reassembly_error(error: TransferReassembler.Error) -> None: self._statistics.errors += 1 try: self._statistics.reassembly_errors[error] += 1 except LookupError: self._statistics.reassembly_errors[error] = 1 self._reassembler = TransferReassembler( source_node_id=source_node_id, extent_bytes=payload_metadata.extent_bytes, on_error_callback=on_reassembly_error, ) super().__init__(specifier=specifier, payload_metadata=payload_metadata, finalizer=finalizer)
def _process_frame(self, timestamp: Timestamp, frame: SerialFrame) -> None: """ This is a part of the transport-internal API. It's a public method despite the name because Python's visibility handling capabilities are limited. I guess we could define a private abstract base to handle this but it feels like too much work. Why can't we have protected visibility in Python? """ assert frame.data_specifier == self._specifier.data_specifier, "Internal protocol violation" self._statistics.frames += 1 transfer: typing.Optional[pyuavcan.transport.TransferFrom] if frame.source_node_id is None: transfer = TransferReassembler.construct_anonymous_transfer( timestamp, frame) if transfer is None: self._statistics.errors += 1 _logger.debug("%s: Invalid anonymous frame: %s", self, frame) else: transfer = self._get_reassembler( frame.source_node_id).process_frame(timestamp, frame, self._transfer_id_timeout) if transfer is not None: self._statistics.transfers += 1 self._statistics.payload_bytes += sum( map(len, transfer.fragmented_payload)) _logger.debug("%s: Received transfer: %s; current stats: %s", self, transfer, self._statistics) try: self._queue.put_nowait(transfer) except asyncio.QueueFull: # pragma: no cover # TODO: make the queue capacity configurable self._statistics.drops += len(transfer.fragmented_payload)
def _get_reassembler(self, source_node_id: int) -> TransferReassembler: assert isinstance( source_node_id, int) and source_node_id >= 0, 'Internal protocol violation' try: return self._reassemblers[source_node_id] except LookupError: def on_reassembly_error(error: TransferReassembler.Error) -> None: self._statistics.errors += 1 d = self._statistics.reassembly_errors_per_source_node_id[ source_node_id] try: d[error] += 1 except LookupError: d[error] = 1 self._statistics.reassembly_errors_per_source_node_id.setdefault( source_node_id, {}) reasm = TransferReassembler( source_node_id=source_node_id, max_payload_size_bytes=self._payload_metadata.max_size_bytes, on_error_callback=on_reassembly_error) self._reassemblers[source_node_id] = reasm _logger.debug('%s: New %s (%d total)', self, reasm, len(self._reassemblers)) return reasm
def _get_reassembler(self, source_node_id: int) -> TransferReassembler: try: return self._reassemblers[source_node_id] except LookupError: def on_reassembly_error(error: TransferReassembler.Error) -> None: self._statistics.errors += 1 d = self._statistics.reassembly_errors_per_source_node_id[ source_node_id] try: d[error] += 1 except LookupError: d[error] = 1 self._statistics.reassembly_errors_per_source_node_id.setdefault( source_node_id, {}) reasm = TransferReassembler( source_node_id=source_node_id, extent_bytes=self._payload_metadata.extent_bytes, on_error_callback=on_reassembly_error, ) self._reassemblers[source_node_id] = reasm _logger.debug("%s: New %s (%d total)", self, reasm, len(self._reassemblers)) return reasm
def update(self, timestamp: Timestamp, frame: SerialFrame) -> typing.Optional[Trace]: reasm = self._reassembler tid_timeout = reasm.transfer_id_timeout if reasm is not None else 0.0 tr: typing.Union[TransferFrom, TransferReassembler.Error, None] if reasm is not None: tr = reasm.process_frame(timestamp, frame) else: tr = TransferReassembler.construct_anonymous_transfer( timestamp, frame) if isinstance(tr, TransferReassembler.Error): return SerialErrorTrace(timestamp=timestamp, error=tr) if isinstance(tr, TransferFrom): meta = AlienTransferMetadata(tr.priority, tr.transfer_id, self._specifier) return TransferTrace(timestamp, AlienTransfer(meta, tr.fragmented_payload), tid_timeout) assert tr is None return None