From 7a1593afaaedb7815d7829ad5a8436a99b9e9eaf Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Sun, 12 Jan 2020 01:46:38 +0100 Subject: [PATCH] Use serde-indexed. Remove hprintln --- .gitignore | 1 + Cargo.toml | 15 +++-- src/authenticator.rs | 5 ++ src/insecure.rs | 4 +- src/pipe.rs | 99 ++++++++++++++-------------- src/types.rs | 32 ++++----- src/types/ctap1.rs | 151 ++++++++++++++++++++++++++++++++----------- 7 files changed, 195 insertions(+), 112 deletions(-) diff --git a/.gitignore b/.gitignore index 6936990..62a67fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk Cargo.lock +generate-certificate.py diff --git a/Cargo.toml b/Cargo.toml index 7a11195..2e4d266 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,15 +19,18 @@ salty = { version = "0.1.0-alpha.0", optional = true } sha2 = { version = "0.8.0", default-features = false, optional = true } ufmt = { version = "0.1.0", optional = true} -[dependencies.serde_cbor] -# path = "../serde_cbor" -git = "https://github.com/nickray/serde-cbor" -branch = "nickray-configurable-packing" -default-features = false +serde_cbor = { version = "0.11.0", default-features = false } +serde-indexed = { git = "https://github.com/nickray/serde-indexed", branch = "main" } +# serde-indexed = { path = "../serde-indexed" } +# [dependencies.serde_cbor] +# # path = "../serde_cbor" +# git = "https://github.com/nickray/serde-cbor" +# branch = "nickray-configurable-packing" +# default-features = false [features] # logging = ["cortex-m-funnel", "ufmt"] -insecure-ram-authenticator = ["nisty", "salty/tweetnacl", "sha2"] #"littlefs2"] +insecure-ram-authenticator = ["nisty", "salty/haase", "sha2"] #"littlefs2"] # issue: this dependency of bindgen triggers non-`no_std`... # [patch.crates-io] diff --git a/src/authenticator.rs b/src/authenticator.rs index f4e0d97..79364bc 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -18,6 +18,11 @@ use crate::types::{ MakeCredentialParameters, }; +// trait SimpleFuture { +// type Output; +// fn poll(&mut self, wake: fn()) -> Poll; +// } + /// an authenticator implements this `authenticator::Api`. /// TODO: modify interface so authenticator can process requests asynchronously. /// Maybe with core::future::Future? diff --git a/src/insecure.rs b/src/insecure.rs index e71716a..66702a1 100644 --- a/src/insecure.rs +++ b/src/insecure.rs @@ -345,11 +345,11 @@ impl authenticator::Api for InsecureRamAuthenticator { // let credential_public_key = if credential_inner.alg == -8 { let keypair = if credential_inner.alg == -8 { // Ed25519 - hprintln!("logging in with 25519").ok(); + // hprintln!("logging in with 25519").ok(); Keypair::Ed25519(salty::Keypair::from(&credential_inner.seed.as_ref().try_into().unwrap())) } else { // NIST P-256 - hprintln!("logging in with NIST").ok(); + // hprintln!("logging in with NIST").ok(); let seed_array: [u8; 32] = credential_inner.seed.as_ref().try_into().unwrap(); Keypair::P256(nisty::Keypair::generate_patiently(&seed_array)) }; diff --git a/src/pipe.rs b/src/pipe.rs index 20491c1..c595622 100644 --- a/src/pipe.rs +++ b/src/pipe.rs @@ -353,7 +353,7 @@ where // HID report descriptors) and although all bytes may not be needed in a // particular packet, the full size always has to be sent. // Unused bytes SHOULD be set to zero." - hprintln!("OK but size {}", size).ok(); + // hprintln!("OK but size {}", size).ok(); return; }, // usb-device lists WouldBlock or BufferOverflow as possible errors. @@ -361,7 +361,7 @@ where // Err(UsbError::WouldBlock) => { return; }, // Err(UsbError::BufferOverflow) => { return; }, Err(error) => { - hprintln!("error no {}", error as i32).ok(); + // hprintln!("error no {}", error as i32).ok(); return; }, }; @@ -403,7 +403,7 @@ where // TODO: add some checks that request.length is OK. // e.g., CTAPHID_INIT should have payload of length 8. - hprintln!("receiving message of length {}", length).ok(); + // hprintln!("receiving message of length {}", length).ok(); if length > PACKET_SIZE as u16 - 7 { // store received part of payload, // prepare for continuation packets @@ -411,7 +411,7 @@ where .copy_from_slice(&packet[7..]); self.state = State::Receiving((request, { let state = MessageState::default(); - hprintln!("got {} so far", state.transmitted).ok(); + // hprintln!("got {} so far", state.transmitted).ok(); state })); // we're done... wait for next packet @@ -429,17 +429,17 @@ where match self.state { State::Receiving((request, mut message_state)) => { let sequence = packet[4]; - hprintln!("receiving continuation packet {}", sequence).ok(); + // hprintln!("receiving continuation packet {}", sequence).ok(); if sequence != message_state.next_sequence { // error handling? - hprintln!("wrong sequence for continuation packet, expected {} received {}", - message_state.next_sequence, sequence).ok(); + // hprintln!("wrong sequence for continuation packet, expected {} received {}", + // message_state.next_sequence, sequence).ok(); return; } if channel != request.channel { // error handling? - hprintln!("wrong channel for continuation packet, expected {} received {}", - request.channel, channel).ok(); + // hprintln!("wrong channel for continuation packet, expected {} received {}", + // request.channel, channel).ok(); return; } @@ -477,7 +477,7 @@ where // dispatch request further match request.command { Command::Init => { - hprintln!("command INIT!").ok(); + // hprintln!("command INIT!").ok(); // hprintln!("data: {:?}", &self.buffer[..request.length as usize]).ok(); match request.channel { // broadcast channel ID - request for assignment @@ -525,14 +525,14 @@ where }, Command::Ping => { - hprintln!("received PING!").ok(); + // hprintln!("received PING!").ok(); // hprintln!("data: {:?}", &self.buffer[..request.length as usize]).ok(); let response = Response::from_request_and_size(request, request.length as usize); self.start_sending(response); }, Command::Wink => { - hprintln!("received WINK!").ok(); + // hprintln!("received WINK!").ok(); // TODO: request.length should be zero // TODO: callback "app" let response = Response::from_request_and_size(request, 1); @@ -540,18 +540,18 @@ where }, Command::Cbor => { - hprintln!("command CBOR!").ok(); + // hprintln!("command CBOR!").ok(); self.handle_cbor(request); }, Command::Msg => { - hprintln!("command MSG!").ok(); + // hprintln!("command MSG!").ok(); self.handle_msg(request); }, // TODO: handle other requests _ => { - hprintln!("unknown command {:?}", request.command).ok(); + // hprintln!("unknown command {:?}", request.command).ok(); }, } } @@ -566,7 +566,7 @@ where let command = ctap1::Command::try_from(&self.buffer[..request.length as usize]); match command { Err(error) => { - hprintln!("ERROR").ok(); + // hprintln!("ERROR").ok(); self.buffer[..2].copy_from_slice(&(error as u16).to_be_bytes()); let response = Response::from_request_and_size(request, 2); self.start_sending(response); @@ -574,24 +574,24 @@ where Ok(command) => { match command { ctap1::Command::Version => { - hprintln!("U2F_VERSION").ok(); + // hprintln!("U2F_VERSION").ok(); // GetVersion // self.buffer[0] = 0; self.buffer[..6].copy_from_slice(b"U2F_V2"); // self.buffer[6..][..2].copy_from_slice(ctap1::NoError::to_be_bytes()); self.buffer[6..][..2].copy_from_slice(&(ctap1::NO_ERROR).to_be_bytes()); let response = Response::from_request_and_size(request, 8); - hprintln!("sending response: {:x?}", &self.buffer[..response.length as usize]).ok(); + // hprintln!("sending response: {:x?}", &self.buffer[..response.length as usize]).ok(); self.start_sending(response); }, ctap1::Command::Register(register) => { - hprintln!("command {:?}", ®ister).ok(); + // hprintln!("command {:?}", ®ister).ok(); self.buffer[..2].copy_from_slice(&(ctap1::Error::InsNotSupported as u16).to_be_bytes()); let response = Response::from_request_and_size(request, 1); self.start_sending(response); }, ctap1::Command::Authenticate(authenticate) => { - hprintln!("command {:?}", &authenticate).ok(); + // hprintln!("command {:?}", &authenticate).ok(); self.buffer[..2].copy_from_slice(&(ctap1::Error::InsNotSupported as u16).to_be_bytes()); let response = Response::from_request_and_size(request, 1); self.start_sending(response); @@ -611,24 +611,23 @@ where let operation = match Operation::try_from(data[0]) { Ok(operation) => { - hprintln!("Operation {:?}", &operation).ok(); + // hprintln!("Operation {:?}", &operation).ok(); operation }, Err(_) => { - hprintln!("Unknown operation code {:x?}", data[0]).ok(); + // hprintln!("Unknown operation code {:x?}", data[0]).ok(); return; }, }; match operation { Operation::GetAssertion => { - hprintln!("received authenticatorGetAssertion").ok(); + // hprintln!("received authenticatorGetAssertion").ok(); // hprintln!("with data: {:?}", &self.buffer[1..request.length as usize]).ok(); let buffer_backup = self.buffer.clone(); - let mut deserializer = serde_cbor::de::Deserializer::from_mut_slice(&mut self.buffer[1..]) - .packed_starts_with(1); + let mut deserializer = serde_cbor::de::Deserializer::from_mut_slice(&mut self.buffer[1..]); // let params: GetAssertionParameters = // serde::de::Deserialize::deserialize(&mut deserializer).unwrap(); @@ -636,8 +635,8 @@ where let params: GetAssertionParameters = match serde::de::Deserialize::deserialize(&mut deserializer) { Ok(params) => params, Err(error) => { - hprintln!("error decoding GetAssertionParameters: {:?}", error).ok(); - hprintln!("from data: {:?}", &buffer_backup[1..request.length as usize]).ok(); + // hprintln!("error decoding GetAssertionParameters: {:?}", error).ok(); + // hprintln!("from data: {:?}", &buffer_backup[1..request.length as usize]).ok(); self.buffer[0] = AuthenticatorError::InvalidCbor as u8; let response = Response::from_request_and_size(request, 1); self.start_sending(response); @@ -657,9 +656,9 @@ where let writer = serde_cbor::ser::SliceWrite::new(&mut self.buffer[1..]); let mut ser = serde_cbor::Serializer::new(writer) - .packed_format() - .pack_starting_with(1) - .pack_to_depth(2) + // .packed_format() + // .pack_starting_with(1) + // .pack_to_depth(2) ; let attestation_object = &assertion_responses[0]; @@ -678,16 +677,16 @@ where }, Operation::MakeCredential => { - hprintln!("received authenticatorMakeCredential").ok(); + // hprintln!("received authenticatorMakeCredential").ok(); let buffer_backup = self.buffer.clone(); - let mut deserializer = serde_cbor::de::Deserializer::from_mut_slice(&mut self.buffer[1..]) - .packed_starts_with(1); + let mut deserializer = serde_cbor::de::Deserializer::from_mut_slice(&mut self.buffer[1..]); + // .packed_starts_with(1); let params: MakeCredentialParameters = match serde::de::Deserialize::deserialize(&mut deserializer) { Ok(params) => params, Err(error) => { - hprintln!("error decoding MakeCredentialParameters: {:?}", error).ok(); - hprintln!("from data: {:?}", &buffer_backup[1..request.length as usize]).ok(); + // hprintln!("error decoding MakeCredentialParameters: {:?}", error).ok(); + // hprintln!("from data: {:?}", &buffer_backup[1..request.length as usize]).ok(); self.buffer[0] = AuthenticatorError::InvalidCbor as u8; let response = Response::from_request_and_size(request, 1); self.start_sending(response); @@ -708,9 +707,9 @@ where let writer = serde_cbor::ser::SliceWrite::new(&mut self.buffer[1..]); let mut ser = serde_cbor::Serializer::new(writer) - .packed_format() - .pack_starting_with(1) - .pack_to_depth(1) + // .packed_format() + // .pack_starting_with(1) + // .pack_to_depth(1) ; attestation_object.serialize(&mut ser).unwrap(); @@ -728,7 +727,7 @@ where }, Operation::GetInfo => { - hprintln!("received authenticatorGetInfo").ok(); + // hprintln!("received authenticatorGetInfo").ok(); let authenticator_info = self.authenticator.get_info(); // hprintln!("authenticator_info = {:?}", &authenticator_info).ok(); @@ -738,9 +737,9 @@ where // actual payload let writer = serde_cbor::ser::SliceWrite::new(&mut self.buffer[1..]); let mut ser = serde_cbor::Serializer::new(writer) - .packed_format() - .pack_starting_with(1) - .pack_to_depth(1) + // .packed_format() + // .pack_starting_with(1) + // .pack_to_depth(1) ; // hprintln!("returning info {:?}", &authenticator_info).ok(); @@ -766,7 +765,7 @@ where }, Operation::Reset => { - hprintln!("received authenticatorReset").ok(); + // hprintln!("received authenticatorReset").ok(); match self.authenticator.reset() { Ok(_) => { self.buffer[0] = 0; }, Err(error) => { self.buffer[0] = error as u8; }, @@ -819,7 +818,7 @@ where // this shouldn't happen probably }, Err(_) => { - hprintln!("weird USB errrorrr").ok(); + // hprintln!("weird USB errrorrr").ok(); panic!("unexpected error writing packet!"); }, Ok(PACKET_SIZE) => { @@ -827,7 +826,7 @@ where if fits_in_one_packet { self.state = State::Idle; // hprintln!("StartSent {} bytes, idle again", response.length).ok(); - hprintln!("IDLE again").ok(); + // hprintln!("IDLE again").ok(); } else { self.state = State::Sending((response, MessageState::default())); // hprintln!( @@ -837,7 +836,7 @@ where } }, Ok(_) => { - hprintln!("short write").ok(); + // hprintln!("short write").ok(); panic!("unexpected size writing packet!"); }, }; @@ -869,18 +868,18 @@ where Err(UsbError::WouldBlock) => { // fine, can't write try later // this shouldn't happen probably - hprintln!("can't send seq {}, write endpoint busy", - message_state.next_sequence).ok(); + // hprintln!("can't send seq {}, write endpoint busy", + // message_state.next_sequence).ok(); }, Err(_) => { - hprintln!("weird USB error").ok(); + // hprintln!("weird USB error").ok(); panic!("unexpected error writing packet!"); }, Ok(PACKET_SIZE) => { // goodie, this worked if last_packet { self.state = State::Idle; - hprintln!("in IDLE state after {:?}", &message_state).ok(); + // hprintln!("in IDLE state after {:?}", &message_state).ok(); } else { message_state.absorb_packet(); // DANGER! destructuring in the match arm copies out diff --git a/src/types.rs b/src/types.rs index 60f4c95..5a606ca 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,6 @@ pub use heapless::{consts, ArrayLength, String, Vec}; use serde::{Deserialize, Serialize}; +use serde_indexed::{DeserializeIndexed, SerializeIndexed}; use crate::{ bytes::Bytes, @@ -108,8 +109,9 @@ pub struct AuthenticatorOptions { pub uv: Option, } -#[derive(Clone,Debug,Eq,PartialEq,Serialize,Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone,Debug,Eq,PartialEq,SerializeIndexed,DeserializeIndexed)] +// #[serde(rename_all = "camelCase")] +#[serde_indexed(offset = 1)] pub struct GetAssertionParameters { pub rp_id: String, pub client_data_hash: Bytes, @@ -124,8 +126,9 @@ pub struct GetAssertionParameters { pub pin_protocol: Option, } -#[derive(Clone,Debug,Eq,PartialEq,Serialize,Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone,Debug,Eq,PartialEq,SerializeIndexed,DeserializeIndexed)] +// #[serde(rename_all = "camelCase")] +#[serde_indexed(offset = 1)] pub struct MakeCredentialParameters { pub client_data_hash: Bytes, pub rp: PublicKeyCredentialRpEntity, @@ -248,8 +251,8 @@ impl AuthenticatorData { // NB: attn object definition / order at end of // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential // does not coincide with what python-fido2 expects in AttestationObject.__init__ *at all* :'-) -#[derive(Clone,Debug,Eq,PartialEq,Serialize,Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone,Debug,Eq,PartialEq,SerializeIndexed,DeserializeIndexed)] +#[serde_indexed(offset = 1)] pub struct AssertionResponse { #[serde(skip_serializing_if = "Option::is_none")] pub credential: Option, @@ -278,8 +281,8 @@ pub enum AttestationStatement { Packed(PackedAttestationStatement), } -#[derive(Clone,Debug,Eq,PartialEq,Serialize/*,Deserialize*/)] -#[serde(rename_all = "camelCase")] +#[derive(Clone,Debug,Eq,PartialEq,SerializeIndexed)] +#[serde_indexed(offset = 1)] pub struct AttestationObject { pub fmt: String, pub auth_data: Bytes, @@ -289,7 +292,8 @@ pub struct AttestationObject { pub type AssertionResponses = Vec; -#[derive(Clone,Debug,Eq,PartialEq,Serialize,Deserialize)] +#[derive(Clone,Debug,Eq,PartialEq,SerializeIndexed,DeserializeIndexed)] +#[serde_indexed(offset = 1)] pub struct AuthenticatorInfo { pub(crate) versions: Vec, consts::U2>, @@ -389,11 +393,8 @@ mod tests { fn test_serialize() { let mut buffer = [0u8; 64]; let writer = serde_cbor::ser::SliceWrite::new(&mut buffer); - let mut ser = serde_cbor::Serializer::new(writer) - .packed_format() - .pack_starting_with(1) - .pack_to_depth(1) - ; + let mut ser = serde_cbor::Serializer::new(writer); + let mut cdh = Vec::::new(); cdh.extend_from_slice(b"1234567890ABCDEF").unwrap(); Bytes::from(cdh).serialize(&mut ser).unwrap(); @@ -448,7 +449,6 @@ mod tests { use serde::de; let mut deserializer = serde_cbor::de::Deserializer::from_mut_slice(&mut buffer[..size]); - //.packed_starts_with(1); let _deser: Vec = de::Deserialize::deserialize(&mut deserializer).unwrap(); } @@ -478,7 +478,7 @@ mod tests { ]; use serde::de; - let mut deserializer = serde_cbor::de::Deserializer::from_mut_slice(&mut buffer).packed_starts_with(1); + let mut deserializer = serde_cbor::de::Deserializer::from_mut_slice(&mut buffer); let _make_cred_params: MakeCredentialParameters = de::Deserialize::deserialize(&mut deserializer).unwrap(); // let make_cred_params: MakeCredentialParameters = diff --git a/src/types/ctap1.rs b/src/types/ctap1.rs index 9faf13a..79cbb2f 100644 --- a/src/types/ctap1.rs +++ b/src/types/ctap1.rs @@ -4,7 +4,19 @@ use core::convert::TryInto; use cortex_m_semihosting::hprintln; -use crate::bytes::{Bytes, consts}; +use crate::{ + bytes::{Bytes, consts}, + types::{ + AuthenticatorOptions, + MakeCredentialParameters, + // GetAssertionParameters, + // PublicKeyCredentialDescriptor, + PublicKeyCredentialParameters, + PublicKeyCredentialRpEntity, + PublicKeyCredentialUserEntity, + }, +}; +pub use heapless::{String, Vec}; // pub struct WrongData; @@ -23,6 +35,11 @@ pub enum Error { #[repr(u8)] #[derive(Copy,Clone,Debug,Eq,PartialEq)] pub enum ControlByte { + // Conor: + // I think U2F check-only maps to FIDO2 MakeCredential with the credID in the excludeList, + // and pinAuth="" so the request will fail before UP check. + // I think this is what the windows hello API does to silently check if a credential is + // on an authenticator CheckOnly = 0x07, EnforceUserPresenceAndSign = 0x03, DontEnforceUserPresenceAndSign = 0x08, @@ -64,6 +81,56 @@ pub struct Register { max_response: usize, } +impl From for AuthenticatorOptions { + fn from(control_byte: ControlByte) -> Self { + AuthenticatorOptions { + rk: Some(false), + up: Some(match control_byte { + ControlByte::CheckOnly => false, + ControlByte::EnforceUserPresenceAndSign => true, + ControlByte::DontEnforceUserPresenceAndSign => false, + }), + // safety hole? + uv: Some(false), + } + } +} + +// impl From for MakeCredentialParameters { +// fn from(register: Register) -> Self { +// let pub_key_cred_params = Vec::new(); +// let key_type = String::new(); +// key_type.push_str("public-key").unwrap(); +// pub_key_cred_params.push(PublicKeyCredentialParameters { +// alg: -7, +// key_type, +// }); + +// MakeCredentialParameters { +// client_data_hash: register.client_data_hash, +// // uff +// rp: { +// let id = String::new(); +// id.push_ +// PublicKeyCredentialRpEntity { +// id: String::from(register.app_id_hash), +// name: None, url: None, +// } +// }, +// user: PublicKeyCredentialUserEntity { +// id: Bytes::new(), +// icon: None, name: None, display_name: None, +// }, +// pub_key_cred_params, +// exclude_list: None, +// extensions: None, +// options: None, +// pin_auth: None, +// pin_protocol: None, +// } +// } +// } + #[derive(Clone,Debug,Eq,PartialEq)] pub struct Authenticate { control_byte: ControlByte, @@ -81,53 +148,61 @@ pub enum Command { } // U2FHID uses extended length encoding -fn parse_apdu_data(mut partial: &[u8]) -> Result<(&[u8], usize)> { - match partial.len() { +fn parse_apdu_data(mut remaining: &[u8]) -> Result<(&[u8], usize)> { + match remaining.len() { // Lc = Le = 0 0 => Ok((&[], 0)), + // non-zero first byte would indicate short encoding, + // but U2FHID is always extended length encoding. + // extended length uses (0,upper byte,lower byte) for + // lengths; u16_be for the extended lengths, the leading + // zero to distinguish from short encoding. + // -> lengths 1 and 2 can't occur 1 => Err(Error::WrongLength), 2 => Err(Error::WrongLength), - 3 => { - // Lc = 0, skipped - let nearly_le = u16::from_be_bytes(partial[1..].try_into().unwrap()); - Ok((&[], match nearly_le { - 0 => 65_536usize, - non_zero => non_zero as usize, - })) - }, _ => { - // first byte is zero - // if partial[0] != 0 ==> error - hprintln!("case l, partial[..4] = {:?}", &partial[..4]).ok(); - partial = &partial[1..]; - - // next two bytes are Lc, followed by request of length Lc, the possibly Le - let lc = u16::from_be_bytes(partial[..2].try_into().unwrap()) as usize; - hprintln!("lc = {}", lc).ok(); - partial = &partial[2..]; - - // request - if partial.len() < lc { - return Err(Error::WrongLength); + if remaining[0] != 0 { + return Err(Error::WrongData); } - let request = &partial[..lc]; - - // now for expected length - partial = &partial[lc..]; - match partial.len() { - 0 => Ok((request, 0)), - 2 => { - let nearly_le = u16::from_be_bytes(partial.try_into().unwrap()); - Ok((request, match nearly_le { - 0 => 65_536usize, - non_zero => non_zero as usize, - })) + remaining = &remaining[1..]; + + let request_length = { + let first_length = u16::from_be_bytes(remaining[..2].try_into().unwrap()) as usize; + remaining = &remaining[2..]; + if remaining.len() == 0 { + let expected = match first_length { + 0 => u16::max_value() as usize + 1, + non_zero => non_zero, + }; + return Ok((&[], expected)); } - _ => Err(Error::WrongLength), + first_length + }; + + if remaining.len() < request_length { + return Err(Error::WrongLength); + } + let request = &remaining[..request_length]; + + remaining = &remaining[request_length..]; + if remaining.len() == 0 { + return Ok((request, 0)); + } + // since Lc is given, Le has no leading zero. + // single byte would again be short encoding + if remaining.len() == 1 { + return Err(Error::WrongData); + } + if remaining.len() > 2 { + return Err(Error::WrongLength); } + let expected = match u16::from_be_bytes(remaining.try_into().unwrap()) as usize { + 0 => u16::max_value() as usize + 1, + non_zero => non_zero, + }; + Ok((request, expected)) }, } - // (0, 0, partial) } // TODO: From for ...