From 5fb4425ac66ebc8ccf88d6ef45b65db88c92730b Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 25 Sep 2025 11:02:57 +0200 Subject: [PATCH 1/5] Add method to fragment vectored payload Signed-off-by: Marvin Gudel --- mctp-estack/src/fragment.rs | 145 ++++++++++++++++++++++++++++++++++++ mctp-estack/src/router.rs | 28 +------ 2 files changed, 148 insertions(+), 25 deletions(-) diff --git a/mctp-estack/src/fragment.rs b/mctp-estack/src/fragment.rs index e499787..0716a7a 100644 --- a/mctp-estack/src/fragment.rs +++ b/mctp-estack/src/fragment.rs @@ -144,6 +144,113 @@ impl Fragmenter { let used = max_total - rest.len(); SendOutput::Packet(&mut out[..used]) } + + pub fn fragment_vectored<'f>( + &mut self, + payload: &[&[u8]], + out: &'f mut [u8], + ) -> SendOutput<'f> { + let total_payload_len = + payload.iter().fold(0, |acc, part| acc + part.len()); + if total_payload_len < self.payload_used { + // Caller is passing varying payload buffers + debug!("varying payload"); + return SendOutput::failure(Error::BadArgument, self); + } + + // Require at least MTU buffer size, to ensure that all non-end + // fragments are the same size per the spec. + if out.len() < self.mtu { + debug!("small out buffer"); + return SendOutput::failure(Error::BadArgument, self); + } + + // Reserve header space, the remaining buffer keeps being + // updated in `rest` + let max_total = out.len().min(self.mtu); + let (h, mut rest) = out[..max_total].split_at_mut(MctpHeader::LEN); + + // Append type byte + if self.header.som { + rest[0] = mctp::encode_type_ic(self.typ, self.ic); + rest = &mut rest[1..]; + } + + let remaining_payload_len = total_payload_len - self.payload_used; + let l = remaining_payload_len.min(rest.len()); + let (d, rest) = rest.split_at_mut(l); + copy_vectored(payload, self.payload_used, d); + self.payload_used += l; + + // Add the header + if self.payload_used == total_payload_len { + self.header.eom = true; + } + // OK unwrap: seq and tag are valid. + h.copy_from_slice(&self.header.encode().unwrap()); + + self.header.som = false; + self.header.seq = (self.header.seq + 1) & mctp::MCTP_SEQ_MASK; + + let used = max_total - rest.len(); + SendOutput::Packet(&mut out[..used]) + } +} + +/// Copy data from a vectored src to dest +/// +/// Copies `dest.len()` bytes from payload to dest, +/// starting after `offset` bytes. +/// +/// ## Panics +/// +/// This function will panic when not enough bytes are available to fill dest. +/// Total size of `payload` has to be `atleast dest.len()` + `offset`. +fn copy_vectored(src: &[&[u8]], offset: usize, dest: &mut [u8]) { + let mut i = 0; + + while i < dest.len() { + let payload_index = i + offset; + let next = get_sub_slice(src, payload_index); + let remaining = dest.len() - i; + if remaining > next.len() { + dest[i..(i + next.len())].copy_from_slice(next); + i += next.len(); + } else { + dest[i..].copy_from_slice(&next[..remaining]); + return; + } + } +} + +/// Get a slice of `vector` indexed by `offset` +/// +/// The `offset` is the absolute byte index. +/// The returned slice is the remaining sub slice starting at `offset`. +/// +/// ## Panics +/// +/// Will panic when offset is larger than the size of `vector`. +/// +/// ## Example +/// ```ignore +/// # use mctp_estack::fragment::get_slice; +/// let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6]]; +/// +/// let slice = get_slice(vector, 4); +/// +/// assert_eq!(slice, &[5, 6]); +/// ``` +fn get_sub_slice<'a>(vector: &'a [&[u8]], offset: usize) -> &'a [u8] { + let mut i = offset; + for slice in vector { + if i >= slice.len() { + i -= slice.len(); + } else { + return &slice[i..]; + } + } + panic!("offset for vector out of bounds"); } pub enum SendOutput<'p> { @@ -194,3 +301,41 @@ impl SendOutput<'_> { Self::Error { err, cookie: None } } } + +#[cfg(test)] +mod tests { + #[test] + fn test_get_slice() { + use super::get_sub_slice; + let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; + let slice = get_sub_slice(vector, 4); + assert_eq!(slice, &[5, 6]); + let slice = get_sub_slice(vector, 0); + assert_eq!(slice, &[1, 2, 3]); + let slice = get_sub_slice(vector, 3); + assert_eq!(slice, &[4, 5, 6]); + } + #[test] + fn test_copy_vectored() { + use super::copy_vectored; + let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5], &[6, 7, 8, 9]]; + + let mut dest = [0; 6]; + copy_vectored(vector, 1, &mut dest); + assert_eq!(&dest, &[2, 3, 4, 5, 6, 7]); + + let mut dest = [0; 5]; + copy_vectored(vector, 4, &mut dest); + assert_eq!(&dest, &[5, 6, 7, 8, 9]); + + let mut dest = [0; 9]; + copy_vectored(vector, 0, &mut dest); + assert_eq!(&dest, &[1, 2, 3, 4, 5, 6, 7, 8, 9]); + + let vector: &[&[u8]] = &[&[1, 2, 3]]; + + let mut dest = [0; 1]; + copy_vectored(vector, 2, &mut dest); + assert_eq!(&dest, &[3]); + } +} diff --git a/mctp-estack/src/router.rs b/mctp-estack/src/router.rs index 23644f8..81b510f 100644 --- a/mctp-estack/src/router.rs +++ b/mctp-estack/src/router.rs @@ -17,7 +17,7 @@ use core::task::{Poll, Waker}; use crate::{ config, AppCookie, Fragmenter, MctpHeader, MctpMessage, SendOutput, Stack, - MAX_MTU, MAX_PAYLOAD, + MAX_MTU, }; use mctp::{Eid, Error, MsgIC, MsgType, Result, Tag, TagValue}; @@ -180,23 +180,8 @@ impl PortTop { &self, fragmenter: &mut Fragmenter, pkt: &[&[u8]], - work_msg: &mut Vec, ) -> Result { trace!("send_message"); - let payload = if pkt.len() == 1 { - // Avoid the copy when sending a single slice - pkt[0] - } else { - work_msg.clear(); - for p in pkt { - work_msg.extend_from_slice(p).map_err(|_| { - debug!("Message too large"); - Error::NoSpace - })?; - } - work_msg - }; - // send_message() needs to wait for packets to get enqueued to the PortTop channel. // It shouldn't hold the send_mutex() across an await, since that would block // forward_packet(). @@ -215,7 +200,7 @@ impl PortTop { }; qpkt.len = 0; - match fragmenter.fragment(payload, &mut qpkt.data) { + match fragmenter.fragment_vectored(pkt, &mut qpkt.data) { SendOutput::Packet(p) => { qpkt.len = p.len(); sender.send_done(); @@ -452,10 +437,6 @@ pub struct Router<'r> { BlockingMutex>>, recv_wakers: WakerPool, - - /// Temporary storage to flatten vectorised local sent messages - // prior to fragmentation and queueing. - work_msg: AsyncMutex>, } pub struct RouterInner<'r> { @@ -497,7 +478,6 @@ impl<'r> Router<'r> { app_listeners, ports: Vec::new(), recv_wakers: Default::default(), - work_msg: AsyncMutex::new(Vec::new()), } } @@ -776,9 +756,7 @@ impl<'r> Router<'r> { // release to allow other ports to continue work drop(inner); - // lock the shared work buffer against other app_send_message() - let mut work_msg = self.work_msg.lock().await; - top.send_message(&mut fragmenter, buf, &mut work_msg).await + top.send_message(&mut fragmenter, buf).await } /// Create a `AsyncReqChannel` instance. From 0f1fdc2c98f43702afa7f1df7c71562cf491bd76 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Tue, 14 Oct 2025 13:12:58 +0200 Subject: [PATCH 2/5] Move vectored copy methods to util mod Signed-off-by: Marvin Gudel --- mctp-estack/src/fragment.rs | 96 +------------------------------------ mctp-estack/src/util.rs | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 95 deletions(-) diff --git a/mctp-estack/src/fragment.rs b/mctp-estack/src/fragment.rs index 0716a7a..5480a7c 100644 --- a/mctp-estack/src/fragment.rs +++ b/mctp-estack/src/fragment.rs @@ -179,7 +179,7 @@ impl Fragmenter { let remaining_payload_len = total_payload_len - self.payload_used; let l = remaining_payload_len.min(rest.len()); let (d, rest) = rest.split_at_mut(l); - copy_vectored(payload, self.payload_used, d); + crate::util::copy_vectored(payload, self.payload_used, d); self.payload_used += l; // Add the header @@ -197,62 +197,6 @@ impl Fragmenter { } } -/// Copy data from a vectored src to dest -/// -/// Copies `dest.len()` bytes from payload to dest, -/// starting after `offset` bytes. -/// -/// ## Panics -/// -/// This function will panic when not enough bytes are available to fill dest. -/// Total size of `payload` has to be `atleast dest.len()` + `offset`. -fn copy_vectored(src: &[&[u8]], offset: usize, dest: &mut [u8]) { - let mut i = 0; - - while i < dest.len() { - let payload_index = i + offset; - let next = get_sub_slice(src, payload_index); - let remaining = dest.len() - i; - if remaining > next.len() { - dest[i..(i + next.len())].copy_from_slice(next); - i += next.len(); - } else { - dest[i..].copy_from_slice(&next[..remaining]); - return; - } - } -} - -/// Get a slice of `vector` indexed by `offset` -/// -/// The `offset` is the absolute byte index. -/// The returned slice is the remaining sub slice starting at `offset`. -/// -/// ## Panics -/// -/// Will panic when offset is larger than the size of `vector`. -/// -/// ## Example -/// ```ignore -/// # use mctp_estack::fragment::get_slice; -/// let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6]]; -/// -/// let slice = get_slice(vector, 4); -/// -/// assert_eq!(slice, &[5, 6]); -/// ``` -fn get_sub_slice<'a>(vector: &'a [&[u8]], offset: usize) -> &'a [u8] { - let mut i = offset; - for slice in vector { - if i >= slice.len() { - i -= slice.len(); - } else { - return &slice[i..]; - } - } - panic!("offset for vector out of bounds"); -} - pub enum SendOutput<'p> { Packet(&'p mut [u8]), Complete { @@ -301,41 +245,3 @@ impl SendOutput<'_> { Self::Error { err, cookie: None } } } - -#[cfg(test)] -mod tests { - #[test] - fn test_get_slice() { - use super::get_sub_slice; - let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; - let slice = get_sub_slice(vector, 4); - assert_eq!(slice, &[5, 6]); - let slice = get_sub_slice(vector, 0); - assert_eq!(slice, &[1, 2, 3]); - let slice = get_sub_slice(vector, 3); - assert_eq!(slice, &[4, 5, 6]); - } - #[test] - fn test_copy_vectored() { - use super::copy_vectored; - let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5], &[6, 7, 8, 9]]; - - let mut dest = [0; 6]; - copy_vectored(vector, 1, &mut dest); - assert_eq!(&dest, &[2, 3, 4, 5, 6, 7]); - - let mut dest = [0; 5]; - copy_vectored(vector, 4, &mut dest); - assert_eq!(&dest, &[5, 6, 7, 8, 9]); - - let mut dest = [0; 9]; - copy_vectored(vector, 0, &mut dest); - assert_eq!(&dest, &[1, 2, 3, 4, 5, 6, 7, 8, 9]); - - let vector: &[&[u8]] = &[&[1, 2, 3]]; - - let mut dest = [0; 1]; - copy_vectored(vector, 2, &mut dest); - assert_eq!(&dest, &[3]); - } -} diff --git a/mctp-estack/src/util.rs b/mctp-estack/src/util.rs index 3b1fdb3..0d852d3 100644 --- a/mctp-estack/src/util.rs +++ b/mctp-estack/src/util.rs @@ -18,3 +18,97 @@ macro_rules! get_build_var { } }}; } + +/// Copy data from a vectored src to dest +/// +/// Copies `dest.len()` bytes from payload to dest, +/// starting after `offset` bytes. +/// +/// ## Panics +/// +/// This function will panic when not enough bytes are available to fill dest. +/// Total size of `payload` has to be `atleast dest.len()` + `offset`. +pub fn copy_vectored(src: &[&[u8]], offset: usize, dest: &mut [u8]) { + let mut i = 0; + + while i < dest.len() { + let payload_index = i + offset; + let next = get_sub_slice(src, payload_index); + let remaining = dest.len() - i; + if remaining > next.len() { + dest[i..(i + next.len())].copy_from_slice(next); + i += next.len(); + } else { + dest[i..].copy_from_slice(&next[..remaining]); + return; + } + } +} + +/// Get a slice of `vector` indexed by `offset` +/// +/// The `offset` is the absolute byte index. +/// The returned slice is the remaining sub slice starting at `offset`. +/// +/// ## Panics +/// +/// Will panic when offset is larger than the size of `vector`. +/// +/// ## Example +/// ```ignore +/// # use mctp_estack::fragment::get_slice; +/// let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6]]; +/// +/// let slice = get_slice(vector, 4); +/// +/// assert_eq!(slice, &[5, 6]); +/// ``` +pub fn get_sub_slice<'a>(vector: &'a [&[u8]], offset: usize) -> &'a [u8] { + let mut i = offset; + for slice in vector { + if i >= slice.len() { + i -= slice.len(); + } else { + return &slice[i..]; + } + } + panic!("offset for vector out of bounds"); +} + +#[cfg(test)] +mod tests { + #[test] + fn test_get_slice() { + use super::get_sub_slice; + let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; + let slice = get_sub_slice(vector, 4); + assert_eq!(slice, &[5, 6]); + let slice = get_sub_slice(vector, 0); + assert_eq!(slice, &[1, 2, 3]); + let slice = get_sub_slice(vector, 3); + assert_eq!(slice, &[4, 5, 6]); + } + #[test] + fn test_copy_vectored() { + use super::copy_vectored; + let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5], &[6, 7, 8, 9]]; + + let mut dest = [0; 6]; + copy_vectored(vector, 1, &mut dest); + assert_eq!(&dest, &[2, 3, 4, 5, 6, 7]); + + let mut dest = [0; 5]; + copy_vectored(vector, 4, &mut dest); + assert_eq!(&dest, &[5, 6, 7, 8, 9]); + + let mut dest = [0; 9]; + copy_vectored(vector, 0, &mut dest); + assert_eq!(&dest, &[1, 2, 3, 4, 5, 6, 7, 8, 9]); + + let vector: &[&[u8]] = &[&[1, 2, 3]]; + + let mut dest = [0; 1]; + copy_vectored(vector, 2, &mut dest); + assert_eq!(&dest, &[3]); + } +} From 4b85d0ec8ada64be25f844a2c30ebf0a2c0b1033 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 16 Oct 2025 11:28:36 +0200 Subject: [PATCH 3/5] Return success on eom for fragment_vectored() Signed-off-by: Marvin Gudel --- mctp-estack/src/fragment.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mctp-estack/src/fragment.rs b/mctp-estack/src/fragment.rs index 5480a7c..310ca33 100644 --- a/mctp-estack/src/fragment.rs +++ b/mctp-estack/src/fragment.rs @@ -150,6 +150,10 @@ impl Fragmenter { payload: &[&[u8]], out: &'f mut [u8], ) -> SendOutput<'f> { + if self.header.eom { + return SendOutput::success(self); + } + let total_payload_len = payload.iter().fold(0, |acc, part| acc + part.len()); if total_payload_len < self.payload_used { From d1650f9dd0fc3c95577d113d1bb20cc4212854c6 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Thu, 16 Oct 2025 11:30:40 +0200 Subject: [PATCH 4/5] Call fragment_vectored() from fragment(), add missing docs Signed-off-by: Marvin Gudel --- mctp-estack/src/fragment.rs | 63 ++++++++++--------------------------- 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/mctp-estack/src/fragment.rs b/mctp-estack/src/fragment.rs index 310ca33..dae1931 100644 --- a/mctp-estack/src/fragment.rs +++ b/mctp-estack/src/fragment.rs @@ -90,61 +90,30 @@ impl Fragmenter { /// In `SendOutput::Packet(buf)`, `out` is borrowed as the returned fragment, filled with /// packet contents. /// + /// Calls to `fragment_vectored()` and `fragment()` should not be mixed. + /// (If you do, the vector has to hold exactly one buffer that is + /// equal to the one passed to `fragment()`.) + /// /// `out` must be at least as large as the specified `mtu`. pub fn fragment<'f>( &mut self, payload: &[u8], out: &'f mut [u8], ) -> SendOutput<'f> { - if self.header.eom { - return SendOutput::success(self); - } - - // Require at least MTU buffer size, to ensure that all non-end - // fragments are the same size per the spec. - if out.len() < self.mtu { - debug!("small out buffer"); - return SendOutput::failure(Error::BadArgument, self); - } - - // Reserve header space, the remaining buffer keeps being - // updated in `rest` - let max_total = out.len().min(self.mtu); - let (h, mut rest) = out[..max_total].split_at_mut(MctpHeader::LEN); - - // Append type byte - if self.header.som { - rest[0] = mctp::encode_type_ic(self.typ, self.ic); - rest = &mut rest[1..]; - } - - if payload.len() < self.payload_used { - // Caller is passing varying payload buffers - debug!("varying payload"); - return SendOutput::failure(Error::BadArgument, self); - } - - // Copy as much as is available in input or output - let p = &payload[self.payload_used..]; - let l = p.len().min(rest.len()); - let (d, rest) = rest.split_at_mut(l); - self.payload_used += l; - d.copy_from_slice(&p[..l]); - - // Add the header - if self.payload_used == payload.len() { - self.header.eom = true; - } - // OK unwrap: seq and tag are valid. - h.copy_from_slice(&self.header.encode().unwrap()); - - self.header.som = false; - self.header.seq = (self.header.seq + 1) & mctp::MCTP_SEQ_MASK; - - let used = max_total - rest.len(); - SendOutput::Packet(&mut out[..used]) + self.fragment_vectored(&[payload], out) } + /// Returns fragments for the MCTP payload + /// + /// The same input message `payload` should be passed to each `fragment_vectored()` call. + /// In `SendOutput::Packet(buf)`, `out` is borrowed as the returned fragment, filled with + /// packet contents. + /// + /// Calls to `fragment_vectored()` and `fragment()` should not be mixed. + /// (If you do, the vector has to hold exactly one buffer that is + /// equal to the one passed to `fragment()`.) + /// + /// `out` must be at least as large as the specified `mtu`. pub fn fragment_vectored<'f>( &mut self, payload: &[&[u8]], From 793949a2dc68e1470d3997656044239597697316 Mon Sep 17 00:00:00 2001 From: Marvin Gudel Date: Mon, 20 Oct 2025 12:49:20 +0200 Subject: [PATCH 5/5] Implement a dedicated reader for vectored buffers Signed-off-by: Marvin Gudel --- mctp-estack/src/fragment.rs | 23 ++--- mctp-estack/src/util.rs | 184 ++++++++++++++++++++++-------------- 2 files changed, 122 insertions(+), 85 deletions(-) diff --git a/mctp-estack/src/fragment.rs b/mctp-estack/src/fragment.rs index dae1931..cfd360a 100644 --- a/mctp-estack/src/fragment.rs +++ b/mctp-estack/src/fragment.rs @@ -10,7 +10,7 @@ use crate::fmt::{debug, error, info, trace, warn}; use mctp::{Eid, Error, MsgIC, MsgType, Result, Tag}; -use crate::{AppCookie, MctpHeader}; +use crate::{util::VectorReader, AppCookie, MctpHeader}; /// Fragments a MCTP message. /// @@ -24,8 +24,8 @@ pub struct Fragmenter { cookie: Option, - // A count of how many bytes have already been sent. - payload_used: usize, + // A reader to read from the payload vector + reader: VectorReader, } impl Fragmenter { @@ -59,7 +59,7 @@ impl Fragmenter { }; Ok(Self { - payload_used: 0, + reader: VectorReader::new(), header, typ, mtu, @@ -123,9 +123,7 @@ impl Fragmenter { return SendOutput::success(self); } - let total_payload_len = - payload.iter().fold(0, |acc, part| acc + part.len()); - if total_payload_len < self.payload_used { + if self.reader.is_exhausted(payload).is_err() { // Caller is passing varying payload buffers debug!("varying payload"); return SendOutput::failure(Error::BadArgument, self); @@ -149,14 +147,13 @@ impl Fragmenter { rest = &mut rest[1..]; } - let remaining_payload_len = total_payload_len - self.payload_used; - let l = remaining_payload_len.min(rest.len()); - let (d, rest) = rest.split_at_mut(l); - crate::util::copy_vectored(payload, self.payload_used, d); - self.payload_used += l; + let Ok(n) = self.reader.read(payload, &mut rest) else { + return SendOutput::failure(Error::BadArgument, self); + }; + let rest = &rest[n..]; // Add the header - if self.payload_used == total_payload_len { + if self.reader.is_exhausted(payload).unwrap() { self.header.eom = true; } // OK unwrap: seq and tag are valid. diff --git a/mctp-estack/src/util.rs b/mctp-estack/src/util.rs index 0d852d3..415f5d8 100644 --- a/mctp-estack/src/util.rs +++ b/mctp-estack/src/util.rs @@ -19,96 +19,136 @@ macro_rules! get_build_var { }}; } -/// Copy data from a vectored src to dest +/// A reader to read a vector of byte slices /// -/// Copies `dest.len()` bytes from payload to dest, -/// starting after `offset` bytes. -/// -/// ## Panics -/// -/// This function will panic when not enough bytes are available to fill dest. -/// Total size of `payload` has to be `atleast dest.len()` + `offset`. -pub fn copy_vectored(src: &[&[u8]], offset: usize, dest: &mut [u8]) { - let mut i = 0; +#[derive(Debug)] +pub struct VectorReader { + /// The index of the current slice + /// + /// Set to `vector.len()` when exhausted. + slice_index: usize, + /// The index in the current slice + /// + /// E.g. the element to be read next. + current_slice_offset: usize, +} - while i < dest.len() { - let payload_index = i + offset; - let next = get_sub_slice(src, payload_index); - let remaining = dest.len() - i; - if remaining > next.len() { - dest[i..(i + next.len())].copy_from_slice(next); - i += next.len(); - } else { - dest[i..].copy_from_slice(&next[..remaining]); - return; +impl VectorReader { + /// Create a new reader + pub fn new() -> Self { + VectorReader { + slice_index: 0, + current_slice_offset: 0, } } -} + /// Read `dest.len()` bytes from `src` into `dest`, returning how many bytes were read + /// + /// Returns a [VectorReaderError] when the current position is out of range for `src`. + /// + /// The same `src` buffer has to be passed to subsequent calls to `read()`. + /// Changing the vector is undefined behaviour. + pub fn read( + &mut self, + src: &[&[u8]], + dest: &mut [u8], + ) -> Result { + let mut i = 0; + while i < dest.len() { + if self.is_exhausted(src)? { + return Ok(i); + } -/// Get a slice of `vector` indexed by `offset` -/// -/// The `offset` is the absolute byte index. -/// The returned slice is the remaining sub slice starting at `offset`. -/// -/// ## Panics -/// -/// Will panic when offset is larger than the size of `vector`. -/// -/// ## Example -/// ```ignore -/// # use mctp_estack::fragment::get_slice; -/// let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6]]; -/// -/// let slice = get_slice(vector, 4); -/// -/// assert_eq!(slice, &[5, 6]); -/// ``` -pub fn get_sub_slice<'a>(vector: &'a [&[u8]], offset: usize) -> &'a [u8] { - let mut i = offset; - for slice in vector { - if i >= slice.len() { - i -= slice.len(); - } else { - return &slice[i..]; + let slice = &src[self.slice_index][self.current_slice_offset..]; + let n = slice.len().min(dest[i..].len()); + dest[i..i + n].copy_from_slice(&slice[..n]); + i += n; + self.increment_index(src, n); + } + Ok(i) + } + /// Checks if `src` has been read to the end + /// + /// Returns a [VectorReaderError] when the current position is out of range for `src`. + /// + /// _Note:_ Might return a `Ok` even if the `src` vector changed between calls. + pub fn is_exhausted( + &self, + src: &[&[u8]], + ) -> Result { + if src.len() == self.slice_index { + return Ok(true); + } + // This shlould only occur if the caller passed varying vectors + src.get(self.slice_index).ok_or(VectorReaderError)?; + Ok(false) + } + /// Increment the index by `n`, panic if out ouf bounds + /// + /// If this exhausts the vector exactly, the index is incremented to `vector[vector.len()][0]` + fn increment_index(&mut self, vector: &[&[u8]], n: usize) { + let mut n = n; + loop { + if vector[self.slice_index] + .get(self.current_slice_offset + n) + .is_some() + { + // If we can index the current slice at offset + n just increment offset and return + self.current_slice_offset += n; + return; + } else { + // Substract what has been read from the current slice, then increment to next slice + n -= + vector[self.slice_index][self.current_slice_offset..].len(); + self.slice_index += 1; + self.current_slice_offset = 0; + if self.slice_index == vector.len() { + // return when the end of the vector is reached + debug_assert_eq!(n, 0); + return; + } + } } } - panic!("offset for vector out of bounds"); } +#[derive(Debug)] +pub struct VectorReaderError; + #[cfg(test)] mod tests { #[test] - fn test_get_slice() { - use super::get_sub_slice; - let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; - let slice = get_sub_slice(vector, 4); - assert_eq!(slice, &[5, 6]); - let slice = get_sub_slice(vector, 0); - assert_eq!(slice, &[1, 2, 3]); - let slice = get_sub_slice(vector, 3); - assert_eq!(slice, &[4, 5, 6]); - } - #[test] - fn test_copy_vectored() { - use super::copy_vectored; + fn test_vector_reader() { + use super::VectorReader; + let mut reader = VectorReader::new(); let vector: &[&[u8]] = &[&[1, 2, 3], &[4, 5], &[6, 7, 8, 9]]; + // Test reading a vector partially + let mut dest = [0; 4]; + let n = reader.read(vector, &mut dest).unwrap(); + assert_eq!(n, 4); + assert_eq!(&dest, &[1, 2, 3, 4]); + + // Test reading all remaining elements into a larger than necessary destination let mut dest = [0; 6]; - copy_vectored(vector, 1, &mut dest); - assert_eq!(&dest, &[2, 3, 4, 5, 6, 7]); + let n = reader.read(vector, &mut dest).unwrap(); + assert_eq!(n, 5); + assert_eq!(&dest[..5], &[5, 6, 7, 8, 9]); - let mut dest = [0; 5]; - copy_vectored(vector, 4, &mut dest); - assert_eq!(&dest, &[5, 6, 7, 8, 9]); + assert!(reader + .is_exhausted(vector) + .expect("Vector should be exhausted")); - let mut dest = [0; 9]; - copy_vectored(vector, 0, &mut dest); - assert_eq!(&dest, &[1, 2, 3, 4, 5, 6, 7, 8, 9]); + // Test reading to end in one pass + let mut reader = VectorReader::new(); + let vector: &[&[u8]] = &[&[1, 2, 3], &[4]]; - let vector: &[&[u8]] = &[&[1, 2, 3]]; + let mut dest = [0; 4]; + let n = reader.read(vector, &mut dest).unwrap(); + assert_eq!(n, 4); + assert_eq!(&dest, &[1, 2, 3, 4]); - let mut dest = [0; 1]; - copy_vectored(vector, 2, &mut dest); - assert_eq!(&dest, &[3]); + assert!(reader + .is_exhausted(vector) + .expect("Vector should be exhausted")); } }