What changed, and why it matters
This commit adds a new optional transport-layer feature called 'ACK piggybacking' to the Trezor hardware wallet's Rust THP (Trezor Host Protocol) implementation. It lets a device or host include an acknowledgment (ACK) inside the next data packet instead of sending a separate tiny ACK packet, reducing USB/network traffic. The change is feature-gated by protocol version 2.1 and falls back to the older separate-ACK behavior if either side does not advertise support. The diff also includes related test and example-code cleanups, such as stricter error handling and a new API to set the peer's protocol version before the handshake begins.
Review as a normal feature commit. Verify that the version-gating correctly prevents v2.0 peers from interpreting piggybacked ACK bits, and that the new ACK checksum verification and retransmission paths do not introduce denial-of-service or deadlock scenarios. Run the expanded test matrix (especially `test_packet_loss_handshake_v21` and `test_open_piggybacking`) to confirm interoperability and recovery behavior.
Security signals we found
New transport-layer feature gated by negotiated protocol version (2.1)
ACK bit now included in outgoing sync bits when piggybacking is enabled
ACK packet checksum verification added in `handle_ack`
Lost ACK retransmission logic added in `handle_invalid_seq`
Receive buffer resize size is now wrapped in `Option<NonZeroU16>` to avoid zero-size edge cases
Host rejects incoming packets before handshake starts (`HandshakeState::Initial`)
Example host CLI now panics on unrecoverable channel errors instead of looping on bad checksums
Evidence from the diff
The patch implements ACK piggybacking for the THP channel layer. Key changes: (1) ChannelSync gains ack_piggybacking state; send_start() now optionally sets the ACK bit, and receive_acknowledge() uses the previous receive bit value. (2) Host and device handshake code enables piggybacking when the peer reports protocol version >= 2.1 (host via set_device_protocol_version; device via the ACK bit in the first handshake packet). (3) PacketInResult::EnlargeBuffer is merged into PacketInResult::Accepted with a new buffer_size: Option<NonZeroU16> field. (4) Reassembler now stores sync_bits and exposes them; Reassembler::single_inplace is renamed to single. (5) ACK handling verifies checksums on ACK packets and supports retransmission of lost ACKs. (6) Tests are expanded to cover v2.0 and v2.1 behavior, packet loss, damage, and piggybacking scenarios. No cryptographic primitives or trust assumptions are changed; this is a reliability/efficiency protocol upgrade.
Changed components
rust/trezor-thp/src/alternating_bit.rsrust/trezor-thp/src/channel/mod.rsrust/trezor-thp/src/channel/host.rsrust/trezor-thp/src/channel/device.rsrust/trezor-thp/src/channel/buffered.rsrust/trezor-thp/src/control_byte.rsrust/trezor-thp/src/fragment.rsrust/trezor-thp/examples/host-cli/client.rsrust/trezor-thp/examples/host-cli/main.rsInspect captured patch +786 / −329
diff --git a/rust/trezor-thp/examples/host-cli/client.rs b/rust/trezor-thp/examples/host-cli/client.rs
index 26bb6472..5dcf46c5 100644
--- a/rust/trezor-thp/examples/host-cli/client.rs
+++ b/rust/trezor-thp/examples/host-cli/client.rs
@@ -2,7 +2,7 @@ use std::io::ErrorKind;
use std::net::{SocketAddr, UdpSocket};
use std::time::Duration;
-use trezor_thp::{Backend, ChannelIO, Error, channel::buffered::Buffered, channel::host::Mux};
+use trezor_thp::{Backend, ChannelIO, channel::buffered::Buffered, channel::host::Mux};
use protobuf::{Enum, Message};
@@ -67,8 +67,7 @@ impl<C: ChannelIO> Client<C> {
None
}
Err(e) => {
- log::error!("Cannot read from UDP socket: {}.", e);
- panic!();
+ panic!("Cannot read from UDP socket: {}.", e);
}
}
}
@@ -120,8 +119,7 @@ impl<C: ChannelIO> Client<C> {
let mut done = false;
while !done {
let Some(packet) = self.recv_from(READ_TIMEOUT) else {
- log::error!("Timed out waiting for response for {:?}.", READ_TIMEOUT);
- panic!();
+ panic!("Timed out waiting for response for {:?}.", READ_TIMEOUT);
};
let pir = self.channel.packet_in(&packet).check_failed().unwrap();
assert!(!pir.got_transport_error());
@@ -130,13 +128,10 @@ impl<C: ChannelIO> Client<C> {
}
result = match self.channel.message_out() {
Ok(r) => Some(r),
- Err(Error::InvalidChecksum | Error::MalformedData) => {
- log::error!("Received bad message, waiting for retransmission.");
- continue;
- }
Err(e) => {
- log::error!("Cannot read message from channel: {:?}.", e);
- panic!();
+ // Since checksum is verified in `packet_in`, there is no recoverable error
+ // returned by `message_out`. Exit now to avoid getting stuck.
+ panic!("Cannot read message from channel: {:?}.", e);
}
};
}
diff --git a/rust/trezor-thp/examples/host-cli/main.rs b/rust/trezor-thp/examples/host-cli/main.rs
index cf3c11a6..6b63199d 100644
--- a/rust/trezor-thp/examples/host-cli/main.rs
+++ b/rust/trezor-thp/examples/host-cli/main.rs
@@ -43,6 +43,19 @@ where
let device_properties =
ThpDeviceProperties::parse_from_bytes(&client.device_properties).unwrap();
log::debug!("Device properties: {:?}.", device_properties);
+ match (
+ device_properties.protocol_version_major,
+ device_properties.protocol_version_minor,
+ ) {
+ (Some(major), Some(minor)) => {
+ let major: u8 = major.try_into().unwrap();
+ let minor: u8 = minor.try_into().unwrap();
+ client.channel.set_device_protocol_version(major, minor);
+ }
+ _ => {
+ log::warn!("Protocol version missing in device properties.");
+ }
+ }
// Handshake should finish within 2 request-response cycles.
client.call(0, &[]);
client.call(0, &[]);
diff --git a/rust/trezor-thp/src/alternating_bit.rs b/rust/trezor-thp/src/alternating_bit.rs
index 1739ab8c..6a678e65 100644
--- a/rust/trezor-thp/src/alternating_bit.rs
+++ b/rust/trezor-thp/src/alternating_bit.rs
@@ -1,5 +1,5 @@
use crate::{
- control_byte::{ACK_BIT, SEQ_BIT},
+ control_byte::{ACK_BIT, SEQ_BIT, SYNC_MASK},
error::Error,
};
@@ -38,7 +38,7 @@ impl SyncBits {
impl From<u8> for SyncBits {
fn from(byte: u8) -> Self {
- Self(byte)
+ Self(byte & SYNC_MASK)
}
}
@@ -99,7 +99,10 @@ impl ChannelSync {
return None; // sending in progress, don't send
}
- let sb = SyncBits::new().with_seq_bit(self.sync_send);
+ let mut sb = SyncBits::new().with_seq_bit(self.sync_send);
+ if self.ack_piggybacking {
+ sb = sb.with_ack_bit(self.get_ack_bit());
+ }
Some(sb) // start sending, use these bits
}
@@ -120,7 +123,7 @@ impl ChannelSync {
/// Call after receving initial fragment of a message.
/// Returns true when seq_bit is correct and we should reassemble the message.
/// If the function returns false all following continuation packets should be discarded.
- pub fn receive_start(&mut self, sb: SyncBits) -> bool {
+ pub fn receive_start(&self, sb: SyncBits) -> bool {
if sb.seq_bit() != self.sync_receive {
// Either this message is a duplicate or previous one was dropped.
return false;
@@ -133,9 +136,23 @@ impl ChannelSync {
/// Call after receiving last fragment and successfully verifying CRC of the message.
/// Caller needs to send ACK with the returned SyncBits.
pub fn receive_acknowledge(&mut self) -> SyncBits {
- let sb = SyncBits::new().with_ack_bit(self.sync_receive);
self.sync_receive.increment();
- sb
+ SyncBits::new().with_ack_bit(self.get_ack_bit())
+ }
+
+ // Returns previous value of the receive bit. We increment the bit after successfully
+ // receiving the message, an ACK is sent afterwards so it has to use the previous value.
+ fn get_ack_bit(&self) -> bool {
+ self.sync_receive.previous()
+ }
+
+ pub fn allow_ack_piggybacking(&mut self) {
+ log::debug!("Enabled ACK piggybacking.");
+ self.ack_piggybacking = true;
+ }
+
+ pub fn is_ack_piggybacking_allowed(&self) -> bool {
+ self.ack_piggybacking
}
/// Serialize for storage.
@@ -177,13 +194,21 @@ impl Default for ChannelSync {
}
trait BoolExt {
+ /// Increments the value.
fn increment(&mut self);
+
+ /// Copies the value and decrements it.
+ fn previous(&self) -> Self;
}
impl BoolExt for bool {
fn increment(&mut self) {
*self = !*self;
}
+
+ fn previous(&self) -> Self {
+ !self
+ }
}
#[cfg(test)]
diff --git a/rust/trezor-thp/src/channel/buffered.rs b/rust/trezor-thp/src/channel/buffered.rs
index 6e4e9059..e8f2b308 100644
--- a/rust/trezor-thp/src/channel/buffered.rs
+++ b/rust/trezor-thp/src/channel/buffered.rs
@@ -37,17 +37,22 @@ impl<C: ChannelIO> Buffered<C> {
let res = self
.channel
.packet_in(packet_buffer, self.receive_buffer.as_mut_slice());
- if let PacketInResult::EnlargeBuffer {
+ if let PacketInResult::Accepted {
ack_received,
- buffer_size,
+ message_ready,
+ pong,
+ buffer_size: Some(s),
+ ..
} = res
{
- log::debug!("Resizing receive buffer to {}.", buffer_size);
- self.receive_buffer.resize(buffer_size.into(), 0u8);
+ let new_size: u16 = s.into();
+ log::debug!("Resizing receive buffer to {}.", new_size);
+ self.receive_buffer.resize(new_size.into(), 0u8);
return PacketInResult::Accepted {
ack_received,
- message_ready: false,
- pong: false,
+ message_ready,
+ pong,
+ buffer_size: None,
};
}
res
diff --git a/rust/trezor-thp/src/channel/device.rs b/rust/trezor-thp/src/channel/device.rs
index 6a59b091..543cfe8c 100644
--- a/rust/trezor-thp/src/channel/device.rs
+++ b/rust/trezor-thp/src/channel/device.rs
@@ -132,7 +132,7 @@ where
// Returns true if allocation request has been received.
fn handle_broadcast(&mut self, packet: &[u8]) -> Result<bool, Error> {
- let (header, payload) = Reassembler::<Device>::single_inplace(packet)?;
+ let (header, payload) = Reassembler::<Device>::single(packet)?;
match header {
Header::Ping if payload.len() == Nonce::LEN => {
let (nonce, _rest) = Nonce::parse(payload)?;
@@ -328,11 +328,18 @@ impl<C: CredentialVerifier, B: Backend> ChannelOpen<C, B> {
}
fn incoming_internal(&mut self) -> Result<(), Error> {
+ let ChannelState::Receiving { reassembler, .. } = &self.channel.state else {
+ return Err(Error::not_ready());
+ };
+ let sync_bits = reassembler.sync_bits();
+
let (header, len) = self.channel.raw_out(&self.internal_buffer)?;
self.internal_buffer.truncate(len);
match (self.state, header.handshake_phase()) {
(HandshakeState::SendingChannelResponse, Some(HandshakeMessage::InitiationRequest)) => {
+ // enable ACK piggybacking if requested
+ self.enable_ack_piggybacking_if_requested(sync_bits);
let try_to_unlock = self.noise.read_initiation_request(&self.internal_buffer)?;
self.state = HandshakeState::StaticKeyRequired { try_to_unlock };
}
@@ -351,6 +358,12 @@ impl<C: CredentialVerifier, B: Backend> ChannelOpen<C, B> {
Ok(())
}
+ fn enable_ack_piggybacking_if_requested(&mut self, sync_bits: SyncBits) {
+ if sync_bits.ack_bit() {
+ self.channel.sync.allow_ack_piggybacking();
+ }
+ }
+
fn send_initiation_response(
&mut self,
static_privkey: &[u8; PRIVKEY_LEN],
@@ -480,19 +493,20 @@ where
let res = self
.channel
.packet_in(packet_buffer, &mut self.internal_buffer);
- if let PacketInResult::EnlargeBuffer { buffer_size, .. } = res {
+ if let PacketInResult::Accepted {
+ buffer_size: Some(s),
+ ..
+ } = res
+ {
log::error!(
"[{:04x}] Payload length {} exceeds handshake limit.",
self.channel_id(),
- buffer_size
+ s
);
// Possibly damaged length field, ignore continuations.
self.channel.state = ChannelState::Idle;
return PacketInResult::ignore(Error::malformed_data());
}
- if res.got_ack() {
- prepare_zeroed(&mut self.internal_buffer);
- }
if res.got_message() {
let handled = self.incoming_internal();
if let Err(e) = handled {
@@ -506,6 +520,8 @@ where
if let HandshakeState::StaticKeyRequired { try_to_unlock } = self.state {
return PacketInResult::HandshakeKeyRequired { try_to_unlock };
}
+ } else if res.got_ack() {
+ prepare_zeroed(&mut self.internal_buffer);
}
res
}
diff --git a/rust/trezor-thp/src/channel/host.rs b/rust/trezor-thp/src/channel/host.rs
index 90807bab..bd55b12c 100644
--- a/rust/trezor-thp/src/channel/host.rs
+++ b/rust/trezor-thp/src/channel/host.rs
@@ -137,7 +137,7 @@ where
let (header, _rest) = Header::<Host>::parse(packet)?;
match header {
Header::Pong => {
- let (_header, payload) = Reassembler::<Host>::single_inplace(packet)?;
+ let (_header, payload) = Reassembler::<Host>::single(packet)?;
let (nonce, _rest) = Nonce::parse(payload)?;
if PingState::AwaitingPong(nonce) != self.ping {
log::warn!("Ignoring PONG with invalid nonce.");
@@ -320,6 +320,8 @@ where
#[derive(Copy, Clone)]
enum HandshakeState {
+ /// Before first packet is sent, allowing user to call [`set_device_protocol_version`].
+ Initial,
/// `HH1`.
SendingInitiationRequest,
/// `HH2`.
@@ -363,14 +365,11 @@ impl<C: CredentialStore, B: Backend> ChannelOpen<C, B> {
try_to_unlock,
&mut internal_buffer,
)?;
- let header = Header::new_handshake(channel_id, HandshakeMessage::InitiationRequest, msg)?;
- let mut channel = Channel::new(channel_id);
- channel.raw_in(header, msg)?;
let len = msg.len();
internal_buffer.truncate(len);
let res = Self {
- channel,
- state: HandshakeState::SendingInitiationRequest,
+ channel: Channel::new(channel_id),
+ state: HandshakeState::Initial,
noise: hss,
internal_buffer,
device_properties,
@@ -436,10 +435,28 @@ impl<C: CredentialStore, B: Backend> ChannelOpen<C, B> {
PairingState::try_from(payload.as_slice())
}
+ /// Returns device's `ThpDeviceProperties` protobuf structure.
pub fn device_properties(&self) -> &[u8] {
self.device_properties.as_slice()
}
+ /// Set peer's protocol version as indicated in `device_properties`.
+ /// This method must be called before the first call to [`ChannelOpen::packet_out`].
+ /// If it's not called, version 2.0 is assumed, which should be universally compatible.
+ /// The library cannot do it automatically because it doesn't understand protocol buffers.
+ pub fn set_device_protocol_version(&mut self, major: u8, minor: u8) {
+ if !matches!(self.state, HandshakeState::Initial) {
+ log::error!(
+ "[{:04x}] Setting protocol version after handshake started has no effect.",
+ self.channel_id()
+ );
+ return;
+ }
+ if (major, minor) >= (2, 1) {
+ self.channel.sync.allow_ack_piggybacking();
+ }
+ }
+
/// True if handshake finished and [`ChannelOpen::complete()`] can be called.
pub fn handshake_done(&self) -> bool {
matches!(self.state, HandshakeState::Finished { .. })
@@ -490,22 +507,27 @@ where
B: Backend,
{
fn packet_in(&mut self, packet_buffer: &[u8], _receive_buffer: &mut [u8]) -> PacketInResult {
+ if matches!(self.state, HandshakeState::Initial) {
+ // Do not accept any packets before we started sending InitiationRequest;
+ return PacketInResult::ignore(Error::malformed_data());
+ }
let res = self
.channel
.packet_in(packet_buffer, &mut self.internal_buffer);
- if let PacketInResult::EnlargeBuffer { buffer_size, .. } = res {
+ if let PacketInResult::Accepted {
+ buffer_size: Some(s),
+ ..
+ } = res
+ {
log::error!(
"[{:04x}] Payload length {} exceeds handshake limit.",
self.channel_id(),
- buffer_size
+ s
);
// Possibly damaged length field, ignore continuations.
self.channel.state = ChannelState::Idle;
return PacketInResult::ignore(Error::malformed_data());
}
- if res.got_ack() {
- prepare_zeroed(&mut self.internal_buffer);
- }
if res.got_message() {
let handled = self.incoming_internal();
if let Err(e) = handled {
@@ -516,17 +538,29 @@ where
return PacketInResult::fail(e);
}
}
+ } else if res.got_ack() {
+ prepare_zeroed(&mut self.internal_buffer);
}
res
}
fn packet_out(&mut self, packet_buffer: &mut [u8], _send_buffer: &[u8]) -> Result<(), Error> {
+ if matches!(self.state, HandshakeState::Initial) {
+ let header = Header::<Host>::new_handshake(
+ self.channel_id(),
+ HandshakeMessage::InitiationRequest,
+ &self.internal_buffer,
+ )?;
+ self.channel
+ .raw_in_ext(header, &self.internal_buffer, true)?;
+ self.state = HandshakeState::SendingInitiationRequest;
+ }
self.channel
.packet_out(packet_buffer, &self.internal_buffer)
}
fn packet_out_ready(&self) -> bool {
- self.channel.packet_out_ready()
+ matches!(self.state, HandshakeState::Initial) || self.channel.packet_out_ready()
}
fn message_in(&mut self, _plaintext_len: usize, _send_buffer: &mut [u8]) -> Result<(), Error> {
diff --git a/rust/trezor-thp/src/channel/mod.rs b/rust/trezor-thp/src/channel/mod.rs
index 8319c941..4ea73745 100644
--- a/rust/trezor-thp/src/channel/mod.rs
+++ b/rust/trezor-thp/src/channel/mod.rs
@@ -14,6 +14,8 @@ use crate::{
header::{BROADCAST_CHANNEL_ID, Header, NONCE_LEN, parse_cb_channel, parse_u16},
};
+use core::num::NonZeroU16;
+
use noise::NoiseCiphers;
pub use noise::{
Backend, Cipher, DH, HANDSHAKE_HASH_LEN, Hash, PRIVKEY_LEN, PUBKEY_LEN, TAG_LEN, U8Array,
@@ -178,11 +180,24 @@ impl<R: Role, B: Backend> Channel<R, B> {
self.state = ChannelState::SendingError { error };
}
- fn raw_in(&mut self, header: Header<R>, send_buffer: &[u8]) -> Result<()> {
+ fn raw_in_ext(
+ &mut self,
+ header: Header<R>,
+ send_buffer: &[u8],
+ override_ack_bit: bool,
+ ) -> Result<()> {
let ChannelState::Idle = self.state else {
return Err(Error::not_ready());
};
- let sb = self.sync.send_start().ok_or_else(Error::not_ready)?;
+ let mut sb = self.sync.send_start().ok_or_else(Error::not_ready)?;
+ if self.sync.is_ack_piggybacking_allowed() {
+ // `sb` contains the ACK bit, cancel sending it as a message.
+ self.send_ack = None;
+ // Signal piggybacking support if host requested it.
+ if override_ack_bit {
+ sb = sb.with_ack_bit(true);
+ }
+ }
let fragmenter = Fragmenter::new(header, sb, send_buffer)?;
self.state = ChannelState::Sending {
fragmenter,
@@ -191,23 +206,21 @@ impl<R: Role, B: Backend> Channel<R, B> {
Ok(())
}
+ fn raw_in(&mut self, header: Header<R>, send_buffer: &[u8]) -> Result<()> {
+ self.raw_in_ext(header, send_buffer, false)
+ }
+
fn raw_out(&mut self, receive_buffer: &[u8]) -> Result<(Header<R>, usize)> {
- let ChannelState::Receiving { reassembler } = &mut self.state else {
+ let ChannelState::Receiving { reassembler, .. } = &mut self.state else {
return Err(Error::not_ready());
};
if !reassembler.is_done() {
return Err(Error::not_ready());
}
- let len = match reassembler.verify(receive_buffer) {
- Ok(len) => len,
- Err(e) => {
- log::warn!(
- "[{}] Reassembled message with invalid checksum.",
- self.channel_id
- );
- return Err(e);
- }
- };
+ // Possible optimization: provided receive_buffer is the same as passed to
+ // `packet_in`, we're verifying the CRC for the second time, would be nice to
+ // get rid of it but probably won't make things massively faster.
+ let len = reassembler.verify(receive_buffer)?;
self.send_ack = Some(self.sync.receive_acknowledge());
let header = reassembler.header().clone();
self.state = ChannelState::Idle;
@@ -219,12 +232,12 @@ impl<R: Role, B: Backend> Channel<R, B> {
packet_buffer: &[u8],
receive_buffer: &mut [u8],
) -> Result<PacketInResult> {
- let (cb, channel_id, _rest) = parse_cb_channel(packet_buffer)?;
- if channel_id != self.channel_id {
+ let (cb, cid, _rest) = parse_cb_channel(packet_buffer)?;
+ if cid != self.channel_id {
log::warn!(
- "[{}] Invalid channel {}, ignoring.",
+ "[{:04x}] Invalid channel {:04x}, ignoring.",
self.channel_id,
- channel_id
+ cid
);
return Err(Error::malformed_data());
}
@@ -234,41 +247,41 @@ impl<R: Role, B: Backend> Channel<R, B> {
} else if cb.is_error() {
let te = self.handle_error(packet_buffer)?;
return Ok(PacketInResult::transport_error(te));
- }
- let is_cont = cb.is_continuation();
- if !(is_cont || cb.is_handshake() || cb.is_encrypted_transport()) {
+ } else if cb.is_continuation() {
+ self.handle_cont(packet_buffer, receive_buffer)?;
+ } else if cb.is_handshake() || cb.is_encrypted_transport() {
+ self.handle_invalid_seq(cb.sync_bits())?;
+ match &self.state {
+ // Initiation packet, normal case.
+ ChannelState::Idle | ChannelState::Receiving { .. } => {
+ self.handle_init(packet_buffer, receive_buffer)?;
+ }
+ // Initiation packet while we're sending.
+ ChannelState::Sending { .. } if self.sync.is_ack_piggybacking_allowed() => {
+ self.handle_init(packet_buffer, receive_buffer)?;
+ }
+ // Unexpected initiation packet.
+ _ => {
+ return Err(Error::malformed_data());
+ }
+ }
+ } else {
// Channel allocation and codec v1 are handled by Mux.
+ let cb = u8::from(cb);
log::warn!(
- "[{}] Unexpected control byte {}.",
+ "[{:04x}] Unexpected control byte 0x{:x}.",
self.channel_id,
- u8::from(cb)
+ cb
);
return Err(Error::malformed_data());
}
- Ok(match &mut self.state {
- // First fragment.
- ChannelState::Receiving { .. } | ChannelState::Idle if !is_cont => {
- let (is_done, enlarge) = self.handle_init(packet_buffer, receive_buffer)?;
- PacketInResult::accept(is_done).with_buffer(enlarge)
- }
- // Continuation fragments.
- ChannelState::Receiving { reassembler } => {
- reassembler.update(packet_buffer, receive_buffer)?;
- PacketInResult::accept(reassembler.is_done())
- }
- // Ignore unexpected continuations.
- ChannelState::Idle => return Err(Error::malformed_data()),
- // Might possibly happen when we've sent an ACK and it got lost.
- // We end up sending reply while the other side is retransmitting.
- // Is this recoverable?
- ChannelState::Sending { .. } => return Err(Error::malformed_data()),
- ChannelState::SendingError { .. } => return Err(Error::unexpected_input()),
- ChannelState::Failed { .. } => return Err(Error::unexpected_input()),
- })
+ self.handle_last_packet(receive_buffer)
}
fn handle_ack(&mut self, packet_buffer: &[u8]) -> Result<()> {
if matches!(self.state, ChannelState::Sending { .. }) {
+ // Verify checksum.
+ let _ = Reassembler::<R>::single(packet_buffer)?;
let sb = SyncBits::try_from(packet_buffer)?;
self.sync.send_mark_delivered(sb);
if self.sync.can_send() {
@@ -281,8 +294,7 @@ impl<R: Role, B: Backend> Channel<R, B> {
}
fn handle_error(&mut self, packet_buffer: &[u8]) -> Result<TransportError> {
- let mut err_buf = [0u8; 16];
- if let Ok((header, payload)) = Reassembler::<R>::single(packet_buffer, &mut err_buf) {
+ if let Ok((header, payload)) = Reassembler::<R>::single(packet_buffer) {
if header.is_error() {
if let Ok(te) = TransportError::try_from(payload) {
log::error!(
@@ -312,23 +324,89 @@ impl<R: Role, B: Backend> Channel<R, B> {
Err(Error::malformed_data())
}
- fn handle_init(
- &mut self,
- packet_buffer: &[u8],
- receive_buffer: &mut [u8],
- ) -> Result<(bool, Option<u16>)> {
- let sb = SyncBits::try_from(packet_buffer)?;
- if !self.sync.receive_start(sb) {
- // Bad sync bit, drop this packet and continuations.
- log::debug!("[{}] Bad sync bit, ignoring packet.", self.channel_id);
- self.state = ChannelState::Idle;
+ fn handle_cont(&mut self, packet_buffer: &[u8], receive_buffer: &mut [u8]) -> Result<()> {
+ let ChannelState::Receiving { reassembler } = &mut self.state else {
return Err(Error::malformed_data());
+ };
+ reassembler.update(packet_buffer, receive_buffer)
+ }
+
+ fn handle_invalid_seq(&mut self, sb: SyncBits) -> Result<()> {
+ if self.sync.receive_start(sb) {
+ return Ok(());
+ }
+ match &self.state {
+ ChannelState::Receiving { .. } => {
+ // Bad sync bit, drop the packet.
+ log::debug!("[{:04x}] Bad sync bit, ignoring packet.", self.channel_id);
+ }
+ ChannelState::Sending { .. } if self.sync.is_ack_piggybacking_allowed() => {
+ // ACK we sent was lost. Will be retransmitted along current outgoing message.
+ log::debug!("[{:04x}] Bad sync bit, ignoring packet.", self.channel_id);
+ }
+ _ => {
+ // Might happen when we've sent an ACK and it got lost.
+ // We end up sending reply while the other side is retransmitting.
+ // NOTE: no checksum verification because we drop the continuations
+ log::debug!(
+ "[{:04x}] Bad sync bit, resending last ACK.",
+ self.channel_id
+ );
+ self.send_ack = Some(SyncBits::new().with_ack_bit(sb.seq_bit()));
+ }
}
+ Err(Error::malformed_data())
+ }
+
+ fn handle_init(&mut self, packet_buffer: &[u8], receive_buffer: &mut [u8]) -> Result<()> {
receive_buffer.fill(0);
let reassembler = Reassembler::new(packet_buffer, receive_buffer)?;
- let is_done = reassembler.is_done();
+ self.state = ChannelState::Receiving { reassembler };
+ Ok(())
+ }
+
+ fn handle_last_packet(&mut self, receive_buffer: &mut [u8]) -> Result<PacketInResult> {
+ let ChannelState::Receiving { reassembler } = &self.state else {
+ return Err(Error::unexpected_input());
+ };
+ let mut message_ready = false;
+ let mut ack_received = false;
+ if reassembler.is_done() {
+ if let Err(e) = reassembler.verify(receive_buffer) {
+ log::warn!(
+ "[{:04x}] Reassembled message with invalid checksum.",
+ self.channel_id
+ );
+ self.state = ChannelState::Idle;
+ return Err(e);
+ }
+ message_ready = true;
+ if self.sync.is_ack_piggybacking_allowed() && !self.sync.can_send() {
+ self.sync.send_mark_delivered(reassembler.sync_bits());
+ if self.sync.can_send() {
+ ack_received = true;
+ } else {
+ log::warn!("[{:04x}] Unexpected ACK bit.", self.channel_id);
+ }
+ }
+ }
+ let pir = PacketInResult::Accepted {
+ ack_received,
+ message_ready,
+ pong: false,
+ buffer_size: self.check_buffer_len(receive_buffer),
+ };
+ Ok(pir)
+ }
+
+ fn check_buffer_len(&self, receive_buffer: &[u8]) -> Option<NonZeroU16> {
+ let ChannelState::Receiving { reassembler } = &self.state else {
+ return None;
+ };
let payload_len = reassembler.header().payload_len();
- let enlarge = (usize::from(payload_len) > receive_buffer.len()).then_some(payload_len);
+ let enlarge = (usize::from(payload_len) > receive_buffer.len())
+ .then_some(payload_len)
+ .and_then(NonZeroU16::new);
if enlarge.is_some() {
log::debug!(
"Message is larger ({}) than receive buffer ({}), requesting reallocation.",
@@ -336,8 +414,7 @@ impl<R: Role, B: Backend> Channel<R, B> {
receive_buffer.len()
);
}
- self.state = ChannelState::Receiving { reassembler };
- Ok((is_done, enlarge))
+ enlarge
}
}
@@ -354,15 +431,11 @@ pub enum PacketInResult {
message_ready: bool,
/// True if the packet was a valid keep-alive reply ("PONG") message.
pong: bool,
- },
- /// Channel ingested the packet and started reassembling a message that is larger
- /// than the current receive buffer. Resize it (keep the initial part) or destroy the channel.
- EnlargeBuffer {
- /// True if the packet contained valid ACK and channel is ready to send next message.
- /// Reserved for ACK piggybacking.
- ack_received: bool,
- /// Message size including checksum, the minimum new size of receive buffer.
- buffer_size: u16,
+ /// If Some, initiation packet was received for message that is larger than the current
+ /// receive buffer. Resize it (keep the initial part) or destroy the channel.
+ /// Please note the size is returned before checksum verification and can be unusually
+ /// large in presence of bit errors.
+ buffer_size: Option<NonZeroU16>,
},
/// Peer sent a `TRANSPORT_ERROR` message.
TransportError {
@@ -399,16 +472,7 @@ impl PacketInResult {
ack_received: false,
message_ready,
pong: false,
- }
- }
-
- const fn with_buffer(self, enlarge_receive_buffer: Option<u16>) -> Self {
- match enlarge_receive_buffer {
- Some(buffer_size) => Self::EnlargeBuffer {
- ack_received: self.got_ack(),
- buffer_size,
- },
- None => self,
+ buffer_size: None,
}
}
@@ -421,6 +485,7 @@ impl PacketInResult {
ack_received: true,
message_ready: false,
pong: false,
+ buffer_size: None,
}
}
@@ -445,6 +510,7 @@ impl PacketInResult {
ack_received: false,
message_ready: false,
pong: true,
+ buffer_size: None,
}
}
@@ -452,7 +518,6 @@ impl PacketInResult {
pub const fn got_ack(&self) -> bool {
match self {
Self::Accepted { ack_received, .. } => *ack_received,
- Self::EnlargeBuffer { ack_received, .. } => *ack_received,
_ => false,
}
}
@@ -518,7 +583,8 @@ impl PacketInResult {
///
/// To give the application freedom in handling precious buffers used during message
/// fragmentation and reassembly, channel does not keep ownership of them, instead they need
-/// to be passed along every call.
+/// to be passed along every call. You can wrap the channel in [`buffered::Buffered`] to
+/// handle the buffers for you.
pub trait ChannelIO {
/// Session ID (1B) + message type (2B) + AEAD tag (16B).
const BUFFER_OVERHEAD: usize = APP_HEADER_LEN + TAG_LEN;
@@ -567,9 +633,6 @@ pub trait ChannelIO {
/// Pick up reassembled and decrypted message. The channel will send ACK packet afterwards.
///
/// Returns [`Error::NotReady`] if there is no reassembled message.
- ///
- /// Returns [`Error::InvalidChecksum`] if message integrity failed. Usually the other side
- /// will retransmit the message.
fn message_out<'a>(&mut self, receive_buffer: &'a mut [u8]) -> Result<(u8, u16, &'a [u8])>;
/// Is there a reassembled incoming message ready?
diff --git a/rust/trezor-thp/src/channel/test.rs b/rust/trezor-thp/src/channel/test.rs
index 400e507e..78468031 100644
--- a/rust/trezor-thp/src/channel/test.rs
+++ b/rust/trezor-thp/src/channel/test.rs
@@ -84,6 +84,7 @@ impl<C: CredentialVerifier, B: Backend> ChannelIO for WithKey<C, B> {
ack_received: false,
message_ready: false,
pong: false,
+ buffer_size: None,
};
}
pir
@@ -149,51 +150,71 @@ impl<C: CredentialVerifier, B: Backend> WithKeyExt<C, B> for super::device::Chan
}
}
-fn take_turns_mutate<C1, C2, F>(
+fn take_turns_until_fn<C1, C2, Fu, Fw, T>(
+ until_func: &mut Fu,
host: &mut Buffered<C1>,
device: &mut Buffered<C2>,
- mut func: F,
-) -> Result<TakeTurnsResult>
+ wire_func: &mut Fw,
+) -> Result<Option<T>>
where
C1: ChannelIO,
C2: ChannelIO,
- F: FnMut(Direction, &mut VecDeque<Packet>),
+ Fu: FnMut(Direction, &mut Buffered<C1>, &mut Buffered<C2>, PacketInResult) -> Option<T>,
+ Fw: FnMut(Direction, &mut VecDeque<Packet>),
{
- let mut msg = TakeTurnsResult::None;
- let mut wire = VecDeque::<Packet>::new();
while host.packet_out_ready() || device.packet_out_ready() {
- while host.packet_out_ready() {
- wire.push_back(host.packet_out()?);
- }
- if !wire.is_empty() {
- func(HostToDevice, &mut wire);
- }
- for packet in wire.iter() {
- log::trace!("> {}", hex::encode(packet));
- device.packet_in(packet).check_failed()?;
- }
- if device.message_out_ready() {
- assert!(msg.is_none());
- msg = Some((HostToDevice, device.message_out()?));
- }
- wire.clear();
- while device.packet_out_ready() {
- wire.push_back(device.packet_out()?);
+ let pir = HostToDevice.transfer_fn(host, device, wire_func)?;
+ let res = until_func(HostToDevice, host, device, pir);
+ if res.is_some() {
+ return Ok(res);
}
- if !wire.is_empty() {
- func(DeviceToHost, &mut wire);
+ let pir = DeviceToHost.transfer_fn(host, device, wire_func)?;
+ let res = until_func(DeviceToHost, host, device, pir);
+ if res.is_some() {
+ return Ok(res);
}
- for packet in wire.iter() {
- log::trace!("< {}", hex::encode(packet));
- host.packet_in(packet).check_failed()?;
- }
- if host.message_out_ready() {
- assert!(msg.is_none());
- msg = Some((DeviceToHost, host.message_out()?));
- }
- wire.clear();
}
- Ok(msg)
+ Ok(None)
+}
+
+fn until_stuck(
+ _dir: Direction,
+ _h: &mut Buffered<impl ChannelIO>,
+ _d: &mut Buffered<impl ChannelIO>,
+ _pir: PacketInResult,
+) -> Option<()> {
+ None
+}
+
+fn wire_flush_acks(_dir: Direction, wire: &mut VecDeque<Packet>) {
+ for packet in wire.iter() {
+ Header::<Host>::parse(packet)
+ .map(|(hdr, _)| assert!(hdr.is_ack()))
+ .unwrap();
+ }
+}
+
+fn take_turns_fn<C1, C2, F>(
+ host: &mut Buffered<C1>,
+ device: &mut Buffered<C2>,
+ wire_func: &mut F,
+) -> Result<TakeTurnsResult>
+where
+ C1: ChannelIO,
+ C2: ChannelIO,
+ F: FnMut(Direction, &mut VecDeque<Packet>),
+{
+ let mut message_ready = |dir, host: &mut Buffered<C1>, device: &mut Buffered<C2>, _pir| {
+ if dir == HostToDevice && device.message_out_ready() {
+ return Some((HostToDevice, device.message_out().unwrap()));
+ } else if dir == DeviceToHost && host.message_out_ready() {
+ return Some((DeviceToHost, host.message_out().unwrap()));
+ }
+ None
+ };
+ let res = take_turns_until_fn(&mut message_ready, host, device, wire_func);
+ take_turns_until_fn(&mut until_stuck, host, device, &mut wire_flush_acks)?;
+ res
}
fn take_turns<C1, C2>(host: &mut Buffered<C1>, device: &mut Buffered<C2>) -> Result<TakeTurnsResult>
@@ -201,10 +222,77 @@ where
C1: ChannelIO,
C2: ChannelIO,
{
- take_turns_mutate(host, device, |_, _| {})
+ take_turns_fn(host, device, &mut |_, _| ())
}
impl Direction {
+ fn reverse_if(&self, cond: bool) -> Self {
+ if !cond {
+ return *self;
+ }
+ match self {
+ HostToDevice => DeviceToHost,
+ DeviceToHost => HostToDevice,
+ }
+ }
+ fn reverse(&self) -> Self {
+ self.reverse_if(true)
+ }
+
+ fn transfer_fn<C1, C2>(
+ self,
+ host: &mut Buffered<C1>,
+ device: &mut Buffered<C2>,
+ func: &mut impl FnMut(Direction, &mut VecDeque<Packet>),
+ ) -> Result<PacketInResult>
+ where
+ C1: ChannelIO,
+ C2: ChannelIO,
+ {
+ let mut wire = VecDeque::<Packet>::new();
+ let mut pir = PacketInResult::Ignored {
+ error: Error::NotReady,
+ };
+ if self == HostToDevice {
+ while host.packet_out_ready() {
+ wire.push_back(host.packet_out()?);
+ }
+ } else {
+ while device.packet_out_ready() {
+ wire.push_back(device.packet_out()?);
+ }
+ }
+ func(self, &mut wire);
+ for packet in wire.iter() {
+ // Only the last PacketInResult is returned, check that the others are "boring".
+ assert!(matches!(
+ pir,
+ PacketInResult::Ignored { .. }
+ | PacketInResult::Accepted {
+ message_ready: false,
+ pong: false,
+ ..
+ }
+ ));
+ if self == HostToDevice {
+ log::trace!("> {}", hex::encode(packet));
+ pir = device.packet_in(packet).check_failed()?;
+ } else {
+ log::trace!("< {}", hex::encode(packet));
+ pir = host.packet_in(packet).check_failed()?;
+ }
+ }
+ Ok(pir)
+ }
+
+ fn transfer(
+ self,
+ host: &mut Buffered<impl ChannelIO>,
+ device: &mut Buffered<impl ChannelIO>,
+ ) -> Result<PacketInResult> {
+ self.transfer_fn(host, device, &mut |_, _| ())
+ }
+
fn send_nocheck<C1, C2>(
self,
host: &mut Buffered<C1>,
@@ -238,7 +326,11 @@ impl Direction {
C2: ChannelIO,
{
let msg = self.send_nocheck(host, device, sid, mty, message)?;
- assert_eq!(msg, Some((self, (sid, mty, message.into()))));
+ if self == HostToDevice {
+ assert_eq!(msg, Some((HostToDevice, (sid, mty, message.into()))));
+ } else {
+ assert_eq!(msg, Some((DeviceToHost, (sid, mty, message.into()))));
+ }
Ok(())
}
@@ -252,22 +344,48 @@ impl Direction {
assert_eq!(msg, None);
Ok(())
}
+
+ fn call<C1: ChannelIO, C2: ChannelIO>(
+ self,
+ host: &mut Buffered<C1>,
+ device: &mut Buffered<C2>,
+ msgs: &[&[u8]],
+ ) -> Result<()> {
+ let mut i: usize = 0;
+ let mut func = |dir, host: &mut Buffered<C1>, device: &mut Buffered<C2>, _pir| {
+ if dir == HostToDevice && device.message_out_ready() {
+ let (_, _, msg) = device.message_out().unwrap();
+ assert_eq!(msg, msgs[i]);
+ i += 1;
+ if i < msgs.len() {
+ device.message_in(5, 10, msgs[i]).unwrap();
+ }
+ } else if dir == DeviceToHost && host.message_out_ready() {
+ let (_, _, msg) = host.message_out().unwrap();
+ assert_eq!(msg, msgs[i]);
+ i += 1;
+ if i < msgs.len() {
+ host.message_in(5, 11, msgs[i]).unwrap();
+ }
+ }
+ None::<()>
+ };
+ if self == HostToDevice {
+ host.message_in(5, 10, msgs[0])?;
+ } else {
+ device.message_in(5, 11, msgs[0])?;
+ }
+ take_turns_until_fn(&mut func, host, device, &mut |_, _| ())?;
+ assert_eq!(i, msgs.len());
+ Ok(())
+ }
}
#[test]
fn test_open() -> Result<()> {
setup();
- let (mut hm, mut dm, cids) = create_mux();
- // channel allocation
- hm.request_channel(false);
- take_turns(&mut hm, &mut dm)?;
- let mut d = dm
- .channel_alloc(cids.get(), TestCredentialVerifier)?
- .with_key(DEVICE_KEY)
- .into_buffered();
- take_turns(&mut hm, &mut d)?;
- let mut h = hm.channel_alloc(NullCredentialStore)?.into_buffered();
+ let (mut h, mut d) = alloc_channel()?;
// handshake
assert!(!h.handshake_done());
@@ -292,8 +410,95 @@ fn test_open() -> Result<()> {
Ok(())
}
+fn check_number_of_packets(mut counts: Vec<usize>) -> impl FnMut(Direction, &mut VecDeque<Packet>) {
+ let mut turn: usize = 0;
+ counts.reverse();
+ return move |_: Direction, wire: &mut VecDeque<Packet>| {
+ turn += 1;
+ let count = counts.pop().unwrap_or(0);
+ assert!(
+ wire.len() == count,
+ "Turn {}: expected {} packets, got {:?}.",
+ turn,
+ count,
+ wire.iter().map(hex::encode).collect::<Vec<_>>(),
+ );
+ };
+}
+
+fn check_just_one(dir: Direction, wire: &mut VecDeque<Packet>) {
+ check_number_of_packets(vec![1])(dir, wire)
+}
+
#[test]
-fn test_device_locked() -> Result<()> {
+fn test_open_piggybacking() -> Result<()> {
+ setup();
+
+ let (mut h, mut d) = alloc_channel()?;
+ h.set_device_protocol_version(2, 1); // Enable piggybacking.
+
+ // handshake
+ assert!(!h.handshake_done());
+ assert!(!d.handshake_done());
+ take_turns_fn(
+ &mut h,
+ &mut d,
+ &mut check_number_of_packets(vec![1, 2, 2, 1, 1]),
+ )?;
+ assert!(h.handshake_done());
+ assert!(d.handshake_done());
+ let mut h = h.map(|h| h.complete())?;
+ let mut d = d.map(|d| d.unwrap().complete())?;
+
+ // first message, no ACK
+ h.message_in(0, 1010, b"ThpSelectMethod with SkipParing")?;
+ let pir = HostToDevice.transfer_fn(&mut h, &mut d, &mut check_just_one)?;
+ assert!(!pir.got_ack() && pir.got_message());
+ d.message_out()?;
+
+ // piggybacked ACK
+ d.message_in(0, 1019, b"ThpEndResponse means done")?;
+ let pir = DeviceToHost.transfer_fn(&mut h, &mut d, &mut check_just_one)?;
+ assert!(pir.got_ack() && pir.got_message());
+ h.message_out()?;
+
+ // packet goes out before message - standalone ACK
+ let pir = HostToDevice.transfer_fn(&mut h, &mut d, &mut check_just_one)?;
+ assert!(pir.got_ack() && !pir.got_message());
+ h.message_in(0, 1234, b"Ping")?;
+ let pir = HostToDevice.transfer_fn(&mut h, &mut d, &mut check_just_one)?;
+ assert!(!pir.got_ack() && pir.got_message());
+ d.message_out()?;
+
+ // packet goes out before message - standalone ACK
+ let pir = DeviceToHost.transfer_fn(&mut h, &mut d, &mut check_just_one)?;
+ assert!(pir.got_ack() && !pir.got_message());
+ d.message_in(0, 5678, b"Pong")?;
+ let pir = DeviceToHost.transfer_fn(&mut h, &mut d, &mut check_just_one)?;
+ assert!(!pir.got_ack() && pir.got_message());
+ h.message_out()?;
+
+ // piggybacked ACK
+ h.message_in(1, 9999, &[0u8; 999])?;
+ let pir = HostToDevice.transfer(&mut h, &mut d)?;
+ assert!(pir.got_ack() && pir.got_message());
+ d.message_out()?;
+
+ // piggybacked ACK
+ d.message_in(1, 9998, &[9u8; 666])?;
+ let pir = DeviceToHost.transfer(&mut h, &mut d)?;
+ assert!(pir.got_ack() && pir.got_message());
+ h.message_out()?;
+
+ // no further message, just ACK
+ let pir = HostToDevice.transfer_fn(&mut h, &mut d, &mut check_just_one)?;
+ assert!(pir.got_ack() && !pir.got_message());
+ Ok(())
+}
+
+#[test_case(false; "v20")]
+#[test_case(true; "v21")]
+fn test_device_locked(ack_piggybacking: bool) -> Result<()> {
setup();
let (mut hm, mut dm, cids) = create_mux();
@@ -305,6 +510,9 @@ fn test_device_locked() -> Result<()> {
.into_buffered();
take_turns(&mut hm, &mut d)?;
let mut h = hm.channel_alloc(NullCredentialStore)?.into_buffered();
+ if ack_piggybacking {
+ h.set_device_protocol_version(2, 1);
+ }
// handshake
assert!(!h.handshake_done());
@@ -334,11 +542,28 @@ fn create_mux() -> (
(hm, dm, cids)
}
+fn alloc_channel() -> Result<(
+ Buffered<host::ChannelOpen<NullCredentialStore, RustCrypto>>,
+ Buffered<WithKey<TestCredentialVerifier, RustCrypto>>,
+)> {
+ let (mut hm, mut dm, cids) = create_mux();
+ hm.request_channel(false);
+ take_turns(&mut hm, &mut dm)?;
+ let mut d = dm
+ .channel_alloc(cids.get(), TestCredentialVerifier)?
+ .with_key(DEVICE_KEY)
+ .into_buffered();
+ take_turns(&mut hm, &mut d)?;
+ let h = hm.channel_alloc(NullCredentialStore)?.into_buffered();
+ Ok((h, d))
+}
+
fn open_channel(
packet_len: usize,
+ enable_piggybacking: bool,
) -> Result<(
- Buffered<Channel<Host, RustCrypto>>,
- Buffered<Channel<Device, RustCrypto>>,
+ Buffered<host::Channel<RustCrypto>>,
+ Buffered<device::Channel<RustCrypto>>,
)> {
let (mut hm, mut dm, cids) = create_mux();
hm.set_packet_len(packet_len);
@@ -351,6 +576,9 @@ fn open_channel(
.into_buffered();
take_turns(&mut d, &mut hm)?;
let mut h = hm.channel_alloc(NullCredentialStore)?.into_buffered();
+ if enable_piggybacking {
+ h.set_device_protocol_version(2, 1);
+ }
take_turns(&mut h, &mut d)?;
let h = h.map(|h| h.complete())?;
let d = d.map(|d| d.unwrap().complete())?;
@@ -358,32 +586,35 @@ fn open_channel(
Ok((h, d))
}
-#[test_case(10, 0; "empty messages")]
-#[test_case(30, 1; "short messages")]
-#[test_case(20, 2000; "medium messages")]
-#[test_case(5, 59976; "huge messages")]
-#[test_case(1000, 100; "lots of messages")]
-fn test_messages(count: usize, length: usize) -> Result<()> {
+#[test_case(10, 0, false; "empty messages")]
+#[test_case(30, 1, false; "short messages")]
+#[test_case(20, 2000, false; "medium messages")]
+#[test_case(5, 59976, false; "huge messages")]
+#[test_case(1000, 100, false; "lots of messages")]
+#[test_case(10, 0, true; "empty messages v21")]
+#[test_case(30, 1, true; "short messages v21")]
+#[test_case(5, 59976, true; "huge messages v21")]
+fn test_messages(count: usize, length: usize, ack_piggybacking: bool) -> Result<()> {
setup();
- let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN)?;
+ let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN, ack_piggybacking)?;
let payload = vec![42u8; length];
for _i in 0..count {
- HostToDevice.send(&mut h, &mut d, 10, 100, &payload)?;
- DeviceToHost.send(&mut h, &mut d, 10, 100, &payload)?;
+ HostToDevice.call(&mut h, &mut d, &[&payload, &payload])?;
}
Ok(())
}
-#[test_case(17; "tiny")]
-#[test_case(256; "medium")]
-#[test_case(1500; "large")]
-fn test_packet_length(packet_len: usize) -> Result<()> {
+#[test_case(17, false; "tiny")]
+#[test_case(256, false; "medium")]
+#[test_case(1500, false; "large")]
+#[test_case(17, true; "tiny_v21")]
+#[test_case(256, true; "medium_v21")]
+fn test_packet_length(packet_len: usize, ack_piggybacking: bool) -> Result<()> {
setup();
- let (mut h, mut d) = open_channel(packet_len)?;
+ let (mut h, mut d) = open_channel(packet_len, ack_piggybacking)?;
let payload = vec![69u8; 3200];
for _i in 0..10 {
- HostToDevice.send(&mut h, &mut d, 10, 100, &payload)?;
- DeviceToHost.send(&mut h, &mut d, 10, 100, &payload)?;
+ HostToDevice.call(&mut h, &mut d, &[&payload, &payload])?;
}
Ok(())
}
@@ -400,7 +631,7 @@ fn test_one_device_multiple_hosts() -> Result<()> {
let mut host_chans = Vec::<Buffered<Channel<Host, RustCrypto>>>::new();
// open channels
- for _i in 0..NHOSTS {
+ for i in 0..NHOSTS {
let mut hm = host::Mux::<RustCrypto>::new().into_buffered();
hm.set_packet_len(DEFAULT_PACKET_LEN);
hm.request_channel(false);
@@ -411,6 +642,7 @@ fn test_one_device_multiple_hosts() -> Result<()> {
.into_buffered();
take_turns(&mut d, &mut hm)?;
let mut h = hm.channel_alloc(NullCredentialStore)?.into_buffered();
+ h.set_device_protocol_version(2, (i as u8) % 2); // odd => piggybacking
take_turns(&mut h, &mut d)?;
let h = h.map(|h| h.complete())?;
let d = d.map(|d| d.unwrap().complete())?;
@@ -427,8 +659,11 @@ fn test_one_device_multiple_hosts() -> Result<()> {
// normal operation
let payload = vec![67u8; 160];
for i in 0..NHOSTS {
- HostToDevice.send(&mut host_chans[i], &mut device_chans[i], 10, 100, &payload)?;
- DeviceToHost.send(&mut host_chans[i], &mut device_chans[i], 10, 100, &payload)?;
+ HostToDevice.call(
+ &mut host_chans[i],
+ &mut device_chans[i],
+ &[&payload, &payload],
+ )?;
}
// channel mismatch, packets should be ignored
@@ -473,7 +708,7 @@ fn test_packet_loss_alloc() -> Result<()> {
let (mut hm, mut dm, cids) = create_mux();
// channel allocation request lost
hm.request_channel(false);
- take_turns_mutate(&mut hm, &mut dm, lose_nth(HostToDevice, 0))?;
+ take_turns_fn(&mut hm, &mut dm, &mut lose_nth(HostToDevice, 0))?;
assert!(!dm.channel_alloc_ready());
// channel allocation response lost
@@ -482,7 +717,7 @@ fn test_packet_loss_alloc() -> Result<()> {
let mut d = dm
.channel_alloc(cids.get(), TestCredentialVerifier)?
.into_buffered();
- take_turns_mutate(&mut hm, &mut d, lose_nth(DeviceToHost, 0))?;
+ take_turns_fn(&mut hm, &mut d, &mut lose_nth(DeviceToHost, 0))?;
assert!(!hm.channel_alloc_ready());
// successful allocation
@@ -496,59 +731,101 @@ fn test_packet_loss_alloc() -> Result<()> {
Ok(())
}
-#[test_case(HostToDevice, 0; "handshake_init_request")]
-//#[test_case(HostToDevice, 1; "handshake_init_response_ack")]
-#[test_case(HostToDevice, 2; "handshake_completion_request")]
-#[test_case(HostToDevice, 3; "handshake_completion_request_cont")]
-//#[test_case(HostToDevice, 4, "handshake_completion_response_ack")]
-//#[test_case(DeviceToHost, 0, "handshake_init_request_ack")]
-#[test_case(DeviceToHost, 1; "handshake_init_response")]
-#[test_case(DeviceToHost, 2; "handshake_init_response_cont")]
-//#[test_case(DeviceToHost, 3; "handshake_completion_request_ack")]
-#[test_case(DeviceToHost, 4; "handshake_completion_response")]
-fn test_packet_loss_handshake(dir: Direction, lost_index: usize) -> Result<()> {
- setup();
+fn handshake_timeout(
+ h: &mut Buffered<host::ChannelOpen<NullCredentialStore, RustCrypto>>,
+ d: &mut Buffered<WithKey<TestCredentialVerifier, RustCrypto>>,
+ who_retransmits: Direction,
+ max_timeouts: usize,
+) -> Result<()> {
+ let mut dir = who_retransmits;
+ for _ in 0..max_timeouts {
+ if dir == DeviceToHost {
+ assert!(d.sending_retry().is_some());
+ d.message_retransmit()?;
+ } else {
+ assert!(h.sending_retry().is_some());
+ h.message_retransmit()?;
+ }
+ take_turns_until_fn(&mut until_stuck, h, d, &mut |_, _| ())?;
+ if h.handshake_done() && d.handshake_done() {
+ return Ok(());
+ }
+ dir = dir.reverse();
+ }
+ panic!("Handshake did not finish within {} timeouts.", max_timeouts);
+}
- let (mut hm, mut dm, cids) = create_mux();
- // channel allocation
- hm.request_channel(false);
- take_turns(&mut hm, &mut dm)?;
- let mut d = dm
- .channel_alloc(cids.get(), TestCredentialVerifier)?
- .with_key(DEVICE_KEY)
- .into_buffered();
- take_turns(&mut hm, &mut d)?;
- let mut h = hm.channel_alloc(NullCredentialStore)?.into_buffered();
+// When an ACK is lost, depending on the timing, either peer can retransmit first.
+// Both cases are tested via the `who_retransmits` parameter, except the last ACK
+// awhere device does not want to send anything.
+#[test_case(HostToDevice, 0, HostToDevice; "handshake_init_request")]
+#[test_case(HostToDevice, 1, DeviceToHost; "handshake_init_response_ack1")]
+#[test_case(HostToDevice, 1, HostToDevice; "handshake_init_response_ack2")]
+#[test_case(HostToDevice, 2, HostToDevice; "handshake_completion_request")]
+#[test_case(HostToDevice, 3, HostToDevice; "handshake_completion_request_cont")]
+#[test_case(HostToDevice, 4, DeviceToHost; "handshake_completion_response_ack1")] // last ACK
+#[test_case(DeviceToHost, 0, HostToDevice; "handshake_init_request_ack1")]
+#[test_case(DeviceToHost, 0, DeviceToHost; "handshake_init_request_ack2")]
+#[test_case(DeviceToHost, 1, DeviceToHost; "handshake_init_response")]
+#[test_case(DeviceToHost, 2, DeviceToHost; "handshake_init_response_cont")]
+#[test_case(DeviceToHost, 3, HostToDevice; "handshake_completion_request_ack1")]
+#[test_case(DeviceToHost, 3, DeviceToHost; "handshake_completion_request_ack2")]
+#[test_case(DeviceToHost, 4, DeviceToHost; "handshake_completion_response")]
+fn test_packet_loss_handshake_v20(
+ dir: Direction,
+ lost_index: usize,
+ who_retransmits: Direction,
+) -> Result<()> {
+ setup();
+ let (mut h, mut d) = alloc_channel()?;
// handshake
- take_turns_mutate(&mut h, &mut d, lose_nth(dir, lost_index))?;
- if dir == DeviceToHost {
- assert!(!h.handshake_done());
- assert_eq!(h.sending_retry(), None);
- assert_eq!(d.sending_retry(), Some(0));
- d.message_retransmit()?;
- assert_eq!(d.sending_retry(), Some(1));
- } else {
- assert!(!d.handshake_done());
- assert_eq!(d.sending_retry(), None);
- assert_eq!(h.sending_retry(), Some(0));
- h.message_retransmit()?;
- assert_eq!(h.sending_retry(), Some(1));
- }
- take_turns(&mut h, &mut d)?;
+ take_turns_until_fn(
+ &mut until_stuck,
+ &mut h,
+ &mut d,
+ &mut lose_nth(dir, lost_index),
+ )?;
+ handshake_timeout(&mut h, &mut d, who_retransmits, 3)?;
let mut h = h.map(|h| h.complete())?;
let mut d = d.map(|d| d.unwrap().complete())?;
- // pairing
- HostToDevice.send(&mut h, &mut d, 0, 1010, b"ThpSelectMethod with SkipParing")?;
- DeviceToHost.send(&mut h, &mut d, 0, 1019, b"ThpEndResponse means done")?;
-
// application messaging
- HostToDevice.send(&mut h, &mut d, 0, 1234, b"Ping")?;
- DeviceToHost.send(&mut h, &mut d, 0, 5678, b"Pong")?;
+ HostToDevice.call(&mut h, &mut d, &[b"Ping", b"Pong"])?;
+
Ok(())
}
+#[test_case(HostToDevice, 0; "handshake_init_request")]
+#[test_case(DeviceToHost, 0; "handshake_init_response")]
+#[test_case(DeviceToHost, 1; "handshake_init_response_cont")]
+#[test_case(HostToDevice, 1; "handshake_completion_request")]
+#[test_case(HostToDevice, 2; "handshake_completion_request_cont")]
+#[test_case(DeviceToHost, 2; "handshake_completion_response")]
+#[test_case(HostToDevice, 3; "handshake_completion_response_ack")] // standalone last ACK
+fn test_packet_loss_handshake_v21(dir: Direction, lost_index: usize) -> Result<()> {
+ setup();
+ let (mut h, mut d) = alloc_channel()?;
+ h.set_device_protocol_version(2, 1);
+
+ // handshake
+ take_turns_until_fn(
+ &mut until_stuck,
+ &mut h,
+ &mut d,
+ &mut lose_nth(dir, lost_index),
+ )?;
+ // When packet is lost in the middle of an exchange, either side can possibly
+ // retransmit first. This test assumes the sender does, except for the last ACK.
+ let who_retransmits = dir.reverse_if(dir == HostToDevice && lost_index == 3);
+ handshake_timeout(&mut h, &mut d, who_retransmits, 2)?;
+ let mut h = h.map(|h| h.complete())?;
+ let mut d = d.map(|d| d.unwrap().complete())?;
+
+ // application messaging
+ HostToDevice.call(&mut h, &mut d, &[b"Ping", b"Pong"])
+}
+
fn damage_nth(
dir: Direction,
packet_index: usize,
@@ -583,7 +860,11 @@ fn test_packet_damage_alloc(byte_index: usize) -> Result<()> {
let (mut hm, mut dm, cids) = create_mux();
// channel allocation request lost
hm.request_channel(false);
- take_turns_mutate(&mut hm, &mut dm, damage_nth(HostToDevice, 0, byte_index))?;
+ take_turns_fn(
+ &mut hm,
+ &mut dm,
+ &mut damage_nth(HostToDevice, 0, byte_index),
+ )?;
assert!(!dm.channel_alloc_ready());
// channel allocation response lost
@@ -592,7 +873,11 @@ fn test_packet_damage_alloc(byte_index: usize) -> Result<()> {
let mut d = dm
.channel_alloc(cids.get(), TestCredentialVerifier)?
.into_buffered();
- take_turns_mutate(&mut hm, &mut d, damage_nth(DeviceToHost, 0, byte_index))?;
+ take_turns_fn(
+ &mut hm,
+ &mut d,
+ &mut damage_nth(DeviceToHost, 0, byte_index),
+ )?;
assert!(!hm.channel_alloc_ready());
// successful allocation
@@ -602,7 +887,7 @@ fn test_packet_damage_alloc(byte_index: usize) -> Result<()> {
.channel_alloc(cids.get(), TestCredentialVerifier)?
.into_buffered();
take_turns(&mut hm, &mut d)?;
- let mut _h = hm.channel_alloc(NullCredentialStore)?.into_buffered();
+ let _h = hm.channel_alloc(NullCredentialStore)?.into_buffered();
Ok(())
}
@@ -611,81 +896,90 @@ fn test_packet_damage_alloc(byte_index: usize) -> Result<()> {
[0, 1, 2, 3, 4],
[0, 1, 3, 6]
)]
-fn test_packet_damage_handshake(
+fn test_packet_damage_handshake_v20(
dir: Direction,
packet_index: usize,
byte_index: usize,
) -> Result<()> {
setup();
- // Don't test ACKs for now.
- let skip = [
+ let (mut h, mut d) = alloc_channel()?;
+ let acks = [
(HostToDevice, 1),
(HostToDevice, 4),
(DeviceToHost, 0),
(DeviceToHost, 3),
];
- if skip.contains(&(dir, packet_index)) {
- log::warn!("Skipping test case");
- return Ok(());
- }
-
- let (mut hm, mut dm, cids) = create_mux();
- // channel allocation
- hm.request_channel(false);
- take_turns(&mut hm, &mut dm)?;
- let mut d = dm
- .channel_alloc(cids.get(), TestCredentialVerifier)?
- .with_key(DEVICE_KEY)
- .into_buffered();
- take_turns(&mut hm, &mut d)?;
- let mut h = hm.channel_alloc(NullCredentialStore)?.into_buffered();
+ let who_retransmits = dir.reverse_if(acks.contains(&(dir, packet_index)));
// handshake
- take_turns_mutate(&mut h, &mut d, damage_nth(dir, packet_index, byte_index))?;
- if dir == DeviceToHost {
- assert!(!h.handshake_done());
- assert_eq!(h.sending_retry(), None);
- assert_eq!(d.sending_retry(), Some(0));
- d.message_retransmit()?;
- assert_eq!(d.sending_retry(), Some(1));
- } else {
- assert!(!d.handshake_done());
- assert_eq!(d.sending_retry(), None);
- assert_eq!(h.sending_retry(), Some(0));
- h.message_retransmit()?;
- assert_eq!(h.sending_retry(), Some(1));
- }
- take_turns(&mut h, &mut d)?;
+ take_turns_fn(
+ &mut h,
+ &mut d,
+ &mut damage_nth(dir, packet_index, byte_index),
+ )?;
+ handshake_timeout(&mut h, &mut d, who_retransmits, 2)?;
let mut h = h.map(|h| h.complete())?;
let mut d = d.map(|d| d.unwrap().complete())?;
- // pairing
- HostToDevice.send(&mut h, &mut d, 0, 1010, b"ThpSelectMethod with SkipParing")?;
- DeviceToHost.send(&mut h, &mut d, 0, 1019, b"ThpEndResponse means done")?;
+ // pairing, appdata
+ HostToDevice.call(
+ &mut h,
+ &mut d,
+ &[
+ b"ThpSelectMethod with SkipParing",
+ b"ThpEndResponse means done",
+ b"Ping",
+ b"Pong",
+ ],
+ )
+}
- // application messaging
- HostToDevice.send(&mut h, &mut d, 0, 1234, b"Ping")?;
- DeviceToHost.send(&mut h, &mut d, 0, 5678, b"Pong")?;
- Ok(())
+#[test_matrix(
+ [HostToDevice, DeviceToHost],
+ [0, 1, 2],
+ [0, 1, 3, 6]
+)]
+fn test_packet_damage_handshake_v21(
+ dir: Direction,
+ packet_index: usize,
+ byte_index: usize,
+) -> Result<()> {
+ setup();
+ let (mut h, mut d) = alloc_channel()?;
+ h.set_device_protocol_version(2, 1);
+
+ take_turns_fn(
+ &mut h,
+ &mut d,
+ &mut damage_nth(dir, packet_index, byte_index),
+ )?;
+ handshake_timeout(&mut h, &mut d, dir, 1)?;
+ let mut h = h.map(|h| h.complete())?;
+ let mut d = d.map(|d| d.unwrap().complete())?;
+
+ HostToDevice.call(&mut h, &mut d, &[b"Ping", b"Pong"])
}
-#[test]
-fn test_packet_damage_application() -> Result<()> {
+#[test_case(false; "v20")]
+#[test_case(true; "v21")]
+fn test_packet_damage_application(ack_piggybacking: bool) -> Result<()> {
setup();
- let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN)?;
+ let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN, ack_piggybacking)?;
h.message_in(0, 0, b"the quick brown fox jumps over the lazy dog")?;
- let res = take_turns_mutate(&mut h, &mut d, damage_nth(HostToDevice, 0, 16));
- assert_eq!(res, Err(Error::InvalidChecksum));
- h.message_retransmit().unwrap();
- take_turns(&mut h, &mut d)?;
+ let res = take_turns_fn(&mut h, &mut d, &mut damage_nth(HostToDevice, 0, 16))?;
+ assert_eq!(res, None);
+ h.message_retransmit()?;
+ let res = take_turns(&mut h, &mut d)?;
+ assert!(matches!(res, Some((HostToDevice, _))));
d.message_in(0, 0, b"the quick lazy dog crawls under the brown fox")?;
- let res = take_turns_mutate(&mut h, &mut d, damage_nth(DeviceToHost, 0, 50));
- assert_eq!(res, Err(Error::InvalidChecksum));
- d.message_retransmit().unwrap();
- take_turns(&mut h, &mut d)?;
+ let res = take_turns_fn(&mut h, &mut d, &mut damage_nth(DeviceToHost, 0, 50))?;
+ assert_eq!(res, None);
+ d.message_retransmit()?;
+ let res = take_turns(&mut h, &mut d)?;
+ assert!(matches!(res, Some((DeviceToHost, _))));
Ok(())
}
@@ -716,7 +1010,8 @@ fn test_codec_v1() -> Result<()> {
PacketInResult::Accepted {
ack_received: false,
message_ready: false,
- pong: false
+ pong: false,
+ buffer_size: None,
}
));
let response = dm.packet_out().unwrap();
@@ -728,7 +1023,7 @@ fn test_codec_v1() -> Result<()> {
assert_ignored(&mut hm, &v1_cont);
// non-broadcast handling
- let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN)?;
+ let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN, false)?;
assert_ne!(h.channel_id, 0x2323);
assert_ne!(d.channel_id, 0x2323);
assert_ignored(&mut d, &v1_init);
@@ -761,7 +1056,8 @@ fn test_ping() -> Result<()> {
PacketInResult::Accepted {
ack_received: false,
message_ready: false,
- pong: false
+ pong: false,
+ buffer_size: None,
}
));
let pong_packet = dm.packet_out()?;
@@ -771,7 +1067,8 @@ fn test_ping() -> Result<()> {
PacketInResult::Accepted {
ack_received: false,
message_ready: false,
- pong: true
+ pong: true,
+ buffer_size: None,
}
));
assert!(pir.got_pong());
@@ -785,7 +1082,7 @@ fn test_ping() -> Result<()> {
assert!(matches!(pir, PacketInResult::Ignored { .. }));
// non-broadcast channels ignore ping
- let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN)?;
+ let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN, false)?;
let pir = h.packet_in(&ping_packet);
assert!(matches!(pir, PacketInResult::Ignored { .. }));
let pir = d.packet_in(&ping_packet);
@@ -838,8 +1135,8 @@ fn test_invalid_channel_id() -> Result<()> {
}
// normal channels return error for invalid ids
- let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN)?;
- for channel_id in INVALID {
+ let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN, false)?;
+ for channel_id in INVALID.iter().chain(&[BROADCAST_CHANNEL_ID]) {
assert_eq!(
d.packet_in(&make_packet(*channel_id)),
PacketInResult::Ignored {
@@ -902,21 +1199,34 @@ fn damage_nth_fix_crc(
};
}
-#[test]
-fn test_invalid_tag() -> Result<()> {
+#[test_case(false; "v20")]
+#[test_case(true; "v21")]
+fn test_invalid_tag(ack_piggybacking: bool) -> Result<()> {
setup();
- let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN)?;
+ let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN, ack_piggybacking)?;
h.message_in(0, 404, b"hello world hello world hello")
.unwrap();
- let res = take_turns_mutate(&mut h, &mut d, damage_nth_fix_crc(HostToDevice, 0, 10));
- assert_eq!(res, Err(Error::CryptoError));
-
- let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN)?;
+ take_turns_until_fn(
+ &mut until_stuck,
+ &mut h,
+ &mut d,
+ &mut damage_nth_fix_crc(HostToDevice, 0, 10),
+ )?;
+ assert!(d.message_out_ready());
+ assert_eq!(d.message_out(), Err(Error::CryptoError));
+
+ let (mut h, mut d) = open_channel(DEFAULT_PACKET_LEN, ack_piggybacking)?;
d.message_in(0, 403, b"hello world hello world hello")
.unwrap();
- let res = take_turns_mutate(&mut h, &mut d, damage_nth_fix_crc(DeviceToHost, 0, 20));
- assert_eq!(res, Err(Error::CryptoError));
+ take_turns_until_fn(
+ &mut until_stuck,
+ &mut h,
+ &mut d,
+ &mut damage_nth_fix_crc(DeviceToHost, 0, 20),
+ )?;
+ assert!(h.message_out_ready());
+ assert_eq!(h.message_out(), Err(Error::CryptoError));
Ok(())
}
diff --git a/rust/trezor-thp/src/control_byte.rs b/rust/trezor-thp/src/control_byte.rs
index a8bfd94b..9d6bbd7b 100644
--- a/rust/trezor-thp/src/control_byte.rs
+++ b/rust/trezor-thp/src/control_byte.rs
@@ -125,7 +125,7 @@ impl ControlByte {
}
pub fn sync_bits(&self) -> SyncBits {
- SyncBits::from(self.0 & SYNC_MASK)
+ SyncBits::from(self.0)
}
pub fn with_sync_bits(self, sb: SyncBits) -> Self {
diff --git a/rust/trezor-thp/src/fragment.rs b/rust/trezor-thp/src/fragment.rs
index a5e7aa33..650a22cf 100644
--- a/rust/trezor-thp/src/fragment.rs
+++ b/rust/trezor-thp/src/fragment.rs
@@ -109,6 +109,7 @@ impl<R: Role> Fragmenter<R> {
pub struct Reassembler<R: Role> {
header: Header<R>,
+ sync_bits: SyncBits,
offset: usize,
checksum: Crc32,
}
@@ -116,6 +117,7 @@ pub struct Reassembler<R: Role> {
impl<R: Role> Reassembler<R> {
pub fn new(input: &[u8], buffer: &mut [u8]) -> Result<Self> {
let (header, after_header) = Header::parse(input)?;
+ let sync_bits = SyncBits::try_from(input)?;
if header.is_continuation() {
return Err(Error::malformed_data());
}
@@ -134,6 +136,7 @@ impl<R: Role> Reassembler<R> {
Ok(Self {
header,
+ sync_bits,
offset: nbytes,
checksum,
})
@@ -200,19 +203,12 @@ impl<R: Role> Reassembler<R> {
&self.header
}
- // Shortcut to deserialize single packet message.
- pub fn single<'a>(buffer: &[u8], dest: &'a mut [u8]) -> Result<(Header<R>, &'a [u8])> {
- let reassembler = Self::new(buffer, dest)?;
- if !reassembler.is_done() {
- log::error!("Single packet message expected.");
- return Err(Error::malformed_data());
- }
- let reply_len = reassembler.verify(dest)?;
- let header = reassembler.header;
- Ok((header, &dest[..reply_len]))
+ pub fn sync_bits(&self) -> SyncBits {
+ self.sync_bits
}
- pub fn single_inplace(buffer: &[u8]) -> Result<(Header<R>, &[u8])> {
+ // Shortcut to deserialize single packet message.
+ pub fn single(buffer: &[u8]) -> Result<(Header<R>, &[u8])> {
let (header, after_header) = Header::parse(buffer)?;
if header.is_continuation() {
return Err(Error::malformed_data());
@@ -318,7 +314,7 @@ mod test {
let packets = fragment(header, SyncBits::new(), source, packet_size);
assert_eq!(packets.len(), 1);
- let res = Reassembler::<Device>::single_inplace(&packets[0]).unwrap();
+ let res = Reassembler::<Device>::single(&packets[0]).unwrap();
let expected_hex = hex::encode(source);
let received_hex = hex::encode(res.1);
assert_eq!(received_hex, expected_hex);
@@ -328,15 +324,15 @@ mod test {
#[test]
fn test_reassemble_single_good() {
let empty = hex::decode(EMPTY_PAYLOAD_EXPECTED).unwrap();
- let res = Reassembler::<Device>::single_inplace(&empty).unwrap();
+ let res = Reassembler::<Device>::single(&empty).unwrap();
assert_eq!(res.1, b"");
- let res = Reassembler::<Host>::single_inplace(&empty).unwrap();
+ let res = Reassembler::<Host>::single(&empty).unwrap();
assert_eq!(res.1, b"");
let short = hex::decode(SHORT_PAYLOAD_EXPECTED).unwrap();
- let res = Reassembler::<Device>::single_inplace(&short).unwrap();
+ let res = Reassembler::<Device>::single(&short).unwrap();
assert_eq!(res.1, b"\x07");
- let res = Reassembler::<Host>::single_inplace(&short).unwrap();
+ let res = Reassembler::<Host>::single(&short).unwrap();
assert_eq!(res.1, b"\x07");
}
@@ -344,16 +340,16 @@ mod test {
fn test_reassemble_single_bad() {
// failure expected when more data follows
let incomplete = hex::decode(LONGER_PAYLOAD_EXPECTED[0]).unwrap();
- let res = Reassembler::<Device>::single_inplace(&incomplete);
+ let res = Reassembler::<Device>::single(&incomplete);
assert_eq!(res, Err(Error::MalformedData));
- let res = Reassembler::<Host>::single_inplace(&incomplete);
+ let res = Reassembler::<Host>::single(&incomplete);
assert_eq!(res, Err(Error::MalformedData));
// failure expected on continuations
let continuation = hex::decode(LONGER_PAYLOAD_EXPECTED[1]).unwrap();
- let res = Reassembler::<Device>::single_inplace(&continuation);
+ let res = Reassembler::<Device>::single(&continuation);
assert_eq!(res, Err(Error::MalformedData));
- let res = Reassembler::<Host>::single_inplace(&continuation);
+ let res = Reassembler::<Host>::single(&continuation);
assert_eq!(res, Err(Error::MalformedData));
}
@@ -458,7 +454,7 @@ mod test {
let reassembler = Reassembler::<Device>::new(packet, &mut received);
assert!(matches!(reassembler, Err(Error::MalformedData)));
- let inplace = Reassembler::<Device>::single_inplace(packet);
+ let inplace = Reassembler::<Device>::single(packet);
assert!(matches!(inplace, Err(Error::MalformedData)));
}
}
Why this scored 29/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.