Skip to main content

StreamDecoder

Struct StreamDecoder 

Source
pub struct StreamDecoder<'a> { /* private fields */ }
Expand description

Incremental frame decoder over a buffer the caller lends it.

Feed it received octets with push or push_slice. Each call buffers the input and reports at most one DecodeEvent, so:

  • A DecodeEvent::Frame borrows the storage. The frame stays buffered until the next push, which commits it (drops it from the buffer) before it reads new input. An empty push commits it too.
  • After any event other than DecodeEvent::NeedMore, more may already be buffered, such as a second frame from the same slice or more noise. Call push_slice(&[]) until it returns NeedMore to drain it.
  • A slice longer than the free storage is refused whole with DecodeEvent::Error carrying FrameError::BufferExhausted (the slice’s length and the free room), and none of it is taken. Drain, then push it again or in smaller pieces. Octets that finish a frame being skipped by count need no room, and don’t count toward the slice’s length.

An extended frame is decoded in place, so the frame’s data is its decoded NPDU, and the storage it was received in holds that from the octet after its header.

§Storage and skipping

Size the storage for the largest frame you accept, plus room for the slices you push: MAX_STANDARD_FRAME_LENGTH for every standard frame, or MAX_EXTENDED_FRAME_LENGTH for extended frames too (see the crate docs). A candidate that could never fit is reported once, as DecodeEvent::Malformed with FrameError::FrameTooLong, as soon as its header shows its size, and is then skipped by count, so the decoder never wedges. Storage too small for even a header reports a candidate at its first octet and drops that octet.

Once a header passes its CRC and the header checks of Clause 9.5.8, its Length can be trusted: a frame dropped after that point (too long for the storage, a reserved COBS type, or data that fails its CRC) is skipped whole, as the receive state machine skips one (Clause 9.5.4.4), and nothing inside it can come out as a frame. A header that fails its CRC or those checks says nothing reliable about the frame’s length, so only its first octet is dropped and the octets after it are scanned again. A decoder built with_reserved_cobs buffers a reserved COBS type and checks it like an extended frame instead.

§Destinations

The decoder does not know which station it serves, so it reports every candidate it drops, whatever the destination. A receive state machine (Clause 9.5.4.4) only counts an oversized, unsupported or bad-data-CRC frame as invalid when it is addressed to that station or broadcast, and skips one meant for another station without complaint. Such a caller should check the destination of a DecodeEvent::Malformed carrying FrameError::FrameTooLong, FrameError::UnsupportedCobsFrame or FrameError::DataCrcMismatch before treating it as an invalid frame. A bad header (a CRC failure, source 255, a Length out of range) counts wherever it was going. Since the header checks come before the type check, an unsupported COBS type always has a good header.

The decoder keeps no time. A receiver that aborts a partial frame after a silence (Clause 9.5.4) calls reset, which also ends a skip.

use bacnet_mstp_codec::{DecodeEvent, Frame, FrameType, StreamDecoder};

let mut wire = [0u8; 16];
Frame::new(FrameType::TOKEN, 1, 0, &[]).encode_into(&mut wire[..8])?;
Frame::new(FrameType::TOKEN, 2, 1, &[]).encode_into(&mut wire[8..])?;

let mut storage = [0u8; 64];
let mut decoder = StreamDecoder::new(&mut storage);
let mut destinations = Vec::new();
let mut event = decoder.push_slice(&wire);
loop {
    match event {
        DecodeEvent::NeedMore => break,
        DecodeEvent::Frame { frame, .. } => destinations.push(frame.header.destination),
        _ => {}
    }
    // Commit the frame, if any, and look for the next one.
    event = decoder.push_slice(&[]);
}
assert_eq!(destinations, [1, 2]);
assert_eq!(decoder.buffered_len(), 0);

Implementations§

Source§

impl<'a> StreamDecoder<'a>

Source

pub const fn new(storage: &'a mut [u8]) -> Self

Create an empty decoder over storage.

Source

pub const fn with_reserved_cobs(self) -> Self

Check the reserved COBS-encoded types, 34-127, as a receiver that supports COBS-encoded frames does, rather than skip them.

Such a receiver takes the encoded fields of every COBS type in, and a frame whose CRC-32K checks out is a valid frame of a type it doesn’t know (Clause 9.5.4.9), for its state machine to ignore or treat as unexpected. So a reserved type whose header passes is buffered like an extended frame: when it fits the storage and its encoded fields check out, it comes out as a DecodeEvent::Frame with its type, which no FrameType constant names, and its decoded data, and otherwise it is dropped and skipped by count, with FrameError::DataCrcMismatch or FrameError::FrameTooLong. FrameError::UnsupportedCobsFrame never comes out.

A decoder from Self::new skips them by count as soon as their header passes, as a receiver without COBS support does, and reports FrameError::UnsupportedCobsFrame.

Source

pub const fn checks_reserved_cobs(&self) -> bool

Whether the decoder checks reserved COBS types; see Self::with_reserved_cobs.

Source

pub const fn buffered_len(&self) -> usize

Number of buffered octets, including a frame awaiting the next push. Octets of a frame being skipped by count are never buffered.

Source

pub const fn capacity(&self) -> usize

Capacity of the caller-owned receive buffer.

Source

pub fn phase(&self) -> StreamPhase

Return the semantic receive phase, excluding retained resync noise.

Source

pub fn reset(&mut self)

Clear all buffered input, the pending frame and any skip.

Source

pub fn push(&mut self, byte: u8) -> DecodeEvent<'_>

Feed one octet; see push_slice.

Source

pub fn push_slice(&mut self, input: &[u8]) -> DecodeEvent<'_>

Commit a frame delivered by the previous push, take input (dropping what finishes a frame being skipped and buffering the rest), and report the first resulting event. The type docs describe the protocol.

Trait Implementations§

Source§

impl Debug for StreamDecoder<'_>

The decoder’s counts, phase and mode; the storage’s contents are left out.

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'a> !UnwindSafe for StreamDecoder<'a>

§

impl<'a> Freeze for StreamDecoder<'a>

§

impl<'a> RefUnwindSafe for StreamDecoder<'a>

§

impl<'a> Send for StreamDecoder<'a>

§

impl<'a> Sync for StreamDecoder<'a>

§

impl<'a> Unpin for StreamDecoder<'a>

§

impl<'a> UnsafeUnpin for StreamDecoder<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.