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::Frameborrows 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. Callpush_slice(&[])until it returnsNeedMoreto drain it. - A slice longer than the free storage is refused whole with
DecodeEvent::ErrorcarryingFrameError::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>
impl<'a> StreamDecoder<'a>
Sourcepub const fn with_reserved_cobs(self) -> Self
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.
Sourcepub const fn checks_reserved_cobs(&self) -> bool
pub const fn checks_reserved_cobs(&self) -> bool
Whether the decoder checks reserved COBS types; see
Self::with_reserved_cobs.
Sourcepub const fn buffered_len(&self) -> usize
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.
Sourcepub fn phase(&self) -> StreamPhase
pub fn phase(&self) -> StreamPhase
Return the semantic receive phase, excluding retained resync noise.
Sourcepub fn push(&mut self, byte: u8) -> DecodeEvent<'_>
pub fn push(&mut self, byte: u8) -> DecodeEvent<'_>
Feed one octet; see push_slice.
Sourcepub fn push_slice(&mut self, input: &[u8]) -> DecodeEvent<'_>
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.