diff --git a/fuzz/fuzz_targets/packet.rs b/fuzz/fuzz_targets/packet.rs index 91a9af4223..f887f7b6f0 100644 --- a/fuzz/fuzz_targets/packet.rs +++ b/fuzz/fuzz_targets/packet.rs @@ -14,7 +14,7 @@ fuzz_target!(|data: &[u8]| { neqo_crypto::init().unwrap(); // Run the fuzzer - _ = PublicPacket::decode(data, decoder); + _ = PublicPacket::decode(&mut data.to_vec(), decoder); }); #[cfg(any(not(fuzzing), windows))] diff --git a/neqo-bin/src/client/http09.rs b/neqo-bin/src/client/http09.rs index 2d95b1dda6..c43f1f404e 100644 --- a/neqo-bin/src/client/http09.rs +++ b/neqo-bin/src/client/http09.rs @@ -195,7 +195,7 @@ impl super::Client for Connection { fn process_multiple_input<'a>( &mut self, - dgrams: impl IntoIterator>, + dgrams: impl IntoIterator>, now: Instant, ) { self.process_multiple_input(dgrams, now); diff --git a/neqo-bin/src/client/http3.rs b/neqo-bin/src/client/http3.rs index d1f6ac10cc..58f897b486 100644 --- a/neqo-bin/src/client/http3.rs +++ b/neqo-bin/src/client/http3.rs @@ -139,7 +139,7 @@ impl super::Client for Http3Client { fn process_multiple_input<'a>( &mut self, - dgrams: impl IntoIterator>, + dgrams: impl IntoIterator>, now: Instant, ) { self.process_multiple_input(dgrams, now); diff --git a/neqo-bin/src/client/mod.rs b/neqo-bin/src/client/mod.rs index 1f929d5d9b..4e038474c9 100644 --- a/neqo-bin/src/client/mod.rs +++ b/neqo-bin/src/client/mod.rs @@ -384,7 +384,7 @@ trait Client { fn process_output(&mut self, now: Instant) -> Output; fn process_multiple_input<'a>( &mut self, - dgrams: impl IntoIterator>, + dgrams: impl IntoIterator>, now: Instant, ); fn has_events(&self) -> bool; diff --git a/neqo-bin/src/server/http09.rs b/neqo-bin/src/server/http09.rs index 600c87a7c2..577a3f0cde 100644 --- a/neqo-bin/src/server/http09.rs +++ b/neqo-bin/src/server/http09.rs @@ -187,7 +187,7 @@ impl HttpServer { } impl super::HttpServer for HttpServer { - fn process(&mut self, dgram: Option>, now: Instant) -> Output { + fn process(&mut self, dgram: Option>, now: Instant) -> Output { self.server.process(dgram, now) } diff --git a/neqo-bin/src/server/http3.rs b/neqo-bin/src/server/http3.rs index c3e7d561b2..7f65d2802f 100644 --- a/neqo-bin/src/server/http3.rs +++ b/neqo-bin/src/server/http3.rs @@ -82,7 +82,7 @@ impl Display for HttpServer { } impl super::HttpServer for HttpServer { - fn process(&mut self, dgram: Option>, now: Instant) -> neqo_http3::Output { + fn process(&mut self, dgram: Option>, now: Instant) -> neqo_http3::Output { self.server.process(dgram, now) } diff --git a/neqo-bin/src/server/mod.rs b/neqo-bin/src/server/mod.rs index 2ff026ffae..bab89a5955 100644 --- a/neqo-bin/src/server/mod.rs +++ b/neqo-bin/src/server/mod.rs @@ -193,7 +193,7 @@ fn qns_read_response(filename: &str) -> Result, io::Error> { #[allow(clippy::module_name_repetitions)] pub trait HttpServer: Display { - fn process(&mut self, dgram: Option>, now: Instant) -> Output; + fn process(&mut self, dgram: Option>, now: Instant) -> Output; fn process_events(&mut self, now: Instant); fn has_events(&self) -> bool; } @@ -243,7 +243,7 @@ impl ServerRunner { timeout: &mut Option>>, sockets: &mut [(SocketAddr, crate::udp::Socket)], now: &dyn Fn() -> Instant, - mut input_dgram: Option>, + mut input_dgram: Option>, ) -> Result<(), io::Error> { loop { match server.process(input_dgram.take(), now()) { diff --git a/neqo-common/src/datagram.rs b/neqo-common/src/datagram.rs index c3b8713c69..e671d70382 100644 --- a/neqo-common/src/datagram.rs +++ b/neqo-common/src/datagram.rs @@ -4,7 +4,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::{net::SocketAddr, ops::Deref}; +use std::{ + net::SocketAddr, + ops::{Deref, DerefMut}, +}; use crate::{hex_with_len, IpTos}; @@ -47,7 +50,6 @@ impl> Datagram { } } -#[cfg(test)] impl + AsRef<[u8]>> AsMut<[u8]> for Datagram { fn as_mut(&mut self) -> &mut [u8] { self.d.as_mut() @@ -65,6 +67,12 @@ impl Datagram> { } } +impl + AsMut<[u8]>> DerefMut for Datagram { + fn deref_mut(&mut self) -> &mut Self::Target { + AsMut::<[u8]>::as_mut(self) + } +} + impl> Deref for Datagram { type Target = [u8]; #[must_use] @@ -86,9 +94,9 @@ impl> std::fmt::Debug for Datagram { } } -impl<'a> Datagram<&'a [u8]> { +impl<'a> Datagram<&'a mut [u8]> { #[must_use] - pub const fn from_slice(src: SocketAddr, dst: SocketAddr, tos: IpTos, d: &'a [u8]) -> Self { + pub fn from_slice(src: SocketAddr, dst: SocketAddr, tos: IpTos, d: &'a mut [u8]) -> Self { Self { src, dst, tos, d } } diff --git a/neqo-crypto/src/aead.rs b/neqo-crypto/src/aead.rs index 059e9acb61..7fed060f75 100644 --- a/neqo-crypto/src/aead.rs +++ b/neqo-crypto/src/aead.rs @@ -123,14 +123,45 @@ impl RealAead { c_uint::try_from(output.len())?, ) }?; - Ok(&output[0..(l.try_into()?)]) + Ok(&output[..l.try_into()?]) } - /// Decrypt a ciphertext. + /// Encrypt `data` consisting of `aad` and plaintext `data` in place. + /// + /// The last `Aead::expansion` of `data` is overwritten by the AEAD tag by this function. + /// Therefore, a buffer should be provided that is that much larger than the plaintext. + /// + /// # Panics + /// + /// If `data` is shorter than `::expansion()`. + /// + /// # Errors /// - /// Note that NSS insists upon having extra space available for decryption, so - /// the buffer for `output` should be the same length as `input`, even though - /// the final result will be shorter. + /// If the input can't be protected or any input is too large for NSS. + pub fn encrypt_in_place<'a>( + &self, + count: u64, + aad: &[u8], + data: &'a mut [u8], + ) -> Res<&'a mut [u8]> { + let mut l: c_uint = 0; + unsafe { + SSL_AeadEncrypt( + *self.ctx, + count, + aad.as_ptr(), + c_uint::try_from(aad.len())?, + data.as_ptr(), + c_uint::try_from(data.len() - self.expansion())?, + data.as_ptr(), + &mut l, + c_uint::try_from(data.len())?, + ) + }?; + Ok(&mut data[..l.try_into()?]) + } + + /// Decrypt a ciphertext. /// /// # Errors /// @@ -144,6 +175,9 @@ impl RealAead { ) -> Res<&'a [u8]> { let mut l: c_uint = 0; unsafe { + // Note that NSS insists upon having extra space available for decryption, so + // the buffer for `output` should be the same length as `input`, even though + // the final result will be shorter. SSL_AeadDecrypt( *self.ctx, count, @@ -156,7 +190,41 @@ impl RealAead { c_uint::try_from(output.len())?, ) }?; - Ok(&output[0..(l.try_into()?)]) + Ok(&output[..l.try_into()?]) + } + + /// Decrypt a ciphertext in place. + /// Returns a subslice of `data` (without the last `::expansion()` bytes), + /// that has been decrypted in place. + /// + /// # Errors + /// + /// If the input isn't authenticated or any input is too large for NSS. + pub fn decrypt_in_place<'a>( + &self, + count: u64, + aad: &[u8], + data: &'a mut [u8], + ) -> Res<&'a mut [u8]> { + let mut l: c_uint = 0; + unsafe { + // Note that NSS insists upon having extra space available for decryption, so + // the buffer for `output` should be the same length as `input`, even though + // the final result will be shorter. + SSL_AeadDecrypt( + *self.ctx, + count, + aad.as_ptr(), + c_uint::try_from(aad.len())?, + data.as_ptr(), + c_uint::try_from(data.len())?, + data.as_ptr(), + &mut l, + c_uint::try_from(data.len())?, + ) + }?; + debug_assert_eq!(usize::try_from(l)?, data.len() - self.expansion()); + Ok(&mut data[..l.try_into()?]) } } diff --git a/neqo-crypto/src/aead_null.rs b/neqo-crypto/src/aead_null.rs index a74c89f35d..cbe4834d10 100644 --- a/neqo-crypto/src/aead_null.rs +++ b/neqo-crypto/src/aead_null.rs @@ -42,23 +42,31 @@ impl AeadNull { ) -> Res<&'a [u8]> { let l = input.len(); output[..l].copy_from_slice(input); - output[l..l + 16].copy_from_slice(AEAD_NULL_TAG); - Ok(&output[..l + 16]) + output[l..l + self.expansion()].copy_from_slice(AEAD_NULL_TAG); + Ok(&output[..l + self.expansion()]) } #[allow(clippy::missing_errors_doc)] - pub fn decrypt<'a>( + pub fn encrypt_in_place<'a>( &self, _count: u64, _aad: &[u8], - input: &[u8], - output: &'a mut [u8], - ) -> Res<&'a [u8]> { - if input.len() < AEAD_NULL_TAG.len() { + data: &'a mut [u8], + ) -> Res<&'a mut [u8]> { + let pos = data.len() - self.expansion(); + data[pos..].copy_from_slice(AEAD_NULL_TAG); + Ok(data) + } + + fn decrypt_check(&self, _count: u64, _aad: &[u8], input: &[u8]) -> Res { + if input.len() < self.expansion() { return Err(Error::from(SEC_ERROR_BAD_DATA)); } - let len_encrypted = input.len() - AEAD_NULL_TAG.len(); + let len_encrypted = input + .len() + .checked_sub(self.expansion()) + .ok_or_else(|| Error::from(SEC_ERROR_BAD_DATA))?; // Check that: // 1) expansion is all zeros and // 2) if the encrypted data is also supplied that at least some values are no zero @@ -66,12 +74,36 @@ impl AeadNull { if &input[len_encrypted..] == AEAD_NULL_TAG && (len_encrypted == 0 || input[..len_encrypted].iter().any(|x| *x != 0x0)) { - output[..len_encrypted].copy_from_slice(&input[..len_encrypted]); - Ok(&output[..len_encrypted]) + Ok(len_encrypted) } else { Err(Error::from(SEC_ERROR_BAD_DATA)) } } + + #[allow(clippy::missing_errors_doc)] + pub fn decrypt<'a>( + &self, + count: u64, + aad: &[u8], + input: &[u8], + output: &'a mut [u8], + ) -> Res<&'a [u8]> { + self.decrypt_check(count, aad, input).map(|len| { + output[..len].copy_from_slice(&input[..len]); + &output[..len] + }) + } + + #[allow(clippy::missing_errors_doc)] + pub fn decrypt_in_place<'a>( + &self, + count: u64, + aad: &[u8], + data: &'a mut [u8], + ) -> Res<&'a mut [u8]> { + self.decrypt_check(count, aad, data) + .map(move |len| &mut data[..len]) + } } impl fmt::Debug for AeadNull { diff --git a/neqo-http3/src/connection_client.rs b/neqo-http3/src/connection_client.rs index 502603b061..7e0959af90 100644 --- a/neqo-http3/src/connection_client.rs +++ b/neqo-http3/src/connection_client.rs @@ -842,7 +842,11 @@ impl Http3Client { } /// This function combines `process_input` and `process_output` function. - pub fn process(&mut self, dgram: Option>>, now: Instant) -> Output { + pub fn process( + &mut self, + dgram: Option + AsMut<[u8]>>>, + now: Instant, + ) -> Output { qtrace!("[{self}] Process"); if let Some(d) = dgram { self.process_input(d, now); @@ -860,13 +864,13 @@ impl Http3Client { /// packets need to be sent or if a timer needs to be updated. /// /// [1]: ../neqo_transport/enum.ConnectionEvent.html - pub fn process_input(&mut self, dgram: Datagram>, now: Instant) { + pub fn process_input(&mut self, dgram: Datagram + AsMut<[u8]>>, now: Instant) { self.process_multiple_input(iter::once(dgram), now); } pub fn process_multiple_input( &mut self, - dgrams: impl IntoIterator>>, + dgrams: impl IntoIterator + AsMut<[u8]>>>, now: Instant, ) { let mut dgrams = dgrams.into_iter().peekable(); diff --git a/neqo-http3/src/server.rs b/neqo-http3/src/server.rs index 4508194f99..53ceed4f3e 100644 --- a/neqo-http3/src/server.rs +++ b/neqo-http3/src/server.rs @@ -117,7 +117,11 @@ impl Http3Server { self.process(None::, now) } - pub fn process(&mut self, dgram: Option>>, now: Instant) -> Output { + pub fn process( + &mut self, + dgram: Option + AsMut<[u8]>>>, + now: Instant, + ) -> Output { qtrace!("[{self}] Process"); let out = self.server.process(dgram, now); self.process_http3(now); diff --git a/neqo-transport/src/connection/mod.rs b/neqo-transport/src/connection/mod.rs index 5531d0513f..c9764c7229 100644 --- a/neqo-transport/src/connection/mod.rs +++ b/neqo-transport/src/connection/mod.rs @@ -22,7 +22,7 @@ use std::{ use neqo_common::{ event::Provider as EventProvider, hex, hex_snip_middle, hrtime, qdebug, qerror, qinfo, - qlog::NeqoQlog, qtrace, qwarn, Datagram, Decoder, Encoder, Role, + qlog::NeqoQlog, qtrace, qwarn, Datagram, Decoder, Encoder, IpTos, Role, }; use neqo_crypto::{ agent::CertificateInfo, Agent, AntiReplay, AuthenticationStatus, Cipher, Client, Group, @@ -1018,14 +1018,14 @@ impl Connection { } /// Process new input datagrams on the connection. - pub fn process_input(&mut self, d: Datagram>, now: Instant) { + pub fn process_input(&mut self, d: Datagram + AsMut<[u8]>>, now: Instant) { self.process_multiple_input(iter::once(d), now); } /// Process new input datagrams on the connection. pub fn process_multiple_input( &mut self, - dgrams: impl IntoIterator>>, + dgrams: impl IntoIterator + AsMut<[u8]>>>, now: Instant, ) { let mut dgrams = dgrams.into_iter().peekable(); @@ -1158,7 +1158,11 @@ impl Connection { /// Process input and generate output. #[must_use = "Output of the process function must be handled"] - pub fn process(&mut self, dgram: Option>>, now: Instant) -> Output { + pub fn process( + &mut self, + dgram: Option + AsMut<[u8]>>>, + now: Instant, + ) -> Output { if let Some(d) = dgram { self.input(d, now, now); self.process_saved(now); @@ -1235,20 +1239,20 @@ impl Connection { } } - fn is_stateless_reset(&self, path: &PathRef, d: &Datagram>) -> bool { + fn is_stateless_reset(&self, path: &PathRef, d: &[u8]) -> bool { // If the datagram is too small, don't try. // If the connection is connected, then the reset token will be invalid. if d.len() < 16 || !self.state.connected() { return false; } - <&[u8; 16]>::try_from(&d.as_ref()[d.len() - 16..]) + <&[u8; 16]>::try_from(&d[d.len() - 16..]) .is_ok_and(|token| path.borrow().is_stateless_reset(token)) } fn check_stateless_reset( &mut self, path: &PathRef, - d: &Datagram>, + d: &[u8], first: bool, now: Instant, ) -> Res<()> { @@ -1499,7 +1503,8 @@ impl Connection { fn postprocess_packet( &mut self, path: &PathRef, - d: &Datagram>, + tos: IpTos, + remote: SocketAddr, packet: &PublicPacket, migrate: bool, now: Instant, @@ -1507,7 +1512,7 @@ impl Connection { let space = PacketNumberSpace::from(packet.packet_type()); if let Some(space) = self.acks.get_mut(space) { let space_ecn_marks = space.ecn_marks(); - *space_ecn_marks += d.tos().into(); + *space_ecn_marks += tos.into(); self.stats.borrow_mut().ecn_rx = *space_ecn_marks; } else { qtrace!("Not tracking ECN for dropped packet number space"); @@ -1518,7 +1523,7 @@ impl Connection { } if self.state.connected() { - self.handle_migration(path, d, migrate, now); + self.handle_migration(path, remote, migrate, now); } else if self.role != Role::Client && (packet.packet_type() == PacketType::Handshake || (packet.dcid().len() >= 8 && packet.dcid() == self.local_initial_source_cid)) @@ -1531,7 +1536,12 @@ impl Connection { /// Take a datagram as input. This reports an error if the packet was bad. /// This takes two times: when the datagram was received, and the current time. - fn input(&mut self, d: Datagram>, received: Instant, now: Instant) { + fn input( + &mut self, + d: Datagram + AsMut<[u8]>>, + received: Instant, + now: Instant, + ) { // First determine the path. let path = self.paths.find_path( d.destination(), @@ -1546,27 +1556,29 @@ impl Connection { _ = self.capture_error(Some(path), now, 0, res); } + #[allow(clippy::too_many_lines)] // Will be addressed as part of https://github.com/mozilla/neqo/pull/2396 fn input_path( &mut self, path: &PathRef, - d: Datagram>, + mut d: Datagram + AsMut<[u8]>>, now: Instant, ) -> Res<()> { - let mut slc = d.as_ref(); - let mut dcid = None; - qtrace!("[{self}] {} input {}", path.borrow(), hex(&d)); + let tos = d.tos(); + let remote = d.source(); + let mut slc = d.as_mut(); + let mut dcid = None; let pto = path.borrow().rtt().pto(self.confirmed()); // Handle each packet in the datagram. while !slc.is_empty() { self.stats.borrow_mut().packets_rx += 1; - let (packet, remainder) = + let slc_len = slc.len(); + let (mut packet, remainder) = match PublicPacket::decode(slc, self.cid_manager.decoder().as_ref()) { Ok((packet, remainder)) => (packet, remainder), Err(e) => { qinfo!("[{self}] Garbage packet: {e}"); - qtrace!("[{self}] Garbage packet contents: {}", hex(slc)); self.stats.borrow_mut().pkt_dropped("Garbage packet"); break; } @@ -1579,17 +1591,18 @@ impl Connection { qtrace!("[{self}] Received unverified packet {packet:?}"); + let packet_len = packet.len(); match packet.decrypt(&mut self.crypto.states, now + pto) { Ok(payload) => { // OK, we have a valid packet. self.idle_timeout.on_packet_received(now); self.log_packet( - packet::MetaData::new_in(path, d.tos(), packet.len(), &payload), + packet::MetaData::new_in(path, tos, packet_len, &payload), now, ); #[cfg(feature = "build-fuzzing-corpus")] - if packet.packet_type() == PacketType::Initial { + if payload.packet_type() == PacketType::Initial { let target = if self.role == Role::Client { "server_initial" } else { @@ -1606,7 +1619,9 @@ impl Connection { } else { match self.process_packet(path, &payload, now) { Ok(migrate) => { - self.postprocess_packet(path, &d, &packet, migrate, now); + self.postprocess_packet( + path, tos, remote, &packet, migrate, now, + ); } Err(e) => { self.ensure_error_path(path, &packet, now); @@ -1627,7 +1642,7 @@ impl Connection { Error::KeysPending(cspace) => { // This packet can't be decrypted because we don't have the keys yet. // Don't check this packet for a stateless reset, just return. - let remaining = slc.len(); + let remaining = slc_len; self.save_datagram(cspace, d, remaining, now); return Ok(()); } @@ -1646,7 +1661,7 @@ impl Connection { // Decryption failure, or not having keys is not fatal. // If the state isn't available, or we can't decrypt the packet, drop // the rest of the datagram on the floor, but don't generate an error. - self.check_stateless_reset(path, &d, dcid.is_none(), now)?; + self.check_stateless_reset(path, packet.data(), dcid.is_none(), now)?; self.stats.borrow_mut().pkt_dropped("Decryption failure"); qlog::packet_dropped(&self.qlog, &packet, now); } @@ -1975,7 +1990,7 @@ impl Connection { fn handle_migration( &mut self, path: &PathRef, - d: &Datagram>, + remote: SocketAddr, migrate: bool, now: Instant, ) { @@ -1988,7 +2003,7 @@ impl Connection { if self.ensure_permanent(path, now).is_ok() { self.paths - .handle_migration(path, d.source(), now, &mut self.stats.borrow_mut()); + .handle_migration(path, remote, now, &mut self.stats.borrow_mut()); } else { qinfo!( "[{self}] {} Peer migrated, but no connection ID available", diff --git a/neqo-transport/src/crypto.rs b/neqo-transport/src/crypto.rs index 062c420259..bb9ffa58d0 100644 --- a/neqo-transport/src/crypto.rs +++ b/neqo-transport/src/crypto.rs @@ -40,7 +40,6 @@ use crate::{ Error, Res, }; -const MAX_AUTH_TAG: usize = 32; /// The number of invocations remaining on a write cipher before we try /// to update keys. This has to be much smaller than the number returned /// by `CryptoDxState::limit` or updates will happen too often. As we don't @@ -636,33 +635,40 @@ impl CryptoDxState { self.used_pn.end } - pub fn encrypt(&mut self, pn: PacketNumber, hdr: &[u8], body: &[u8]) -> Res> { + pub fn encrypt<'a>( + &mut self, + pn: PacketNumber, + hdr: Range, + data: &'a mut [u8], + ) -> Res<&'a mut [u8]> { debug_assert_eq!(self.direction, CryptoDxDirection::Write); qtrace!( - "[{self}] encrypt pn={pn} hdr={} body={}", - hex(hdr), - hex(body) + "[{self}] encrypt_in_place pn={pn} hdr={} body={}", + hex(data[hdr.clone()].as_ref()), + hex(data[hdr.end..].as_ref()) ); // The numbers in `Self::limit` assume a maximum packet size of `LIMIT`. // Adjust them as we encounter larger packets. - debug_assert!(body.len() < 65536); - if body.len() > self.largest_packet_len { + let body_len = data.len() - hdr.len() - self.aead.expansion(); + debug_assert!(body_len <= u16::MAX.into()); + if body_len > self.largest_packet_len { let new_bits = usize::leading_zeros(self.largest_packet_len - 1) - - usize::leading_zeros(body.len() - 1); + - usize::leading_zeros(body_len - 1); self.invocations >>= new_bits; - self.largest_packet_len = body.len(); + self.largest_packet_len = body_len; } self.invoked()?; - let size = body.len() + MAX_AUTH_TAG; - let mut out = vec![0; size]; - let res = self.aead.encrypt(pn, hdr, body, &mut out)?; + let (prev, data) = data.split_at_mut(hdr.end); + // `prev` may have already-encrypted packets this one is being coalesced with. + // Use only the actual current header for AAD. + let data = self.aead.encrypt_in_place(pn, &prev[hdr], data)?; - qtrace!("[{self}] encrypt ct={}", hex(res)); + qtrace!("[{self}] encrypt ct={}", hex(&data)); debug_assert_eq!(pn, self.next_pn()); self.used(pn)?; - Ok(res.to_vec()) + Ok(data) } #[must_use] @@ -670,18 +676,23 @@ impl CryptoDxState { self.aead.expansion() } - pub fn decrypt(&mut self, pn: PacketNumber, hdr: &[u8], body: &[u8]) -> Res> { + pub fn decrypt<'a>( + &mut self, + pn: PacketNumber, + hdr: Range, + data: &'a mut [u8], + ) -> Res<&'a mut [u8]> { debug_assert_eq!(self.direction, CryptoDxDirection::Read); qtrace!( - "[{self}] decrypt pn={pn} hdr={} body={}", - hex(hdr), - hex(body) + "[{self}] decrypt_in_place pn={pn} hdr={} body={}", + hex(data[hdr.clone()].as_ref()), + hex(data[hdr.end..].as_ref()) ); self.invoked()?; - let mut out = vec![0; body.len()]; - let res = self.aead.decrypt(pn, hdr, body, &mut out)?; + let (hdr, data) = data.split_at_mut(hdr.end); + let data = self.aead.decrypt_in_place(pn, hdr, data)?; self.used(pn)?; - Ok(res.to_vec()) + Ok(data) } #[cfg(not(feature = "disable-encryption"))] diff --git a/neqo-transport/src/fc.rs b/neqo-transport/src/fc.rs index abe92c57f5..a0c0dcc389 100644 --- a/neqo-transport/src/fc.rs +++ b/neqo-transport/src/fc.rs @@ -105,12 +105,8 @@ where /// This is `Some` with the active limit if `blocked` has been called, /// if a blocking frame has not been sent (or it has been lost), and /// if the blocking condition remains. - const fn blocked_needed(&self) -> Option { - if self.blocked_frame && self.limit < self.blocked_at { - Some(self.blocked_at - 1) - } else { - None - } + fn blocked_needed(&self) -> Option { + (self.blocked_frame && self.limit < self.blocked_at).then(|| self.blocked_at - 1) } /// Clear the need to send a blocked frame. diff --git a/neqo-transport/src/packet/mod.rs b/neqo-transport/src/packet/mod.rs index 328e349785..ad3b634280 100644 --- a/neqo-transport/src/packet/mod.rs +++ b/neqo-transport/src/packet/mod.rs @@ -425,9 +425,13 @@ impl PacketBuilder { hex(hdr), hex(body) ); - let ciphertext = crypto.encrypt(self.pn, hdr, body)?; + + // Add space for crypto expansion. + let data_end = self.encoder.len(); + self.pad_to(data_end + crypto.expansion(), 0); // Calculate the mask. + let ciphertext = crypto.encrypt(self.pn, self.header.clone(), self.encoder.as_mut())?; let offset = SAMPLE_OFFSET - self.offsets.pn.len(); if offset + SAMPLE_SIZE > ciphertext.len() { return Err(Error::InternalError); @@ -441,9 +445,6 @@ impl PacketBuilder { self.encoder.as_mut()[j] ^= mask[i]; } - // Finally, cut off the plaintext and add back the ciphertext. - self.encoder.truncate(self.header.end); - self.encoder.encode(&ciphertext); qtrace!("Packet built {}", hex(&self.encoder)); Ok(self.encoder) } @@ -558,18 +559,18 @@ pub struct PublicPacket<'a> { /// The packet type. packet_type: PacketType, /// The recovered destination connection ID. - dcid: ConnectionIdRef<'a>, + dcid: ConnectionId, /// The source connection ID, if this is a long header packet. - scid: Option>, + scid: Option, /// Any token that is included in the packet (Retry always has a token; Initial sometimes /// does). This is empty when there is no token. - token: &'a [u8], + token: Vec, /// The size of the header, not including the packet number. header_len: usize, /// Protocol version, if present in header. version: Option, /// A reference to the entire packet, including the header. - data: &'a [u8], + data: &'a mut [u8], } impl<'a> PublicPacket<'a> { @@ -615,7 +616,10 @@ impl<'a> PublicPacket<'a> { /// # Errors /// /// This will return an error if the packet could not be decoded. - pub fn decode(data: &'a [u8], dcid_decoder: &dyn ConnectionIdDecoder) -> Res<(Self, &'a [u8])> { + pub fn decode( + data: &'a mut [u8], + dcid_decoder: &dyn ConnectionIdDecoder, + ) -> Res<(Self, &'a mut [u8])> { let mut decoder = Decoder::new(data); let first = Self::opt(decoder.decode_uint::())?; @@ -625,29 +629,30 @@ impl<'a> PublicPacket<'a> { if decoder.remaining() < SAMPLE_OFFSET + SAMPLE_SIZE { return Err(Error::InvalidPacket); } - let dcid = Self::opt(dcid_decoder.decode_cid(&mut decoder))?; + let dcid = Self::opt(dcid_decoder.decode_cid(&mut decoder))?.into(); if decoder.remaining() < SAMPLE_OFFSET + SAMPLE_SIZE { return Err(Error::InvalidPacket); } let header_len = decoder.offset(); + return Ok(( Self { packet_type: PacketType::Short, dcid, scid: None, - token: &[], + token: vec![], header_len, version: None, data, }, - &[], + &mut [], )); } // Generic long header. let version = Self::opt(decoder.decode_uint())?; - let dcid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?); - let scid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?); + let dcid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?).into(); + let scid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?).into(); // Version negotiation. if version == 0 { @@ -656,12 +661,12 @@ impl<'a> PublicPacket<'a> { packet_type: PacketType::VersionNegotiation, dcid, scid: Some(scid), - token: &[], + token: vec![], header_len: decoder.offset(), version: None, data, }, - &[], + &mut [], )); } @@ -672,12 +677,12 @@ impl<'a> PublicPacket<'a> { packet_type: PacketType::OtherVersion, dcid, scid: Some(scid), - token: &[], + token: vec![], header_len: decoder.offset(), version: Some(version), data, }, - &[], + &mut [], )); }; @@ -687,9 +692,10 @@ impl<'a> PublicPacket<'a> { let packet_type = PacketType::from_byte((first >> 4) & 3, version); // The type-specific code includes a token. This consumes the remainder of the packet. - let (token, header_len) = Self::decode_long(&mut decoder, packet_type, version)?; + let (token, header_len) = PublicPacket::decode_long(&mut decoder, packet_type, version)?; + let token = token.to_vec(); let end = data.len() - decoder.remaining(); - let (data, remainder) = data.split_at(end); + let (data, remainder) = data.split_at_mut(end); Ok(( Self { packet_type, @@ -742,22 +748,24 @@ impl<'a> PublicPacket<'a> { } #[must_use] - pub const fn dcid(&self) -> ConnectionIdRef<'a> { - self.dcid + pub fn dcid(&self) -> ConnectionIdRef { + self.dcid.as_cid_ref() } /// # Panics /// /// This will panic if called for a short header packet. #[must_use] - pub fn scid(&self) -> ConnectionIdRef<'a> { + pub fn scid(&self) -> ConnectionIdRef { self.scid + .as_ref() .expect("should only be called for long header packets") + .as_cid_ref() } #[must_use] - pub const fn token(&self) -> &'a [u8] { - self.token + pub fn token(&self) -> &[u8] { + &self.token } #[must_use] @@ -777,6 +785,11 @@ impl<'a> PublicPacket<'a> { self.data.len() } + #[must_use] + pub fn data(&self) -> &[u8] { + self.data + } + const fn decode_pn(expected: PacketNumber, pn: u64, w: usize) -> PacketNumber { let window = 1_u64 << (w * 8); let candidate = (expected & !(window - 1)) | pn; @@ -794,9 +807,9 @@ impl<'a> PublicPacket<'a> { /// Decrypt the header of the packet. fn decrypt_header( - &self, + &mut self, crypto: &CryptoDxState, - ) -> Res<(bool, PacketNumber, Vec, &'a [u8])> { + ) -> Res<(bool, PacketNumber, Range)> { assert_ne!(self.packet_type, PacketType::Retry); assert_ne!(self.packet_type, PacketType::VersionNegotiation); @@ -817,40 +830,40 @@ impl<'a> PublicPacket<'a> { }; let first_byte = self.data[0] ^ (mask[0] & bits); - // Make a copy of the header to work on. - let mut hdrbytes = self.data[..self.header_len + 4].to_vec(); - hdrbytes[0] = first_byte; + let mut hdrbytes = 0..self.header_len + 4; + self.data[0] = first_byte; // Unmask the PN. let mut pn_encoded: u64 = 0; + let mut pn_bytes = + self.data[self.header_len..self.header_len + MAX_PACKET_NUMBER_LEN].to_vec(); for i in 0..MAX_PACKET_NUMBER_LEN { - hdrbytes[self.header_len + i] ^= mask[1 + i]; + pn_bytes[i] ^= mask[1 + i]; pn_encoded <<= 8; - pn_encoded += u64::from(hdrbytes[self.header_len + i]); + pn_encoded += u64::from(pn_bytes[i]); } - // Now decode the packet number length and apply it, hopefully in constant time. let pn_len = usize::from((first_byte & 0x3) + 1); - hdrbytes.truncate(self.header_len + pn_len); + self.data[self.header_len..self.header_len + pn_len].copy_from_slice(&pn_bytes[..pn_len]); + hdrbytes.end = self.header_len + pn_len; pn_encoded >>= 8 * (MAX_PACKET_NUMBER_LEN - pn_len); - qtrace!("unmasked hdr={}", hex(&hdrbytes)); + qtrace!("unmasked hdr={}", hex(&self.data[hdrbytes.clone()])); let key_phase = self.packet_type == PacketType::Short && (first_byte & PACKET_BIT_KEY_PHASE) == PACKET_BIT_KEY_PHASE; let pn = Self::decode_pn(crypto.next_pn(), pn_encoded, pn_len); - Ok(( - key_phase, - pn, - hdrbytes, - &self.data[self.header_len + pn_len..], - )) + Ok((key_phase, pn, hdrbytes)) } /// # Errors /// /// This will return an error if the packet cannot be decrypted. - pub fn decrypt(&self, crypto: &mut CryptoStates, release_at: Instant) -> Res { + pub fn decrypt( + &mut self, + crypto: &mut CryptoStates, + release_at: Instant, + ) -> Res { let cspace: CryptoSpace = self.packet_type.into(); // When we don't have a version, the crypto code doesn't need a version // for lookup, so use the default, but fix it up if decryption succeeds. @@ -862,13 +875,13 @@ impl<'a> PublicPacket<'a> { // This is OK in this case because we the only reason this can // fail is if the cryptographic module is bad or the packet is // too small (which is public information). - let (key_phase, pn, header, body) = self.decrypt_header(rx)?; + let (key_phase, pn, header) = self.decrypt_header(rx)?; qtrace!("[{rx}] decoded header: {header:?}"); let Some(rx) = crypto.rx(version, cspace, key_phase) else { return Err(Error::DecryptError); }; let version = rx.version(); // Version fixup; see above. - let d = rx.decrypt(pn, &header, body)?; + let d = rx.decrypt(pn, header, self.data)?; // If this is the first packet ever successfully decrypted // using `rx`, make sure to initiate a key update. if rx.needs_update() { @@ -919,14 +932,14 @@ impl fmt::Debug for PublicPacket<'_> { } } -pub struct DecryptedPacket { +pub struct DecryptedPacket<'a> { version: Version, pt: PacketType, pn: PacketNumber, - data: Vec, + data: &'a [u8], } -impl DecryptedPacket { +impl DecryptedPacket<'_> { #[must_use] pub const fn version(&self) -> Version { self.version @@ -943,15 +956,16 @@ impl DecryptedPacket { } } -impl Deref for DecryptedPacket { +impl Deref for DecryptedPacket<'_> { type Target = [u8]; fn deref(&self) -> &Self::Target { - &self.data[..] + self.data } } #[cfg(all(test, not(feature = "disable-encryption")))] +#[cfg(test)] mod tests { use neqo_common::Encoder; use test_fixture::{fixture_init, now}; @@ -1002,7 +1016,8 @@ mod tests { // The spec uses PN=1, but our crypto refuses to skip packet numbers. // So burn an encryption: - let burn = prot.encrypt(0, &[], &[]).expect("burn OK"); + let mut burn = [0; 16]; + prot.encrypt(0, 0..0, &mut burn).expect("burn OK"); assert_eq!(burn.len(), prot.expansion()); let mut builder = PacketBuilder::long( @@ -1026,7 +1041,7 @@ mod tests { fixture_init(); let mut padded = SAMPLE_INITIAL.to_vec(); padded.extend_from_slice(EXTRA); - let (packet, remainder) = PublicPacket::decode(&padded, &cid_mgr()).unwrap(); + let (mut packet, remainder) = PublicPacket::decode(&mut padded, &cid_mgr()).unwrap(); assert_eq!(packet.packet_type(), PacketType::Initial); assert_eq!(&packet.dcid()[..], &[] as &[u8]); assert_eq!(&packet.scid()[..], SERVER_CID); @@ -1048,7 +1063,7 @@ mod tests { enc.encode_vec(1, &[]); enc.encode(&[0xff; 40]); // junk - assert!(PublicPacket::decode(enc.as_ref(), &cid_mgr()).is_err()); + assert!(PublicPacket::decode(enc.as_mut(), &cid_mgr()).is_err()); } #[test] @@ -1060,7 +1075,7 @@ mod tests { enc.encode_vec(1, &[0x00; MAX_CONNECTION_ID_LEN + 2]); enc.encode(&[0xff; 40]); // junk - assert!(PublicPacket::decode(enc.as_ref(), &cid_mgr()).is_err()); + assert!(PublicPacket::decode(enc.as_mut(), &cid_mgr()).is_err()); } const SAMPLE_SHORT: &[u8] = &[ @@ -1107,7 +1122,8 @@ mod tests { #[test] fn decode_short() { fixture_init(); - let (packet, remainder) = PublicPacket::decode(SAMPLE_SHORT, &cid_mgr()).unwrap(); + let mut sample_short = SAMPLE_SHORT.to_vec(); + let (mut packet, remainder) = PublicPacket::decode(&mut sample_short, &cid_mgr()).unwrap(); assert_eq!(packet.packet_type(), PacketType::Short); assert!(remainder.is_empty()); let decrypted = packet @@ -1121,8 +1137,9 @@ mod tests { #[test] fn decode_short_bad_cid() { fixture_init(); - let (packet, remainder) = PublicPacket::decode( - SAMPLE_SHORT, + let mut sample_short = SAMPLE_SHORT.to_vec(); + let (mut packet, remainder) = PublicPacket::decode( + &mut sample_short, &RandomConnectionIdGenerator::new(SERVER_CID.len() - 1), ) .unwrap(); @@ -1136,8 +1153,9 @@ mod tests { /// Saying that the connection ID is longer causes the initial decode to fail. #[test] fn decode_short_long_cid() { + let mut sample_short = SAMPLE_SHORT.to_vec(); assert!(PublicPacket::decode( - SAMPLE_SHORT, + &mut sample_short, &RandomConnectionIdGenerator::new(SERVER_CID.len() + 1) ) .is_err()); @@ -1290,10 +1308,10 @@ mod tests { #[cfg(test)] fn build_retry_single(version: Version, sample_retry: &[u8]) { fixture_init(); - let retry = + let mut retry = PacketBuilder::retry(version, &[], SERVER_CID, RETRY_TOKEN, CLIENT_CID).unwrap(); - let (packet, remainder) = PublicPacket::decode(&retry, &cid_mgr()).unwrap(); + let (packet, remainder) = PublicPacket::decode(&mut retry, &cid_mgr()).unwrap(); assert!(packet.is_valid_retry(&ConnectionId::from(CLIENT_CID))); assert!(remainder.is_empty()); @@ -1339,8 +1357,7 @@ mod tests { } } - #[cfg(test)] - fn decode_retry(version: Version, sample_retry: &[u8]) { + fn decode_retry(version: Version, sample_retry: &mut [u8]) { fixture_init(); let (packet, remainder) = PublicPacket::decode(sample_retry, &RandomConnectionIdGenerator::new(5)).unwrap(); @@ -1354,17 +1371,20 @@ mod tests { #[test] fn decode_retry_v2() { - decode_retry(Version::Version2, SAMPLE_RETRY_V2); + let mut sample_retry_v2 = SAMPLE_RETRY_V2.to_vec(); + decode_retry(Version::Version2, &mut sample_retry_v2); } #[test] fn decode_retry_v1() { - decode_retry(Version::Version1, SAMPLE_RETRY_V1); + let mut sample_retry_v1 = SAMPLE_RETRY_V1.to_vec(); + decode_retry(Version::Version1, &mut sample_retry_v1); } #[test] fn decode_retry_29() { - decode_retry(Version::Draft29, SAMPLE_RETRY_29); + let mut sample_retry_29 = SAMPLE_RETRY_29.to_vec(); + decode_retry(Version::Draft29, &mut sample_retry_29); } /// Check some packets that are clearly not valid Retry packets. @@ -1374,30 +1394,31 @@ mod tests { let cid_mgr = RandomConnectionIdGenerator::new(5); let odcid = ConnectionId::from(CLIENT_CID); - assert!(PublicPacket::decode(&[], &cid_mgr).is_err()); + assert!(PublicPacket::decode(&mut [], &cid_mgr).is_err()); - let (packet, remainder) = PublicPacket::decode(SAMPLE_RETRY_V1, &cid_mgr).unwrap(); + let mut sample_retry_v1 = SAMPLE_RETRY_V1.to_vec(); + let (packet, remainder) = PublicPacket::decode(&mut sample_retry_v1, &cid_mgr).unwrap(); assert!(remainder.is_empty()); assert!(packet.is_valid_retry(&odcid)); let mut damaged_retry = SAMPLE_RETRY_V1.to_vec(); let last = damaged_retry.len() - 1; damaged_retry[last] ^= 66; - let (packet, remainder) = PublicPacket::decode(&damaged_retry, &cid_mgr).unwrap(); + let (packet, remainder) = PublicPacket::decode(&mut damaged_retry, &cid_mgr).unwrap(); assert!(remainder.is_empty()); assert!(!packet.is_valid_retry(&odcid)); damaged_retry.truncate(last); - let (packet, remainder) = PublicPacket::decode(&damaged_retry, &cid_mgr).unwrap(); + let (packet, remainder) = PublicPacket::decode(&mut damaged_retry, &cid_mgr).unwrap(); assert!(remainder.is_empty()); assert!(!packet.is_valid_retry(&odcid)); // An invalid token should be rejected sooner. damaged_retry.truncate(last - 4); - assert!(PublicPacket::decode(&damaged_retry, &cid_mgr).is_err()); + assert!(PublicPacket::decode(&mut damaged_retry, &cid_mgr).is_err()); damaged_retry.truncate(last - 1); - assert!(PublicPacket::decode(&damaged_retry, &cid_mgr).is_err()); + assert!(PublicPacket::decode(&mut damaged_retry, &cid_mgr).is_err()); } const SAMPLE_VN: &[u8] = &[ @@ -1438,8 +1459,9 @@ mod tests { #[test] fn parse_vn() { + let mut sample_vn = SAMPLE_VN.to_vec(); let (packet, remainder) = - PublicPacket::decode(SAMPLE_VN, &EmptyConnectionIdGenerator::default()).unwrap(); + PublicPacket::decode(&mut sample_vn, &EmptyConnectionIdGenerator::default()).unwrap(); assert!(remainder.is_empty()); assert_eq!(&packet.dcid[..], SERVER_CID); assert!(packet.scid.is_some()); @@ -1460,7 +1482,7 @@ mod tests { enc.encode_uint(4, 0x5a6a_7a8a_u64); let (packet, remainder) = - PublicPacket::decode(enc.as_ref(), &EmptyConnectionIdGenerator::default()).unwrap(); + PublicPacket::decode(enc.as_mut(), &EmptyConnectionIdGenerator::default()).unwrap(); assert!(remainder.is_empty()); assert_eq!(&packet.dcid[..], BIG_DCID); assert!(packet.scid.is_some()); @@ -1495,8 +1517,9 @@ mod tests { 0x5d, 0x79, 0x99, 0xc2, 0x5a, 0x5b, 0xfb, ]; fixture_init(); - let (packet, slice) = - PublicPacket::decode(PACKET, &EmptyConnectionIdGenerator::default()).unwrap(); + let mut packet = PACKET.to_vec(); + let (mut packet, slice) = + PublicPacket::decode(&mut packet, &EmptyConnectionIdGenerator::default()).unwrap(); assert!(slice.is_empty()); let decrypted = packet .decrypt(&mut CryptoStates::test_chacha(), now()) @@ -1509,17 +1532,15 @@ mod tests { #[test] fn decode_empty() { neqo_crypto::init().unwrap(); - let res = PublicPacket::decode(&[], &EmptyConnectionIdGenerator::default()); + let res = PublicPacket::decode(&mut [], &EmptyConnectionIdGenerator::default()); assert!(res.is_err()); } #[test] fn decode_too_short() { neqo_crypto::init().unwrap(); - let res = PublicPacket::decode( - &[179, 255, 0, 0, 29, 0, 0], - &EmptyConnectionIdGenerator::default(), - ); + let mut data = [179, 255, 0, 0, 29, 0, 0]; + let res = PublicPacket::decode(&mut data, &EmptyConnectionIdGenerator::default()); assert!(res.is_err()); } } diff --git a/neqo-transport/src/server.rs b/neqo-transport/src/server.rs index 3e337b473e..2a901aa4e0 100644 --- a/neqo-transport/src/server.rs +++ b/neqo-transport/src/server.rs @@ -196,7 +196,7 @@ impl Server { fn handle_initial( &mut self, initial: InitialDetails, - dgram: Datagram>, + dgram: Datagram + AsMut<[u8]>>, now: Instant, ) -> Output { qdebug!("[{self}] Handle initial"); @@ -307,7 +307,7 @@ impl Server { fn accept_connection( &mut self, initial: InitialDetails, - dgram: Datagram>, + dgram: Datagram + AsMut<[u8]>>, orig_dcid: Option, now: Instant, ) -> Output { @@ -349,12 +349,19 @@ impl Server { } } - fn process_input(&mut self, dgram: Datagram>, now: Instant) -> Output { + fn process_input( + &mut self, + mut dgram: Datagram + AsMut<[u8]>>, + now: Instant, + ) -> Output { qtrace!("Process datagram: {}", hex(&dgram[..])); // This is only looking at the first packet header in the datagram. // All packets in the datagram are routed to the same connection. - let res = PublicPacket::decode(&dgram[..], self.cid_generator.borrow().as_decoder()); + let len = dgram.len(); + let destination = dgram.destination(); + let source = dgram.source(); + let res = PublicPacket::decode(&mut dgram[..], self.cid_generator.borrow().as_decoder()); let Ok((packet, _remainder)) = res else { qtrace!("[{self}] Discarding {dgram:?}"); return Output::None; @@ -383,7 +390,7 @@ impl Server { .all() .contains(&packet.version().expect("packet has version"))) { - if dgram.len() < MIN_INITIAL_PACKET_SIZE { + if len < MIN_INITIAL_PACKET_SIZE { qdebug!("[{self}] Unsupported version: too short"); return Output::None; } @@ -399,8 +406,8 @@ impl Server { "[{self}] type={:?} path:{} {}->{} {:?} len {}", PacketType::VersionNegotiation, packet.dcid(), - dgram.destination(), - dgram.source(), + destination, + source, IpTos::default(), vn.len(), ); @@ -422,7 +429,7 @@ impl Server { match packet.packet_type() { PacketType::Initial => { - if dgram.len() < MIN_INITIAL_PACKET_SIZE { + if len < MIN_INITIAL_PACKET_SIZE { qdebug!("[{self}] Drop initial: too short"); return Output::None; } @@ -470,7 +477,11 @@ impl Server { } #[must_use] - pub fn process(&mut self, dgram: Option>>, now: Instant) -> Output { + pub fn process( + &mut self, + dgram: Option + AsMut<[u8]>>>, + now: Instant, + ) -> Output { let out = dgram .map_or(Output::None, |d| self.process_input(d, now)) .or_else(|| self.process_next_output(now)); diff --git a/neqo-udp/src/lib.rs b/neqo-udp/src/lib.rs index 615f62cdf4..88fe5a61da 100644 --- a/neqo-udp/src/lib.rs +++ b/neqo-udp/src/lib.rs @@ -11,7 +11,7 @@ use std::{ io::{self, IoSliceMut}, iter, net::SocketAddr, - slice::{self, Chunks}, + slice::{self, ChunksMut}, }; use log::{log_enabled, Level}; @@ -120,7 +120,7 @@ pub fn recv_inner<'a>( Ok(DatagramIter { current_buffer: None, - remaining_buffers: metas.into_iter().zip(recv_buf.0.iter()).take(n), + remaining_buffers: metas.into_iter().zip(recv_buf.0.iter_mut()).take(n), local_address, }) } @@ -128,17 +128,17 @@ pub fn recv_inner<'a>( pub struct DatagramIter<'a> { /// The current buffer, containing zero or more datagrams, each sharing the /// same [`RecvMeta`]. - current_buffer: Option<(RecvMeta, Chunks<'a, u8>)>, + current_buffer: Option<(RecvMeta, ChunksMut<'a, u8>)>, /// Remaining buffers, each containing zero or more datagrams, one /// [`RecvMeta`] per buffer. remaining_buffers: - iter::Take, slice::Iter<'a, Vec>>>, + iter::Take, slice::IterMut<'a, Vec>>>, /// The local address of the UDP socket used to receive the datagrams. local_address: SocketAddr, } impl<'a> Iterator for DatagramIter<'a> { - type Item = Datagram<&'a [u8]>; + type Item = Datagram<&'a mut [u8]>; fn next(&mut self) -> Option { loop { @@ -177,7 +177,7 @@ impl<'a> Iterator for DatagramIter<'a> { // Got another buffer. Let's chunk it into datagrams and return the // first datagram in the next loop iteration. - self.current_buffer = Some((meta, buf[0..meta.len].chunks(meta.stride))); + self.current_buffer = Some((meta, buf[0..meta.len].chunks_mut(meta.stride))); } } }