Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 52 additions & 16 deletions sv2/codec-sv2/src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use core::marker::PhantomData;
#[cfg(feature = "noise_sv2")]
use framing_sv2::framing::HandShakeFrame;
use framing_sv2::{
framing::{Frame, Sv2Frame},
framing::{Frame, SizeHint, Sv2Frame},
header::Header,
};
#[cfg(feature = "noise_sv2")]
Expand All @@ -43,9 +43,7 @@ use noise_sv2::NoiseEngine;
#[cfg(feature = "noise_sv2")]
use noise_sv2::NOISE_FRAME_HEADER_SIZE;

#[cfg(feature = "noise_sv2")]
use crate::error::Error;
use crate::error::Result;
use crate::error::{Error, Result};

use crate::Error::MissingBytes;
#[cfg(feature = "noise_sv2")]
Expand Down Expand Up @@ -357,13 +355,15 @@ pub struct WithoutNoise<B: IsBuffer, T: Serialize + binary_sv2::GetSize> {
}

impl<T: Serialize + binary_sv2::GetSize, B: IsBuffer> WithoutNoise<B, T> {
/// Attempts to decode the next frame, returning either a frame or an error indicating how many
/// bytes are missing.
///
/// Attempts to decode the next Sv2 frame.
/// Attempts to decode the next frame, returning either a frame or an error describing how the
/// buffered bytes differ from the frame size declared by the header.
///
/// On success, the decoded frame is returned. Otherwise, an error indicating the number of
/// missing bytes required to complete the frame is returned.
/// On success, the decoded frame is returned. Otherwise, the error is either
/// `Error::MissingBytes`, carrying the number of bytes still required to complete the frame,
/// or `Error::UnexpectedTrailingBytes`, carrying the number of bytes buffered beyond the
/// expected frame size. The latter discards the buffered data; since the surplus bytes may
/// have contained the start of the next frame, the caller should treat the stream as
/// desynchronized.
///
/// In the case of `Error::MissingBytes`, the user should resize the decoder buffer using
/// `writable`, read another chunk from the incoming message stream, and then call `next_frame`
Expand All @@ -373,19 +373,25 @@ impl<T: Serialize + binary_sv2::GetSize, B: IsBuffer> WithoutNoise<B, T> {
pub fn next_frame(&mut self) -> Result<Sv2Frame<T, B::Slice>> {
let len = self.buffer.len();
let src = self.buffer.get_data_by_ref(len);
let hint = Sv2Frame::<T, B::Slice>::size_hint(src) as usize;

match hint {
0 => {
match Sv2Frame::<T, B::Slice>::size_hint(src) {
SizeHint::Exact => {
self.missing_b = Header::SIZE;
let src = self.buffer.get_data_owned();
let frame = Sv2Frame::<T, B::Slice>::from_bytes_unchecked(src);
Ok(frame)
}
_ => {
self.missing_b = hint;
SizeHint::Missing(missing) => {
self.missing_b = missing;
Err(MissingBytes(self.missing_b))
}
SizeHint::Surplus(surplus) => {
// Reachable if the buffer was filled past the slice returned by `writable`; the
// frame boundary is lost, so drain everything and report the surplus.
self.missing_b = Header::SIZE;
let _ = self.buffer.get_data_owned();
Err(Error::UnexpectedTrailingBytes(surplus))
}
}
}

Expand Down Expand Up @@ -446,7 +452,7 @@ mod prop_tests {
use buffer_sv2::Buffer as IsBuffer;
#[cfg(feature = "noise_sv2")]
use framing_sv2::framing::Frame;
use framing_sv2::framing::Sv2Frame;
use framing_sv2::{framing::Sv2Frame, header::Header};
#[cfg(feature = "noise_sv2")]
use key_utils::{Secp256k1PublicKey, Secp256k1SecretKey};
#[cfg(feature = "noise_sv2")]
Expand Down Expand Up @@ -598,6 +604,36 @@ mod prop_tests {
TestResult::from_bool(missing_bytes_count > 0)
}

/// Verifies that over-filling the buffer (calling `writable` twice before `next_frame`)
/// surfaces `UnexpectedTrailingBytes`, drains the buffer, and leaves the decoder usable.
#[test]
fn test_decoder_excess_bytes_drains_and_recovers() {
let msg = TestMessage { value: 42 };
let frame = Sv2Frame::<TestMessage, Slice>::from_message(msg.clone(), 0, 0, false).unwrap();
let mut encoder = Encoder::<TestMessage>::new();
let encoded = encoder.encode(frame).unwrap();
let encoded: &[u8] = encoded.as_ref();

let mut decoder = StandardDecoder::<TestMessage>::new();
decoder.writable().copy_from_slice(&encoded[..Header::SIZE]);
assert!(matches!(
decoder.next_frame(),
Err(crate::Error::MissingBytes(_))
));
decoder.writable().copy_from_slice(&encoded[Header::SIZE..]);
let surplus = decoder.writable().len();
match decoder.next_frame() {
Err(crate::Error::UnexpectedTrailingBytes(n)) => assert_eq!(n, surplus),
Ok(_) => panic!("expected UnexpectedTrailingBytes, got a frame"),
Err(e) => panic!("expected UnexpectedTrailingBytes, got {e:?}"),
}

let mut decoded =
decode_frame(&mut decoder, encoded, None).expect("decoder should recover");
let decoded_msg: TestMessage = binary_sv2::from_bytes(decoded.payload()).unwrap();
assert_eq!(decoded_msg, msg);
}

/// Verifies that a single decoder instance correctly decodes two consecutive independent
/// frames in sequence, confirming that internal state resets between frames.
#[quickcheck]
Expand Down
6 changes: 6 additions & 0 deletions sv2/codec-sv2/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub enum Error {

/// Unexpected state in the Noise protocol.
UnexpectedNoiseState,

/// The decoder buffer holds a complete frame plus the given number of surplus bytes.
UnexpectedTrailingBytes(usize),
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -94,6 +97,9 @@ impl fmt::Display for Error {
UnexpectedNoiseState => {
write!(f, "Noise state is incorrect")
}
UnexpectedTrailingBytes(u) => {
write!(f, "Buffer holds `{u}` bytes beyond the end of the frame")
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions sv2/codec-sv2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub use error::{Error, Result};

pub use decoder::{StandardEitherFrame, StandardSv2Frame};

pub use framing_sv2::framing::SizeHint;

pub use decoder::StandardDecoder;
#[cfg(feature = "noise_sv2")]
pub use decoder::StandardNoiseDecoder;
Expand Down
Loading
Loading