diff --git a/lib/xdrgen/generators/rust.rb b/lib/xdrgen/generators/rust.rb index 8a4d36a37..bfab30c33 100644 --- a/lib/xdrgen/generators/rust.rb +++ b/lib/xdrgen/generators/rust.rb @@ -76,7 +76,7 @@ def render_top_matter(out) // #{@output.relative_source_paths.join("\n// ")} EOS out.break - out.puts "#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)]" + out.puts "#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)]" out.break source_paths_sha256_hashes = @output.relative_source_path_sha256_hashes out.puts <<-EOS.strip_heredoc @@ -175,7 +175,7 @@ def render_enum_of_all_types(out, types) pub const VARIANTS: [TypeVariant; #{types.count}] = [ #{types.map { |t| "TypeVariant::#{t}," }.join("\n")} ]; pub const VARIANTS_STR: [&'static str; #{types.count}] = [ #{types.map { |t| "\"#{t}\"," }.join("\n")} ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -190,7 +190,7 @@ def render_enum_of_all_types(out, types) Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -234,7 +234,7 @@ def render_enum_of_all_types(out, types) } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -318,7 +318,7 @@ def render_enum_of_all_types(out, types) } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { @@ -407,7 +407,7 @@ def render_struct(out, struct) out.puts "" out.puts <<-EOS.strip_heredoc impl ReadXdr for #{name struct} { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -420,7 +420,7 @@ def render_struct(out, struct) } impl WriteXdr for #{name struct} { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { #{struct.members.map do |m| @@ -517,7 +517,7 @@ def render_enum(out, enum) } impl ReadXdr for #{name enum} { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -528,7 +528,7 @@ def render_enum(out, enum) } impl WriteXdr for #{name enum} { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -656,7 +656,7 @@ def render_union(out, union) impl Union<#{discriminant_type}> for #{name union} {} impl ReadXdr for #{name union} { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: #{discriminant_type} = <#{discriminant_type} as ReadXdr>::read_xdr(r)?; @@ -678,7 +678,7 @@ def render_union(out, union) } impl WriteXdr for #{name union} { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -816,7 +816,7 @@ def render_typedef(out, typedef) } impl ReadXdr for #{name typedef} { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = #{reference_to_call(typedef, typedef.type)}::read_xdr(r)?; @@ -827,7 +827,7 @@ def render_typedef(out, typedef) } impl WriteXdr for #{name typedef} { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } diff --git a/lib/xdrgen/generators/rust/src/types.rs b/lib/xdrgen/generators/rust/src/types.rs index 13a36c004..ccf139f32 100644 --- a/lib/xdrgen/generators/rust/src/types.rs +++ b/lib/xdrgen/generators/rust/src/types.rs @@ -33,14 +33,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -56,6 +59,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -72,13 +77,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -105,6 +131,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -157,6 +185,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -190,7 +221,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -207,7 +238,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -238,13 +269,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -297,6 +328,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -323,6 +362,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -396,7 +447,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -431,7 +482,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -471,7 +522,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -495,7 +546,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -544,7 +595,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -568,10 +619,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -593,13 +644,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -611,7 +662,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -622,7 +673,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -634,7 +685,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -645,7 +696,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -657,7 +708,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -668,7 +719,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -680,7 +731,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -691,35 +742,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -730,7 +781,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -740,7 +791,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -757,7 +808,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -772,35 +823,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -819,7 +870,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -833,7 +884,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -848,7 +899,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1207,7 +1258,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1234,7 +1285,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1254,7 +1305,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1274,7 +1325,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1647,7 +1698,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1674,7 +1725,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2050,7 +2101,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2077,7 +2128,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2123,7 +2174,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2143,10 +2194,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2255,7 +2304,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2767,3 +2816,335 @@ mod test { assert_eq!(v.to_option(), Some(1)); } } + +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} diff --git a/spec/output/generator_spec_rust/block_comments.x/MyXDR.rs b/spec/output/generator_spec_rust/block_comments.x/MyXDR.rs index 6bb6392dc..de07f4fcb 100644 --- a/spec/output/generator_spec_rust/block_comments.x/MyXDR.rs +++ b/spec/output/generator_spec_rust/block_comments.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/block_comments.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// AccountFlags is an XDR Enum defines as: /// /// ```text @@ -2856,7 +3237,7 @@ impl From for i32 { } impl ReadXdr for AccountFlags { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2867,7 +3248,7 @@ impl ReadXdr for AccountFlags { } impl WriteXdr for AccountFlags { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -2955,7 +3336,7 @@ impl Type { pub const VARIANTS: [TypeVariant; 1] = [ TypeVariant::AccountFlags, ]; pub const VARIANTS_STR: [&'static str; 1] = [ "AccountFlags", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2970,7 +3351,7 @@ impl Type { Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3014,7 +3395,7 @@ impl Type { } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3098,7 +3479,7 @@ impl Variants for Type { } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust/const.x/MyXDR.rs b/spec/output/generator_spec_rust/const.x/MyXDR.rs index 966b54a40..60fb45f34 100644 --- a/spec/output/generator_spec_rust/const.x/MyXDR.rs +++ b/spec/output/generator_spec_rust/const.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/const.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Foo is an XDR Const defines as: /// /// ```text @@ -2890,7 +3271,7 @@ TypeVariant::TestArray2, ]; pub const VARIANTS_STR: [&'static str; 2] = [ "TestArray", "TestArray2", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2906,7 +3287,7 @@ TypeVariant::TestArray2 => r.with_limited_depth(|r| Ok(Self::TestArray2(Box::new Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -2953,7 +3334,7 @@ TypeVariant::TestArray2 => Box::new(ReadXdrIter::<_, TestArray2>::new(dec, r.lim } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3042,7 +3423,7 @@ Self::TestArray2(_) => TypeVariant::TestArray2, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust/enum.x/MyXDR.rs b/spec/output/generator_spec_rust/enum.x/MyXDR.rs index 9968bbd13..ae497de05 100644 --- a/spec/output/generator_spec_rust/enum.x/MyXDR.rs +++ b/spec/output/generator_spec_rust/enum.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/enum.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// MessageType is an XDR Enum defines as: /// /// ```text @@ -2940,7 +3321,7 @@ Self::FbaMessage => "FbaMessage", } impl ReadXdr for MessageType { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2951,7 +3332,7 @@ Self::FbaMessage => "FbaMessage", } impl WriteXdr for MessageType { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3049,7 +3430,7 @@ Self::Blue => "Blue", } impl ReadXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -3060,7 +3441,7 @@ Self::Blue => "Blue", } impl WriteXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3158,7 +3539,7 @@ Self::Blue2 => "Blue2", } impl ReadXdr for Color2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -3169,7 +3550,7 @@ Self::Blue2 => "Blue2", } impl WriteXdr for Color2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3267,7 +3648,7 @@ Self::R3 => "R3", } impl ReadXdr for Color3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -3278,7 +3659,7 @@ Self::R3 => "R3", } impl WriteXdr for Color3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3393,7 +3774,7 @@ TypeVariant::Color3, ]; "Color2", "Color3", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -3411,7 +3792,7 @@ TypeVariant::Color3 => r.with_limited_depth(|r| Ok(Self::Color3(Box::new(Color3: Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3464,7 +3845,7 @@ TypeVariant::Color3 => Box::new(ReadXdrIter::<_, Color3>::new(dec, r.limits.clon } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3563,7 +3944,7 @@ Self::Color3(_) => TypeVariant::Color3, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust/nesting.x/MyXDR.rs b/spec/output/generator_spec_rust/nesting.x/MyXDR.rs index 4cdff6d8b..0074d1a05 100644 --- a/spec/output/generator_spec_rust/nesting.x/MyXDR.rs +++ b/spec/output/generator_spec_rust/nesting.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/nesting.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// UnionKey is an XDR Enum defines as: /// /// ```text @@ -2867,7 +3248,7 @@ Self::Offer => "Offer", } impl ReadXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2878,7 +3259,7 @@ Self::Offer => "Offer", } impl WriteXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -2912,7 +3293,7 @@ pub struct MyUnionOne { } impl ReadXdr for MyUnionOne { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2923,7 +3304,7 @@ impl ReadXdr for MyUnionOne { } impl WriteXdr for MyUnionOne { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.some_int.write_xdr(w)?; @@ -2951,7 +3332,7 @@ pub struct MyUnionTwo { } impl ReadXdr for MyUnionTwo { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2963,7 +3344,7 @@ foo: i32::read_xdr(r)?, } impl WriteXdr for MyUnionTwo { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.some_int.write_xdr(w)?; @@ -3066,7 +3447,7 @@ Self::Offer => UnionKey::Offer, impl Union for MyUnion {} impl ReadXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: UnionKey = ::read_xdr(r)?; @@ -3084,7 +3465,7 @@ UnionKey::Offer => Self::Offer, } impl WriteXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -3214,7 +3595,7 @@ TypeVariant::MyUnionTwo, ]; "MyUnionOne", "MyUnionTwo", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -3233,7 +3614,7 @@ TypeVariant::MyUnionTwo => r.with_limited_depth(|r| Ok(Self::MyUnionTwo(Box::new Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3289,7 +3670,7 @@ TypeVariant::MyUnionTwo => Box::new(ReadXdrIter::<_, MyUnionTwo>::new(dec, r.lim } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3393,7 +3774,7 @@ Self::MyUnionTwo(_) => TypeVariant::MyUnionTwo, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust/optional.x/MyXDR.rs b/spec/output/generator_spec_rust/optional.x/MyXDR.rs index b9454b181..83faca17e 100644 --- a/spec/output/generator_spec_rust/optional.x/MyXDR.rs +++ b/spec/output/generator_spec_rust/optional.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/optional.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Arr is an XDR Typedef defines as: /// /// ```text @@ -2808,7 +3189,7 @@ pub struct HasOptions { } impl ReadXdr for HasOptions { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2821,7 +3202,7 @@ third_option: Option::::read_xdr(r)?, } impl WriteXdr for HasOptions { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.first_option.write_xdr(w)?; @@ -2920,7 +3301,7 @@ TypeVariant::HasOptions, ]; pub const VARIANTS_STR: [&'static str; 2] = [ "Arr", "HasOptions", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2936,7 +3317,7 @@ TypeVariant::HasOptions => r.with_limited_depth(|r| Ok(Self::HasOptions(Box::new Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -2983,7 +3364,7 @@ TypeVariant::HasOptions => Box::new(ReadXdrIter::<_, HasOptions>::new(dec, r.lim } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3072,7 +3453,7 @@ Self::HasOptions(_) => TypeVariant::HasOptions, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust/struct.x/MyXDR.rs b/spec/output/generator_spec_rust/struct.x/MyXDR.rs index 696a6f49a..39123298f 100644 --- a/spec/output/generator_spec_rust/struct.x/MyXDR.rs +++ b/spec/output/generator_spec_rust/struct.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/struct.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Int64 is an XDR Typedef defines as: /// /// ```text @@ -2812,7 +3193,7 @@ pub struct MyStruct { } impl ReadXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2827,7 +3208,7 @@ max_string: StringM::<100>::read_xdr(r)?, } impl WriteXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.some_int.write_xdr(w)?; @@ -2928,7 +3309,7 @@ TypeVariant::MyStruct, ]; pub const VARIANTS_STR: [&'static str; 2] = [ "Int64", "MyStruct", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2944,7 +3325,7 @@ TypeVariant::MyStruct => r.with_limited_depth(|r| Ok(Self::MyStruct(Box::new(MyS Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -2991,7 +3372,7 @@ TypeVariant::MyStruct => Box::new(ReadXdrIter::<_, MyStruct>::new(dec, r.limits. } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3080,7 +3461,7 @@ Self::MyStruct(_) => TypeVariant::MyStruct, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust/test.x/MyXDR.rs b/spec/output/generator_spec_rust/test.x/MyXDR.rs index 5d234038f..f8397c2b4 100644 --- a/spec/output/generator_spec_rust/test.x/MyXDR.rs +++ b/spec/output/generator_spec_rust/test.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/test.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Uint512 is an XDR Typedef defines as: /// /// ```text @@ -2872,7 +3253,7 @@ impl AsRef<[u8; 64]> for Uint512 { } impl ReadXdr for Uint512 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = <[u8; 64]>::read_xdr(r)?; @@ -2883,7 +3264,7 @@ impl ReadXdr for Uint512 { } impl WriteXdr for Uint512 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -2962,7 +3343,7 @@ impl AsRef> for Uint513 { } impl ReadXdr for Uint513 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = BytesM::<64>::read_xdr(r)?; @@ -2973,7 +3354,7 @@ impl ReadXdr for Uint513 { } impl WriteXdr for Uint513 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3064,7 +3445,7 @@ impl AsRef for Uint514 { } impl ReadXdr for Uint514 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = BytesM::read_xdr(r)?; @@ -3075,7 +3456,7 @@ impl ReadXdr for Uint514 { } impl WriteXdr for Uint514 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3166,7 +3547,7 @@ impl AsRef> for Str { } impl ReadXdr for Str { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = StringM::<64>::read_xdr(r)?; @@ -3177,7 +3558,7 @@ impl ReadXdr for Str { } impl WriteXdr for Str { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3268,7 +3649,7 @@ impl AsRef for Str2 { } impl ReadXdr for Str2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = StringM::read_xdr(r)?; @@ -3279,7 +3660,7 @@ impl ReadXdr for Str2 { } impl WriteXdr for Str2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3428,7 +3809,7 @@ impl AsRef<[u8; 32]> for Hash { } impl ReadXdr for Hash { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = <[u8; 32]>::read_xdr(r)?; @@ -3439,7 +3820,7 @@ impl ReadXdr for Hash { } impl WriteXdr for Hash { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3517,7 +3898,7 @@ impl AsRef<[Hash; 12]> for Hashes1 { } impl ReadXdr for Hashes1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = <[Hash; 12]>::read_xdr(r)?; @@ -3528,7 +3909,7 @@ impl ReadXdr for Hashes1 { } impl WriteXdr for Hashes1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3607,7 +3988,7 @@ impl AsRef> for Hashes2 { } impl ReadXdr for Hashes2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = VecM::::read_xdr(r)?; @@ -3618,7 +3999,7 @@ impl ReadXdr for Hashes2 { } impl WriteXdr for Hashes2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3709,7 +4090,7 @@ impl AsRef> for Hashes3 { } impl ReadXdr for Hashes3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = VecM::::read_xdr(r)?; @@ -3720,7 +4101,7 @@ impl ReadXdr for Hashes3 { } impl WriteXdr for Hashes3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3810,7 +4191,7 @@ impl AsRef> for OptHash1 { } impl ReadXdr for OptHash1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = Option::::read_xdr(r)?; @@ -3821,7 +4202,7 @@ impl ReadXdr for OptHash1 { } impl WriteXdr for OptHash1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3862,7 +4243,7 @@ impl AsRef> for OptHash2 { } impl ReadXdr for OptHash2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = Option::::read_xdr(r)?; @@ -3873,7 +4254,7 @@ impl ReadXdr for OptHash2 { } impl WriteXdr for OptHash2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3941,7 +4322,7 @@ pub struct MyStruct { } impl ReadXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -3958,7 +4339,7 @@ field7: bool::read_xdr(r)?, } impl WriteXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.field1.write_xdr(w)?; @@ -3991,7 +4372,7 @@ pub struct LotsOfMyStructs { } impl ReadXdr for LotsOfMyStructs { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4002,7 +4383,7 @@ impl ReadXdr for LotsOfMyStructs { } impl WriteXdr for LotsOfMyStructs { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.members.write_xdr(w)?; @@ -4029,7 +4410,7 @@ pub struct HasStuff { } impl ReadXdr for HasStuff { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4040,7 +4421,7 @@ impl ReadXdr for HasStuff { } impl WriteXdr for HasStuff { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.data.write_xdr(w)?; @@ -4138,7 +4519,7 @@ Self::Green => "Green", } impl ReadXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -4149,7 +4530,7 @@ Self::Green => "Green", } impl WriteXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -4257,7 +4638,7 @@ Self::B2 => "B2", } impl ReadXdr for NesterNestedEnum { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -4268,7 +4649,7 @@ Self::B2 => "B2", } impl WriteXdr for NesterNestedEnum { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -4294,7 +4675,7 @@ pub struct NesterNestedStruct { } impl ReadXdr for NesterNestedStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4305,7 +4686,7 @@ impl ReadXdr for NesterNestedStruct { } impl WriteXdr for NesterNestedStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.blah.write_xdr(w)?; @@ -4387,7 +4768,7 @@ impl Variants for NesterNestedUnion { impl Union for NesterNestedUnion {} impl ReadXdr for NesterNestedUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: Color = ::read_xdr(r)?; @@ -4403,7 +4784,7 @@ impl ReadXdr for NesterNestedUnion { } impl WriteXdr for NesterNestedUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -4452,7 +4833,7 @@ pub struct Nester { } impl ReadXdr for Nester { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4465,7 +4846,7 @@ nested_union: NesterNestedUnion::read_xdr(r)?, } impl WriteXdr for Nester { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.nested_enum.write_xdr(w)?; @@ -4753,7 +5134,7 @@ TypeVariant::NesterNestedUnion, ]; "NesterNestedStruct", "NesterNestedUnion", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -4790,7 +5171,7 @@ TypeVariant::NesterNestedUnion => r.with_limited_depth(|r| Ok(Self::NesterNested Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -4900,7 +5281,7 @@ TypeVariant::NesterNestedUnion => Box::new(ReadXdrIter::<_, NesterNestedUnion>:: } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -5094,7 +5475,7 @@ Self::NesterNestedUnion(_) => TypeVariant::NesterNestedUnion, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust/union.x/MyXDR.rs b/spec/output/generator_spec_rust/union.x/MyXDR.rs index ad02b2966..68c0c2f96 100644 --- a/spec/output/generator_spec_rust/union.x/MyXDR.rs +++ b/spec/output/generator_spec_rust/union.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/union.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// SError is an XDR Typedef defines as: /// /// ```text @@ -2877,7 +3258,7 @@ Self::Multi => "Multi", } impl ReadXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2888,7 +3269,7 @@ Self::Multi => "Multi", } impl WriteXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -2978,7 +3359,7 @@ Self::Multi(_) => UnionKey::Multi, impl Union for MyUnion {} impl ReadXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: UnionKey = ::read_xdr(r)?; @@ -2995,7 +3376,7 @@ UnionKey::Multi => Self::Multi(VecM::::read_xdr(r)?), } impl WriteXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -3089,7 +3470,7 @@ Self::V1(_) => 1, impl Union for IntUnion {} impl ReadXdr for IntUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: i32 = ::read_xdr(r)?; @@ -3106,7 +3487,7 @@ Self::V1(_) => 1, } impl WriteXdr for IntUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -3155,7 +3536,7 @@ impl AsRef for IntUnion2 { } impl ReadXdr for IntUnion2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = IntUnion::read_xdr(r)?; @@ -3166,7 +3547,7 @@ impl ReadXdr for IntUnion2 { } impl WriteXdr for IntUnion2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3296,7 +3677,7 @@ TypeVariant::IntUnion2, ]; "IntUnion", "IntUnion2", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -3316,7 +3697,7 @@ TypeVariant::IntUnion2 => r.with_limited_depth(|r| Ok(Self::IntUnion2(Box::new(I Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3375,7 +3756,7 @@ TypeVariant::IntUnion2 => Box::new(ReadXdrIter::<_, IntUnion2>::new(dec, r.limit } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3484,7 +3865,7 @@ Self::IntUnion2(_) => TypeVariant::IntUnion2, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_jsonschema_impls/block_comments.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_jsonschema_impls/block_comments.x/MyXDR.rs index 6bb6392dc..de07f4fcb 100644 --- a/spec/output/generator_spec_rust_custom_jsonschema_impls/block_comments.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_jsonschema_impls/block_comments.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/block_comments.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// AccountFlags is an XDR Enum defines as: /// /// ```text @@ -2856,7 +3237,7 @@ impl From for i32 { } impl ReadXdr for AccountFlags { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2867,7 +3248,7 @@ impl ReadXdr for AccountFlags { } impl WriteXdr for AccountFlags { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -2955,7 +3336,7 @@ impl Type { pub const VARIANTS: [TypeVariant; 1] = [ TypeVariant::AccountFlags, ]; pub const VARIANTS_STR: [&'static str; 1] = [ "AccountFlags", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2970,7 +3351,7 @@ impl Type { Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3014,7 +3395,7 @@ impl Type { } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3098,7 +3479,7 @@ impl Variants for Type { } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_jsonschema_impls/const.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_jsonschema_impls/const.x/MyXDR.rs index 966b54a40..60fb45f34 100644 --- a/spec/output/generator_spec_rust_custom_jsonschema_impls/const.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_jsonschema_impls/const.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/const.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Foo is an XDR Const defines as: /// /// ```text @@ -2890,7 +3271,7 @@ TypeVariant::TestArray2, ]; pub const VARIANTS_STR: [&'static str; 2] = [ "TestArray", "TestArray2", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2906,7 +3287,7 @@ TypeVariant::TestArray2 => r.with_limited_depth(|r| Ok(Self::TestArray2(Box::new Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -2953,7 +3334,7 @@ TypeVariant::TestArray2 => Box::new(ReadXdrIter::<_, TestArray2>::new(dec, r.lim } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3042,7 +3423,7 @@ Self::TestArray2(_) => TypeVariant::TestArray2, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_jsonschema_impls/enum.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_jsonschema_impls/enum.x/MyXDR.rs index 62d418406..1d02858cd 100644 --- a/spec/output/generator_spec_rust_custom_jsonschema_impls/enum.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_jsonschema_impls/enum.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/enum.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// MessageType is an XDR Enum defines as: /// /// ```text @@ -2940,7 +3321,7 @@ Self::FbaMessage => "FbaMessage", } impl ReadXdr for MessageType { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2951,7 +3332,7 @@ Self::FbaMessage => "FbaMessage", } impl WriteXdr for MessageType { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3049,7 +3430,7 @@ Self::Blue => "Blue", } impl ReadXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -3060,7 +3441,7 @@ Self::Blue => "Blue", } impl WriteXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3157,7 +3538,7 @@ Self::Blue2 => "Blue2", } impl ReadXdr for Color2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -3168,7 +3549,7 @@ Self::Blue2 => "Blue2", } impl WriteXdr for Color2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3266,7 +3647,7 @@ Self::R3 => "R3", } impl ReadXdr for Color3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -3277,7 +3658,7 @@ Self::R3 => "R3", } impl WriteXdr for Color3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3392,7 +3773,7 @@ TypeVariant::Color3, ]; "Color2", "Color3", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -3410,7 +3791,7 @@ TypeVariant::Color3 => r.with_limited_depth(|r| Ok(Self::Color3(Box::new(Color3: Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3463,7 +3844,7 @@ TypeVariant::Color3 => Box::new(ReadXdrIter::<_, Color3>::new(dec, r.limits.clon } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3562,7 +3943,7 @@ Self::Color3(_) => TypeVariant::Color3, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_jsonschema_impls/nesting.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_jsonschema_impls/nesting.x/MyXDR.rs index 81767aed3..b0c8be9ca 100644 --- a/spec/output/generator_spec_rust_custom_jsonschema_impls/nesting.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_jsonschema_impls/nesting.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/nesting.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// UnionKey is an XDR Enum defines as: /// /// ```text @@ -2866,7 +3247,7 @@ Self::Offer => "Offer", } impl ReadXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2877,7 +3258,7 @@ Self::Offer => "Offer", } impl WriteXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -2911,7 +3292,7 @@ pub struct MyUnionOne { } impl ReadXdr for MyUnionOne { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2922,7 +3303,7 @@ impl ReadXdr for MyUnionOne { } impl WriteXdr for MyUnionOne { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.some_int.write_xdr(w)?; @@ -2950,7 +3331,7 @@ pub struct MyUnionTwo { } impl ReadXdr for MyUnionTwo { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2962,7 +3343,7 @@ foo: i32::read_xdr(r)?, } impl WriteXdr for MyUnionTwo { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.some_int.write_xdr(w)?; @@ -3064,7 +3445,7 @@ Self::Offer => UnionKey::Offer, impl Union for MyUnion {} impl ReadXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: UnionKey = ::read_xdr(r)?; @@ -3082,7 +3463,7 @@ UnionKey::Offer => Self::Offer, } impl WriteXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -3212,7 +3593,7 @@ TypeVariant::MyUnionTwo, ]; "MyUnionOne", "MyUnionTwo", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -3231,7 +3612,7 @@ TypeVariant::MyUnionTwo => r.with_limited_depth(|r| Ok(Self::MyUnionTwo(Box::new Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3287,7 +3668,7 @@ TypeVariant::MyUnionTwo => Box::new(ReadXdrIter::<_, MyUnionTwo>::new(dec, r.lim } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3391,7 +3772,7 @@ Self::MyUnionTwo(_) => TypeVariant::MyUnionTwo, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_jsonschema_impls/optional.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_jsonschema_impls/optional.x/MyXDR.rs index 0848b0a4a..d20779ca3 100644 --- a/spec/output/generator_spec_rust_custom_jsonschema_impls/optional.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_jsonschema_impls/optional.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/optional.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Arr is an XDR Typedef defines as: /// /// ```text @@ -2807,7 +3188,7 @@ pub struct HasOptions { } impl ReadXdr for HasOptions { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2820,7 +3201,7 @@ third_option: Option::::read_xdr(r)?, } impl WriteXdr for HasOptions { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.first_option.write_xdr(w)?; @@ -2919,7 +3300,7 @@ TypeVariant::HasOptions, ]; pub const VARIANTS_STR: [&'static str; 2] = [ "Arr", "HasOptions", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2935,7 +3316,7 @@ TypeVariant::HasOptions => r.with_limited_depth(|r| Ok(Self::HasOptions(Box::new Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -2982,7 +3363,7 @@ TypeVariant::HasOptions => Box::new(ReadXdrIter::<_, HasOptions>::new(dec, r.lim } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3071,7 +3452,7 @@ Self::HasOptions(_) => TypeVariant::HasOptions, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_jsonschema_impls/struct.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_jsonschema_impls/struct.x/MyXDR.rs index e43738634..0624e0af2 100644 --- a/spec/output/generator_spec_rust_custom_jsonschema_impls/struct.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_jsonschema_impls/struct.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/struct.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Int64 is an XDR Typedef defines as: /// /// ```text @@ -2811,7 +3192,7 @@ pub struct MyStruct { } impl ReadXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2826,7 +3207,7 @@ max_string: StringM::<100>::read_xdr(r)?, } impl WriteXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.some_int.write_xdr(w)?; @@ -2927,7 +3308,7 @@ TypeVariant::MyStruct, ]; pub const VARIANTS_STR: [&'static str; 2] = [ "Int64", "MyStruct", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2943,7 +3324,7 @@ TypeVariant::MyStruct => r.with_limited_depth(|r| Ok(Self::MyStruct(Box::new(MyS Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -2990,7 +3371,7 @@ TypeVariant::MyStruct => Box::new(ReadXdrIter::<_, MyStruct>::new(dec, r.limits. } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3079,7 +3460,7 @@ Self::MyStruct(_) => TypeVariant::MyStruct, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_jsonschema_impls/test.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_jsonschema_impls/test.x/MyXDR.rs index c16a6f4f9..a5810baad 100644 --- a/spec/output/generator_spec_rust_custom_jsonschema_impls/test.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_jsonschema_impls/test.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/test.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Uint512 is an XDR Typedef defines as: /// /// ```text @@ -2872,7 +3253,7 @@ impl AsRef<[u8; 64]> for Uint512 { } impl ReadXdr for Uint512 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = <[u8; 64]>::read_xdr(r)?; @@ -2883,7 +3264,7 @@ impl ReadXdr for Uint512 { } impl WriteXdr for Uint512 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -2962,7 +3343,7 @@ impl AsRef> for Uint513 { } impl ReadXdr for Uint513 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = BytesM::<64>::read_xdr(r)?; @@ -2973,7 +3354,7 @@ impl ReadXdr for Uint513 { } impl WriteXdr for Uint513 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3064,7 +3445,7 @@ impl AsRef for Uint514 { } impl ReadXdr for Uint514 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = BytesM::read_xdr(r)?; @@ -3075,7 +3456,7 @@ impl ReadXdr for Uint514 { } impl WriteXdr for Uint514 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3166,7 +3547,7 @@ impl AsRef> for Str { } impl ReadXdr for Str { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = StringM::<64>::read_xdr(r)?; @@ -3177,7 +3558,7 @@ impl ReadXdr for Str { } impl WriteXdr for Str { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3268,7 +3649,7 @@ impl AsRef for Str2 { } impl ReadXdr for Str2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = StringM::read_xdr(r)?; @@ -3279,7 +3660,7 @@ impl ReadXdr for Str2 { } impl WriteXdr for Str2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3428,7 +3809,7 @@ impl AsRef<[u8; 32]> for Hash { } impl ReadXdr for Hash { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = <[u8; 32]>::read_xdr(r)?; @@ -3439,7 +3820,7 @@ impl ReadXdr for Hash { } impl WriteXdr for Hash { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3517,7 +3898,7 @@ impl AsRef<[Hash; 12]> for Hashes1 { } impl ReadXdr for Hashes1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = <[Hash; 12]>::read_xdr(r)?; @@ -3528,7 +3909,7 @@ impl ReadXdr for Hashes1 { } impl WriteXdr for Hashes1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3607,7 +3988,7 @@ impl AsRef> for Hashes2 { } impl ReadXdr for Hashes2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = VecM::::read_xdr(r)?; @@ -3618,7 +3999,7 @@ impl ReadXdr for Hashes2 { } impl WriteXdr for Hashes2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3709,7 +4090,7 @@ impl AsRef> for Hashes3 { } impl ReadXdr for Hashes3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = VecM::::read_xdr(r)?; @@ -3720,7 +4101,7 @@ impl ReadXdr for Hashes3 { } impl WriteXdr for Hashes3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3810,7 +4191,7 @@ impl AsRef> for OptHash1 { } impl ReadXdr for OptHash1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = Option::::read_xdr(r)?; @@ -3821,7 +4202,7 @@ impl ReadXdr for OptHash1 { } impl WriteXdr for OptHash1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3862,7 +4243,7 @@ impl AsRef> for OptHash2 { } impl ReadXdr for OptHash2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = Option::::read_xdr(r)?; @@ -3873,7 +4254,7 @@ impl ReadXdr for OptHash2 { } impl WriteXdr for OptHash2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3940,7 +4321,7 @@ pub struct MyStruct { } impl ReadXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -3957,7 +4338,7 @@ field7: bool::read_xdr(r)?, } impl WriteXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.field1.write_xdr(w)?; @@ -3989,7 +4370,7 @@ pub struct LotsOfMyStructs { } impl ReadXdr for LotsOfMyStructs { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4000,7 +4381,7 @@ impl ReadXdr for LotsOfMyStructs { } impl WriteXdr for LotsOfMyStructs { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.members.write_xdr(w)?; @@ -4027,7 +4408,7 @@ pub struct HasStuff { } impl ReadXdr for HasStuff { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4038,7 +4419,7 @@ impl ReadXdr for HasStuff { } impl WriteXdr for HasStuff { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.data.write_xdr(w)?; @@ -4136,7 +4517,7 @@ Self::Green => "Green", } impl ReadXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -4147,7 +4528,7 @@ Self::Green => "Green", } impl WriteXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -4255,7 +4636,7 @@ Self::B2 => "B2", } impl ReadXdr for NesterNestedEnum { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -4266,7 +4647,7 @@ Self::B2 => "B2", } impl WriteXdr for NesterNestedEnum { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -4292,7 +4673,7 @@ pub struct NesterNestedStruct { } impl ReadXdr for NesterNestedStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4303,7 +4684,7 @@ impl ReadXdr for NesterNestedStruct { } impl WriteXdr for NesterNestedStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.blah.write_xdr(w)?; @@ -4385,7 +4766,7 @@ impl Variants for NesterNestedUnion { impl Union for NesterNestedUnion {} impl ReadXdr for NesterNestedUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: Color = ::read_xdr(r)?; @@ -4401,7 +4782,7 @@ impl ReadXdr for NesterNestedUnion { } impl WriteXdr for NesterNestedUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -4450,7 +4831,7 @@ pub struct Nester { } impl ReadXdr for Nester { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4463,7 +4844,7 @@ nested_union: NesterNestedUnion::read_xdr(r)?, } impl WriteXdr for Nester { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.nested_enum.write_xdr(w)?; @@ -4751,7 +5132,7 @@ TypeVariant::NesterNestedUnion, ]; "NesterNestedStruct", "NesterNestedUnion", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -4788,7 +5169,7 @@ TypeVariant::NesterNestedUnion => r.with_limited_depth(|r| Ok(Self::NesterNested Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -4898,7 +5279,7 @@ TypeVariant::NesterNestedUnion => Box::new(ReadXdrIter::<_, NesterNestedUnion>:: } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -5092,7 +5473,7 @@ Self::NesterNestedUnion(_) => TypeVariant::NesterNestedUnion, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_jsonschema_impls/union.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_jsonschema_impls/union.x/MyXDR.rs index d0b2a427a..aa6620724 100644 --- a/spec/output/generator_spec_rust_custom_jsonschema_impls/union.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_jsonschema_impls/union.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/union.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// SError is an XDR Typedef defines as: /// /// ```text @@ -2876,7 +3257,7 @@ Self::Multi => "Multi", } impl ReadXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2887,7 +3268,7 @@ Self::Multi => "Multi", } impl WriteXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -2976,7 +3357,7 @@ Self::Multi(_) => UnionKey::Multi, impl Union for MyUnion {} impl ReadXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: UnionKey = ::read_xdr(r)?; @@ -2993,7 +3374,7 @@ UnionKey::Multi => Self::Multi(VecM::::read_xdr(r)?), } impl WriteXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -3087,7 +3468,7 @@ Self::V1(_) => 1, impl Union for IntUnion {} impl ReadXdr for IntUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: i32 = ::read_xdr(r)?; @@ -3104,7 +3485,7 @@ Self::V1(_) => 1, } impl WriteXdr for IntUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -3153,7 +3534,7 @@ impl AsRef for IntUnion2 { } impl ReadXdr for IntUnion2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = IntUnion::read_xdr(r)?; @@ -3164,7 +3545,7 @@ impl ReadXdr for IntUnion2 { } impl WriteXdr for IntUnion2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3294,7 +3675,7 @@ TypeVariant::IntUnion2, ]; "IntUnion", "IntUnion2", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -3314,7 +3695,7 @@ TypeVariant::IntUnion2 => r.with_limited_depth(|r| Ok(Self::IntUnion2(Box::new(I Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3373,7 +3754,7 @@ TypeVariant::IntUnion2 => Box::new(ReadXdrIter::<_, IntUnion2>::new(dec, r.limit } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3482,7 +3863,7 @@ Self::IntUnion2(_) => TypeVariant::IntUnion2, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_str_impls/block_comments.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_str_impls/block_comments.x/MyXDR.rs index 6bb6392dc..de07f4fcb 100644 --- a/spec/output/generator_spec_rust_custom_str_impls/block_comments.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_str_impls/block_comments.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/block_comments.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// AccountFlags is an XDR Enum defines as: /// /// ```text @@ -2856,7 +3237,7 @@ impl From for i32 { } impl ReadXdr for AccountFlags { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2867,7 +3248,7 @@ impl ReadXdr for AccountFlags { } impl WriteXdr for AccountFlags { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -2955,7 +3336,7 @@ impl Type { pub const VARIANTS: [TypeVariant; 1] = [ TypeVariant::AccountFlags, ]; pub const VARIANTS_STR: [&'static str; 1] = [ "AccountFlags", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2970,7 +3351,7 @@ impl Type { Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3014,7 +3395,7 @@ impl Type { } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3098,7 +3479,7 @@ impl Variants for Type { } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_str_impls/const.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_str_impls/const.x/MyXDR.rs index 966b54a40..60fb45f34 100644 --- a/spec/output/generator_spec_rust_custom_str_impls/const.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_str_impls/const.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/const.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Foo is an XDR Const defines as: /// /// ```text @@ -2890,7 +3271,7 @@ TypeVariant::TestArray2, ]; pub const VARIANTS_STR: [&'static str; 2] = [ "TestArray", "TestArray2", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2906,7 +3287,7 @@ TypeVariant::TestArray2 => r.with_limited_depth(|r| Ok(Self::TestArray2(Box::new Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -2953,7 +3334,7 @@ TypeVariant::TestArray2 => Box::new(ReadXdrIter::<_, TestArray2>::new(dec, r.lim } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3042,7 +3423,7 @@ Self::TestArray2(_) => TypeVariant::TestArray2, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_str_impls/enum.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_str_impls/enum.x/MyXDR.rs index 3a63ac95d..d071894b1 100644 --- a/spec/output/generator_spec_rust_custom_str_impls/enum.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_str_impls/enum.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/enum.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// MessageType is an XDR Enum defines as: /// /// ```text @@ -2940,7 +3321,7 @@ Self::FbaMessage => "FbaMessage", } impl ReadXdr for MessageType { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2951,7 +3332,7 @@ Self::FbaMessage => "FbaMessage", } impl WriteXdr for MessageType { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3049,7 +3430,7 @@ Self::Blue => "Blue", } impl ReadXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -3060,7 +3441,7 @@ Self::Blue => "Blue", } impl WriteXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3158,7 +3539,7 @@ Self::Blue2 => "Blue2", } impl ReadXdr for Color2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -3169,7 +3550,7 @@ Self::Blue2 => "Blue2", } impl WriteXdr for Color2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3267,7 +3648,7 @@ Self::R3 => "R3", } impl ReadXdr for Color3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -3278,7 +3659,7 @@ Self::R3 => "R3", } impl WriteXdr for Color3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -3393,7 +3774,7 @@ TypeVariant::Color3, ]; "Color2", "Color3", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -3411,7 +3792,7 @@ TypeVariant::Color3 => r.with_limited_depth(|r| Ok(Self::Color3(Box::new(Color3: Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3464,7 +3845,7 @@ TypeVariant::Color3 => Box::new(ReadXdrIter::<_, Color3>::new(dec, r.limits.clon } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3563,7 +3944,7 @@ Self::Color3(_) => TypeVariant::Color3, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_str_impls/nesting.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_str_impls/nesting.x/MyXDR.rs index fd6a7b54f..5b6ec062b 100644 --- a/spec/output/generator_spec_rust_custom_str_impls/nesting.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_str_impls/nesting.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/nesting.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// UnionKey is an XDR Enum defines as: /// /// ```text @@ -2867,7 +3248,7 @@ Self::Offer => "Offer", } impl ReadXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2878,7 +3259,7 @@ Self::Offer => "Offer", } impl WriteXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -2912,7 +3293,7 @@ pub struct MyUnionOne { } impl ReadXdr for MyUnionOne { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2923,7 +3304,7 @@ impl ReadXdr for MyUnionOne { } impl WriteXdr for MyUnionOne { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.some_int.write_xdr(w)?; @@ -2951,7 +3332,7 @@ pub struct MyUnionTwo { } impl ReadXdr for MyUnionTwo { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2963,7 +3344,7 @@ foo: i32::read_xdr(r)?, } impl WriteXdr for MyUnionTwo { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.some_int.write_xdr(w)?; @@ -3066,7 +3447,7 @@ Self::Offer => UnionKey::Offer, impl Union for MyUnion {} impl ReadXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: UnionKey = ::read_xdr(r)?; @@ -3084,7 +3465,7 @@ UnionKey::Offer => Self::Offer, } impl WriteXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -3214,7 +3595,7 @@ TypeVariant::MyUnionTwo, ]; "MyUnionOne", "MyUnionTwo", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -3233,7 +3614,7 @@ TypeVariant::MyUnionTwo => r.with_limited_depth(|r| Ok(Self::MyUnionTwo(Box::new Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3289,7 +3670,7 @@ TypeVariant::MyUnionTwo => Box::new(ReadXdrIter::<_, MyUnionTwo>::new(dec, r.lim } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3393,7 +3774,7 @@ Self::MyUnionTwo(_) => TypeVariant::MyUnionTwo, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_str_impls/optional.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_str_impls/optional.x/MyXDR.rs index d4fed65d3..8e40dde5d 100644 --- a/spec/output/generator_spec_rust_custom_str_impls/optional.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_str_impls/optional.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/optional.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Arr is an XDR Typedef defines as: /// /// ```text @@ -2808,7 +3189,7 @@ pub struct HasOptions { } impl ReadXdr for HasOptions { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2821,7 +3202,7 @@ third_option: Option::::read_xdr(r)?, } impl WriteXdr for HasOptions { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.first_option.write_xdr(w)?; @@ -2920,7 +3301,7 @@ TypeVariant::HasOptions, ]; pub const VARIANTS_STR: [&'static str; 2] = [ "Arr", "HasOptions", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2936,7 +3317,7 @@ TypeVariant::HasOptions => r.with_limited_depth(|r| Ok(Self::HasOptions(Box::new Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -2983,7 +3364,7 @@ TypeVariant::HasOptions => Box::new(ReadXdrIter::<_, HasOptions>::new(dec, r.lim } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3072,7 +3453,7 @@ Self::HasOptions(_) => TypeVariant::HasOptions, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_str_impls/struct.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_str_impls/struct.x/MyXDR.rs index 882f72e13..cd458840a 100644 --- a/spec/output/generator_spec_rust_custom_str_impls/struct.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_str_impls/struct.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/struct.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Int64 is an XDR Typedef defines as: /// /// ```text @@ -2812,7 +3193,7 @@ pub struct MyStruct { } impl ReadXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -2827,7 +3208,7 @@ max_string: StringM::<100>::read_xdr(r)?, } impl WriteXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.some_int.write_xdr(w)?; @@ -2928,7 +3309,7 @@ TypeVariant::MyStruct, ]; pub const VARIANTS_STR: [&'static str; 2] = [ "Int64", "MyStruct", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -2944,7 +3325,7 @@ TypeVariant::MyStruct => r.with_limited_depth(|r| Ok(Self::MyStruct(Box::new(MyS Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -2991,7 +3372,7 @@ TypeVariant::MyStruct => Box::new(ReadXdrIter::<_, MyStruct>::new(dec, r.limits. } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3080,7 +3461,7 @@ Self::MyStruct(_) => TypeVariant::MyStruct, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_str_impls/test.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_str_impls/test.x/MyXDR.rs index 52d6e3d11..052796e22 100644 --- a/spec/output/generator_spec_rust_custom_str_impls/test.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_str_impls/test.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/test.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// Uint512 is an XDR Typedef defines as: /// /// ```text @@ -2872,7 +3253,7 @@ impl AsRef<[u8; 64]> for Uint512 { } impl ReadXdr for Uint512 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = <[u8; 64]>::read_xdr(r)?; @@ -2883,7 +3264,7 @@ impl ReadXdr for Uint512 { } impl WriteXdr for Uint512 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -2962,7 +3343,7 @@ impl AsRef> for Uint513 { } impl ReadXdr for Uint513 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = BytesM::<64>::read_xdr(r)?; @@ -2973,7 +3354,7 @@ impl ReadXdr for Uint513 { } impl WriteXdr for Uint513 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3064,7 +3445,7 @@ impl AsRef for Uint514 { } impl ReadXdr for Uint514 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = BytesM::read_xdr(r)?; @@ -3075,7 +3456,7 @@ impl ReadXdr for Uint514 { } impl WriteXdr for Uint514 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3166,7 +3547,7 @@ impl AsRef> for Str { } impl ReadXdr for Str { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = StringM::<64>::read_xdr(r)?; @@ -3177,7 +3558,7 @@ impl ReadXdr for Str { } impl WriteXdr for Str { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3268,7 +3649,7 @@ impl AsRef for Str2 { } impl ReadXdr for Str2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = StringM::read_xdr(r)?; @@ -3279,7 +3660,7 @@ impl ReadXdr for Str2 { } impl WriteXdr for Str2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3428,7 +3809,7 @@ impl AsRef<[u8; 32]> for Hash { } impl ReadXdr for Hash { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = <[u8; 32]>::read_xdr(r)?; @@ -3439,7 +3820,7 @@ impl ReadXdr for Hash { } impl WriteXdr for Hash { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3517,7 +3898,7 @@ impl AsRef<[Hash; 12]> for Hashes1 { } impl ReadXdr for Hashes1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = <[Hash; 12]>::read_xdr(r)?; @@ -3528,7 +3909,7 @@ impl ReadXdr for Hashes1 { } impl WriteXdr for Hashes1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3607,7 +3988,7 @@ impl AsRef> for Hashes2 { } impl ReadXdr for Hashes2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = VecM::::read_xdr(r)?; @@ -3618,7 +3999,7 @@ impl ReadXdr for Hashes2 { } impl WriteXdr for Hashes2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3709,7 +4090,7 @@ impl AsRef> for Hashes3 { } impl ReadXdr for Hashes3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = VecM::::read_xdr(r)?; @@ -3720,7 +4101,7 @@ impl ReadXdr for Hashes3 { } impl WriteXdr for Hashes3 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3810,7 +4191,7 @@ impl AsRef> for OptHash1 { } impl ReadXdr for OptHash1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = Option::::read_xdr(r)?; @@ -3821,7 +4202,7 @@ impl ReadXdr for OptHash1 { } impl WriteXdr for OptHash1 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3862,7 +4243,7 @@ impl AsRef> for OptHash2 { } impl ReadXdr for OptHash2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = Option::::read_xdr(r)?; @@ -3873,7 +4254,7 @@ impl ReadXdr for OptHash2 { } impl WriteXdr for OptHash2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3941,7 +4322,7 @@ pub struct MyStruct { } impl ReadXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -3958,7 +4339,7 @@ field7: bool::read_xdr(r)?, } impl WriteXdr for MyStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.field1.write_xdr(w)?; @@ -3991,7 +4372,7 @@ pub struct LotsOfMyStructs { } impl ReadXdr for LotsOfMyStructs { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4002,7 +4383,7 @@ impl ReadXdr for LotsOfMyStructs { } impl WriteXdr for LotsOfMyStructs { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.members.write_xdr(w)?; @@ -4029,7 +4410,7 @@ pub struct HasStuff { } impl ReadXdr for HasStuff { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4040,7 +4421,7 @@ impl ReadXdr for HasStuff { } impl WriteXdr for HasStuff { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.data.write_xdr(w)?; @@ -4138,7 +4519,7 @@ Self::Green => "Green", } impl ReadXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -4149,7 +4530,7 @@ Self::Green => "Green", } impl WriteXdr for Color { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -4257,7 +4638,7 @@ Self::B2 => "B2", } impl ReadXdr for NesterNestedEnum { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -4268,7 +4649,7 @@ Self::B2 => "B2", } impl WriteXdr for NesterNestedEnum { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -4294,7 +4675,7 @@ pub struct NesterNestedStruct { } impl ReadXdr for NesterNestedStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4305,7 +4686,7 @@ impl ReadXdr for NesterNestedStruct { } impl WriteXdr for NesterNestedStruct { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.blah.write_xdr(w)?; @@ -4387,7 +4768,7 @@ impl Variants for NesterNestedUnion { impl Union for NesterNestedUnion {} impl ReadXdr for NesterNestedUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: Color = ::read_xdr(r)?; @@ -4403,7 +4784,7 @@ impl ReadXdr for NesterNestedUnion { } impl WriteXdr for NesterNestedUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -4452,7 +4833,7 @@ pub struct Nester { } impl ReadXdr for Nester { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { Ok(Self{ @@ -4465,7 +4846,7 @@ nested_union: NesterNestedUnion::read_xdr(r)?, } impl WriteXdr for Nester { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.nested_enum.write_xdr(w)?; @@ -4753,7 +5134,7 @@ TypeVariant::NesterNestedUnion, ]; "NesterNestedStruct", "NesterNestedUnion", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -4790,7 +5171,7 @@ TypeVariant::NesterNestedUnion => r.with_limited_depth(|r| Ok(Self::NesterNested Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -4900,7 +5281,7 @@ TypeVariant::NesterNestedUnion => Box::new(ReadXdrIter::<_, NesterNestedUnion>:: } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -5094,7 +5475,7 @@ Self::NesterNestedUnion(_) => TypeVariant::NesterNestedUnion, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self { diff --git a/spec/output/generator_spec_rust_custom_str_impls/union.x/MyXDR.rs b/spec/output/generator_spec_rust_custom_str_impls/union.x/MyXDR.rs index 946a36cc9..7af495ec5 100644 --- a/spec/output/generator_spec_rust_custom_str_impls/union.x/MyXDR.rs +++ b/spec/output/generator_spec_rust_custom_str_impls/union.x/MyXDR.rs @@ -1,7 +1,7 @@ // Module is generated from: // spec/fixtures/generator/union.x -#![allow(clippy::missing_errors_doc, clippy::unreadable_literal)] +#![allow(clippy::missing_errors_doc, clippy::unreadable_literal, clippy::needless_question_mark)] /// `XDR_FILES_SHA256` is a list of pairs of source files and their SHA256 hashes. pub const XDR_FILES_SHA256: [(&str, &str); 1] = [ @@ -43,14 +43,17 @@ use std::string::FromUtf8Error; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; -// TODO: Add support for read/write xdr fns when std not available. - #[cfg(feature = "std")] use std::{ error, io, io::{BufRead, BufReader, Cursor, Read, Write}, }; +#[cfg(feature = "embedded_io")] +use embedded_io_extras::{Cursor, Error as _, ErrorType, Read, Write}; +#[cfg(feature = "embedded_io")] +use alloc::vec; + /// Error contains all errors returned by functions in this crate. It can be /// compared via `PartialEq`, however any contained IO errors will only be /// compared on their `ErrorKind`. @@ -66,6 +69,8 @@ pub enum Error { InvalidHex, #[cfg(feature = "std")] Io(io::Error), + #[cfg(feature = "embedded_io")] + Io(embedded_io_extras::ErrorKind), DepthLimitExceeded, #[cfg(feature = "serde_json")] Json(serde_json::Error), @@ -82,13 +87,34 @@ impl PartialEq for Error { // case for comparing errors outputted by the XDR library is for // error case testing, and a lack of the ability to compare has a // detrimental affect on failure testing, so this is a tradeoff. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] (Self::Io(l), Self::Io(r)) => l.kind() == r.kind(), _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } } +#[cfg(feature = "embedded_io")] +impl embedded_io_extras::Error for Error { + fn kind(&self) -> embedded_io_extras::ErrorKind { + match self { + Self::Io(e) => *e, + _ => embedded_io_extras::ErrorKind::Other, + } + } +} + +#[cfg(feature = "embedded_io")] +impl From> for Error { + fn from(value: embedded_io_extras::ReadExactError) -> Self { + match value { + // TODO: maybe we should map the error to a more specific error? + embedded_io_extras::ReadExactError::UnexpectedEof => Error::Io(embedded_io_extras::ErrorKind::Other), + embedded_io_extras::ReadExactError::Other(e) => e + } + } +} + #[cfg(feature = "std")] impl error::Error for Error { #[must_use] @@ -115,6 +141,8 @@ impl fmt::Display for Error { Error::InvalidHex => write!(f, "hex invalid"), #[cfg(feature = "std")] Error::Io(e) => write!(f, "{e}"), + #[cfg(feature = "embedded_io")] + Error::Io(_) => write!(f, "io error"), Error::DepthLimitExceeded => write!(f, "depth limit exceeded"), #[cfg(feature = "serde_json")] Error::Json(e) => write!(f, "{e}"), @@ -167,6 +195,9 @@ impl From for () { #[allow(dead_code)] type Result = core::result::Result; +#[cfg(feature = "embedded_io")] +impl ErrorType for Limited { type Error = Error; } + /// Name defines types that assign a static name to their value, such as the /// name given to an identifier in an XDR enum, or the name given to the case in /// a union. @@ -200,7 +231,7 @@ where /// `Limits` contains the limits that a limited reader or writer will be /// constrained to. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Limits { /// Defines the maximum depth for recursive calls in `Read/WriteXdr` to @@ -217,7 +248,7 @@ pub struct Limits { pub len: usize, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limits { #[must_use] pub fn none() -> Self { @@ -248,13 +279,13 @@ impl Limits { /// /// Intended for use with readers and writers and limiting their reads and /// writes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] pub struct Limited { pub inner: L, pub(crate) limits: Limits, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] impl Limited { /// Constructs a new `Limited`. /// @@ -307,6 +338,14 @@ impl Read for Limited { } } +#[cfg(feature = "embedded_io")] +impl Read for Limited { + /// Forwards the read operation to the wrapped object. + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.inner.read(buf).map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] impl BufRead for Limited { /// Forwards the read operation to the wrapped object. @@ -333,6 +372,18 @@ impl Write for Limited { } } +#[cfg(feature = "embedded_io")] +impl Write for Limited { + /// Forwards the write operation to the wrapped object. + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.inner.write(buf).map_err(|e| Error::Io(e.kind())) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.inner.flush().map_err(|e| Error::Io(e.kind())) + } +} + #[cfg(feature = "std")] pub struct ReadXdrIter { reader: Limited>, @@ -406,7 +457,7 @@ where /// /// Use [`ReadXdR: Read_xdr_to_end`] when the intent is for all bytes in the /// read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result; /// Construct the type from the XDR bytes base64 encoded. @@ -441,7 +492,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_to_end(r: &mut Limited) -> Result { let s = Self::read_xdr(r)?; // Check that any further reads, such as this read of one byte, read no @@ -481,7 +532,7 @@ where /// /// Use [`ReadXdR: Read_xdr_into_to_end`] when the intent is for all bytes /// in the read implementation to be consumed by the read. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into(&mut self, r: &mut Limited) -> Result<()> { *self = Self::read_xdr(r)?; Ok(()) @@ -505,7 +556,7 @@ where /// /// All implementations should continue if the read implementation returns /// [`ErrorKind::Interrupted`](std::io::ErrorKind::Interrupted). - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr_into_to_end(&mut self, r: &mut Limited) -> Result<()> { Self::read_xdr_into(self, r)?; // Check that any further reads, such as this read of one byte, read no @@ -554,7 +605,7 @@ where /// /// An error is returned if the bytes are not completely consumed by the /// deserialization. - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn from_xdr(bytes: impl AsRef<[u8]>, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(&mut cursor)?; @@ -578,10 +629,10 @@ where } pub trait WriteXdr { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()>; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn to_xdr(&self, limits: Limits) -> Result> { let mut cursor = Limited::new(Cursor::new(vec![]), limits); self.write_xdr(&mut cursor)?; @@ -603,13 +654,13 @@ pub trait WriteXdr { /// `Pad_len` returns the number of bytes to pad an XDR value of the given /// length to make the final serialized size a multiple of 4. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "embedded_io"))] fn pad_len(len: usize) -> usize { (4 - (len % 4)) % 4 } impl ReadXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -621,7 +672,7 @@ impl ReadXdr for i32 { } impl WriteXdr for i32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -632,7 +683,7 @@ impl WriteXdr for i32 { } impl ReadXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 4]; r.with_limited_depth(|r| { @@ -644,7 +695,7 @@ impl ReadXdr for u32 { } impl WriteXdr for u32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 4] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -655,7 +706,7 @@ impl WriteXdr for u32 { } impl ReadXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -667,7 +718,7 @@ impl ReadXdr for i64 { } impl WriteXdr for i64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -678,7 +729,7 @@ impl WriteXdr for i64 { } impl ReadXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { let mut b = [0u8; 8]; r.with_limited_depth(|r| { @@ -690,7 +741,7 @@ impl ReadXdr for u64 { } impl WriteXdr for u64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { let b: [u8; 8] = self.to_be_bytes(); w.with_limited_depth(|w| { @@ -701,35 +752,35 @@ impl WriteXdr for u64 { } impl ReadXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f32 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { todo!() } } impl WriteXdr for f64 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { todo!() } } impl ReadXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -740,7 +791,7 @@ impl ReadXdr for bool { } impl WriteXdr for bool { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i = u32::from(*self); // true = 1, false = 0 @@ -750,7 +801,7 @@ impl WriteXdr for bool { } impl ReadXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = u32::read_xdr(r)?; @@ -767,7 +818,7 @@ impl ReadXdr for Option { } impl WriteXdr for Option { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { if let Some(t) = self { @@ -782,35 +833,35 @@ impl WriteXdr for Option { } impl ReadXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| Ok(Box::new(T::read_xdr(r)?))) } } impl WriteXdr for Box { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| T::write_xdr(self, w)) } } impl ReadXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(_r: &mut Limited) -> Result { Ok(()) } } impl WriteXdr for () { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, _w: &mut Limited) -> Result<()> { Ok(()) } } impl ReadXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { r.consume_len(N)?; @@ -829,7 +880,7 @@ impl ReadXdr for [u8; N] { } impl WriteXdr for [u8; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { w.consume_len(N)?; @@ -843,7 +894,7 @@ impl WriteXdr for [u8; N] { } impl ReadXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let mut vec = Vec::with_capacity(N); @@ -858,7 +909,7 @@ impl ReadXdr for [T; N] { } impl WriteXdr for [T; N] { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { for t in self { @@ -1217,7 +1268,7 @@ impl<'a, const MAX: u32> TryFrom<&'a VecM> for &'a str { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1244,7 +1295,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1264,7 +1315,7 @@ impl WriteXdr for VecM { } impl ReadXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len = u32::read_xdr(r)?; @@ -1284,7 +1335,7 @@ impl ReadXdr for VecM { } impl WriteXdr for VecM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -1657,7 +1708,7 @@ impl<'a, const MAX: u32> TryFrom<&'a BytesM> for &'a str { } impl ReadXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -1684,7 +1735,7 @@ impl ReadXdr for BytesM { } impl WriteXdr for BytesM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2060,7 +2111,7 @@ impl<'a, const MAX: u32> TryFrom<&'a StringM> for &'a str { } impl ReadXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let len: u32 = u32::read_xdr(r)?; @@ -2087,7 +2138,7 @@ impl ReadXdr for StringM { } impl WriteXdr for StringM { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let len: u32 = self.len().try_into().map_err(|_| Error::LengthExceedsMax)?; @@ -2133,7 +2184,7 @@ impl ReadXdr for Frame where T: ReadXdr, { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { // Read the frame header value that contains 1 flag-bit and a 33-bit length. // - The 1 flag bit is 0 when there are more frames for the same record. @@ -2153,10 +2204,8 @@ where } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod tests { - use std::io::Cursor; - use super::*; #[test] @@ -2265,7 +2314,7 @@ mod tests { } } -#[cfg(all(test, feature = "std"))] +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] mod test { use super::*; @@ -2778,6 +2827,338 @@ mod test { } } +#[cfg(all(test, any(feature = "std", feature = "embedded_io")))] +mod test_io { + // We will use different IO libraries according to the feature, + // we hope the selected IO library can work as expected + + use super::*; + + macro_rules! assert_unexpected_eof { + ($result:expr) => {{ + let err = $result.unwrap_err(); + #[cfg(feature = "std")] + { + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + #[cfg(feature = "embedded_io")] + { + assert_eq!(err, embedded_io_extras::ReadExactError::UnexpectedEof); + } + }}; + } + + #[test] + fn test_cursor_read_exact_success() { + let data = b"The quick brown fox jumps over the lazy dog."; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + assert_eq!(cursor.position(), 19); + } + + #[test] + fn test_cursor_read_exact_less_than_available() { + let data = b"Hello, Stellar!"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn test_cursor_read_exact_exact_eof() { + let data = b"Data"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire buffer + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + assert_eq!(cursor.position(), 4); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = cursor.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 4); // Position should remain at EOF + } + + #[test] + fn test_cursor_read_exact_past_eof() { + let data = b"Short"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte buffer + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 5); // Position should move to EOF + } + + #[test] + fn test_cursor_read_exact_empty_buffer() { + let data = b"Non-empty"; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 0]; + + // Attempt to read zero bytes + assert!(cursor.read_exact(&mut buffer).is_ok()); + assert_eq!(cursor.position(), 0); // Position should remain unchanged + } + + #[test] + fn test_cursor_read_exact_from_empty_cursor() { + let data: &[u8] = b""; + let mut cursor = Cursor::new(data); + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty cursor + let result = cursor.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + assert_eq!(cursor.position(), 0); // Position remains at 0 + } + + #[test] + fn test_cursor_read_exact_multiple_reads() { + let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut cursor = Cursor::new(data); + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(cursor.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + assert_eq!(cursor.position(), 10); + + // Second read: 10 bytes + assert!(cursor.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + assert_eq!(cursor.position(), 20); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = cursor.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + // read_exact makes no promises about the content of the buffer on error + // https://github.com/rust-lang/rust/blob/84ac80f1921afc243d71fd0caaa4f2838c294102/library/std/src/io/impls.rs#L287 + // assert_eq!(&buffer3[..6], b"UVWXYZ"); + assert_eq!(cursor.position(), 26); // Position should be at EOF + } + + #[test] + fn test_slice_read_exact_success() { + let data: &[u8] = b"The quick brown fox jumps over the lazy dog."; + let mut reader = data; + let mut buffer = [0u8; 19]; + + // Attempt to read exactly 19 bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"The quick brown fox"); + } + + #[test] + fn test_slice_read_exact_less_than_available() { + let data: &[u8] = b"Hello, Stellar!"; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read 5 bytes from the start + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Hello"); + } + + #[test] + fn test_slice_read_exact_exact_eof() { + let data: &[u8] = b"Data"; + let mut reader = data; + let mut buffer = [0u8; 4]; + + // Attempt to read exactly 4 bytes, which is the entire slice + assert!(reader.read_exact(&mut buffer).is_ok()); + assert_eq!(&buffer, b"Data"); + + // Further attempt to read should fail with EOF + let mut buffer_eof = [0u8; 1]; + let result = reader.read_exact(&mut buffer_eof); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_past_eof() { + let data: &[u8] = b"Short"; + let mut reader = data; + let mut buffer = [0u8; 10]; // Requesting more bytes than available + + // Attempt to read 10 bytes from a 5-byte slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_empty_buffer() { + let data: &[u8] = b"Non-empty"; + let mut reader = data; + let mut buffer = [0u8; 0]; // Zero-length buffer + + // Attempt to read zero bytes + assert!(reader.read_exact(&mut buffer).is_ok()); + } + + #[test] + fn test_slice_read_exact_from_empty_slice() { + let data: &[u8] = b""; + let mut reader = data; + let mut buffer = [0u8; 5]; + + // Attempt to read from an empty slice + let result = reader.read_exact(&mut buffer); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_slice_read_exact_multiple_reads() { + let data: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let mut reader = data; + + let mut buffer1 = [0u8; 10]; + let mut buffer2 = [0u8; 10]; + let mut buffer3 = [0u8; 10]; // Only 6 bytes left + + // First read: 10 bytes + assert!(reader.read_exact(&mut buffer1).is_ok()); + assert_eq!(&buffer1, b"ABCDEFGHIJ"); + + // Second read: 10 bytes + assert!(reader.read_exact(&mut buffer2).is_ok()); + assert_eq!(&buffer2, b"KLMNOPQRST"); + + // Third read: Attempt to read 10 bytes, but only 6 are available + let result = reader.read_exact(&mut buffer3); + assert!(result.is_err()); + assert_unexpected_eof!(result); + } + + #[test] + fn test_cursor_write_success() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b"Hello, Stellar!"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_multiple_writes() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data1: &[u8] = b"Hello, "; + let write_data2: &[u8] = b"Rust!"; + + // First write + assert!(cursor.write_all(write_data1).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data1); + assert_eq!(cursor.position(), write_data1.len() as u64); + + // Second write + assert!(cursor.write_all(write_data2).is_ok()); + let mut expected = Vec::new(); + expected.extend_from_slice(write_data1); + expected.extend_from_slice(write_data2); + assert_eq!(&cursor.get_ref()[..], expected.as_slice()); + assert_eq!(cursor.position(), expected.len() as u64); + } + + #[test] + fn test_cursor_write_empty() { + let data = Vec::with_capacity(100); + let mut cursor = Cursor::new(data); + let write_data = b""; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.get_ref().len(), 0); + assert_eq!(cursor.position(), 0); + } + + #[test] + fn test_cursor_write_at_position() { + let mut data = [0u8; 10]; + { + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Rust"; + + // Move cursor to position 6 + cursor.set_position(6); + assert_eq!(cursor.position(), 6); + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(cursor.position(), 10); + } + + let mut expected = [0u8; 10]; + expected[6..10].copy_from_slice(b"Rust"); + assert_eq!(&data[..], &expected[..]); + } + + #[test] + fn test_cursor_write_overflow() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Too long data"; + + let result = cursor.write_all(write_data); + assert!(result.is_err()); + + #[cfg(feature = "std")] + assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero); + #[cfg(feature = "embedded_io")] + assert_eq!( + result.unwrap_err().kind(), + embedded_io_extras::ErrorKind::WriteZero + ); + } + + #[test] + fn test_cursor_write_from_empty_cursor() { + let data = Vec::new(); + let mut cursor = Cursor::new(data); + let write_data = b"Initial Data"; + + assert!(cursor.write_all(write_data).is_ok()); + assert_eq!(&cursor.get_ref()[..], write_data); + assert_eq!(cursor.position(), write_data.len() as u64); + } + + #[test] + fn test_cursor_write_partial() { + let mut data = [0u8; 5]; + let mut cursor = Cursor::new(&mut data[..]); + let write_data = b"Hello, Stellar!"; + + // Attempt to write data that exceeds the buffer size + // using `write` instead of `write_all` + let result = cursor.write(&write_data[..7]).unwrap(); + assert_eq!(result, 5); + assert_eq!(&cursor.get_ref()[..], b"Hello"); + } +} + /// SError is an XDR Typedef defines as: /// /// ```text @@ -2877,7 +3258,7 @@ Self::Multi => "Multi", } impl ReadXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let e = i32::read_xdr(r)?; @@ -2888,7 +3269,7 @@ Self::Multi => "Multi", } impl WriteXdr for UnionKey { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { let i: i32 = (*self).into(); @@ -2978,7 +3359,7 @@ Self::Multi(_) => UnionKey::Multi, impl Union for MyUnion {} impl ReadXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: UnionKey = ::read_xdr(r)?; @@ -2995,7 +3376,7 @@ UnionKey::Multi => Self::Multi(VecM::::read_xdr(r)?), } impl WriteXdr for MyUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -3089,7 +3470,7 @@ Self::V1(_) => 1, impl Union for IntUnion {} impl ReadXdr for IntUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let dv: i32 = ::read_xdr(r)?; @@ -3106,7 +3487,7 @@ Self::V1(_) => 1, } impl WriteXdr for IntUnion { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w| { self.discriminant().write_xdr(w)?; @@ -3155,7 +3536,7 @@ impl AsRef for IntUnion2 { } impl ReadXdr for IntUnion2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn read_xdr(r: &mut Limited) -> Result { r.with_limited_depth(|r| { let i = IntUnion::read_xdr(r)?; @@ -3166,7 +3547,7 @@ impl ReadXdr for IntUnion2 { } impl WriteXdr for IntUnion2 { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] fn write_xdr(&self, w: &mut Limited) -> Result<()> { w.with_limited_depth(|w|{ self.0.write_xdr(w) }) } @@ -3296,7 +3677,7 @@ TypeVariant::IntUnion2, ]; "IntUnion", "IntUnion2", ]; - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] pub fn read_xdr(v: TypeVariant, r: &mut Limited) -> Result { match v { @@ -3316,7 +3697,7 @@ TypeVariant::IntUnion2 => r.with_limited_depth(|r| Ok(Self::IntUnion2(Box::new(I Ok(t) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn read_xdr_to_end(v: TypeVariant, r: &mut Limited) -> Result { let s = Self::read_xdr(v, r)?; // Check that any further reads, such as this read of one byte, read no @@ -3375,7 +3756,7 @@ TypeVariant::IntUnion2 => Box::new(ReadXdrIter::<_, IntUnion2>::new(dec, r.limit } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] pub fn from_xdr>(v: TypeVariant, bytes: B, limits: Limits) -> Result { let mut cursor = Limited::new(Cursor::new(bytes.as_ref()), limits); let t = Self::read_xdr_to_end(v, &mut cursor)?; @@ -3484,7 +3865,7 @@ Self::IntUnion2(_) => TypeVariant::IntUnion2, } impl WriteXdr for Type { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "embedded_io"))] #[allow(clippy::too_many_lines)] fn write_xdr(&self, w: &mut Limited) -> Result<()> { match self {