diff --git a/Cargo.lock b/Cargo.lock index 829b0568d..63b85715b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1829,9 +1829,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128-tokio" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e94e2f8143ae63a07c670a27f2fc42a333293c249566f8d4cd6609689656bb" +checksum = "f1411e775c0f76d980266d80374665fc9c3c50cb75b1897ecce8170cdd27f435" dependencies = [ "tokio", "tokio-util", @@ -3977,9 +3977,9 @@ dependencies = [ [[package]] name = "wasm-tokio" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89e5174a1312bd31d081a1dd6db8e48e2dc680b2988aba43befabb8459ea999" +checksum = "44af5abbb5bbba6903c16c26c57d58ccd4ffe56b8e0cbb65536ace3d6ac7ab9f" dependencies = [ "leb128-tokio", "tokio", @@ -5014,7 +5014,7 @@ dependencies = [ [[package]] name = "wrpc-wave" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 192bfafa7..24c600ed7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -187,7 +187,7 @@ url = { version = "2" } uuid = { version = "1", default-features = false } wasi = { version = "0.14", default-features = false } wasi-preview1-component-adapter-provider = { version = "49", default-features = false } -wasm-tokio = { version = "0.6.1", default-features = false } +wasm-tokio = { version = "0.7.0", default-features = false } wasm-wave = { version = "0.258", default-features = false } wasmparser = { version = "0.261", default-features = false } wasmtime = { version = "49", default-features = false } diff --git a/crates/transport/src/frame/codec.rs b/crates/transport/src/frame/codec.rs index 19de20493..5c3ff86c5 100644 --- a/crates/transport/src/frame/codec.rs +++ b/crates/transport/src/frame/codec.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use bytes::{Bytes, BytesMut}; use tracing::{instrument, trace}; -use wasm_tokio::{Leb128DecoderU32, Leb128DecoderU64, Leb128Encoder}; +use wasm_tokio::{DEFAULT_MAX_INITIAL_CAPACITY, Leb128DecoderU32, Leb128DecoderU64, Leb128Encoder}; -use super::{Frame, FrameRef}; +use crate::frame::{Frame, FrameRef, MAX_INITIAL_PATH_CAPACITY}; /// [Frame] decoder pub struct Decoder { @@ -31,7 +31,7 @@ impl Decoder { impl Default for Decoder { fn default() -> Self { - Self::new(32, u32::MAX.into()) + Self::new(u32::MAX, u32::MAX.into()) } } @@ -59,18 +59,11 @@ impl tokio_util::codec::Decoder for Decoder { ), )); } - let n = n - .try_into() + let n = usize::try_from(n) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; self.path_cap = n; - Vec::with_capacity(n) + Vec::with_capacity(n.min(MAX_INITIAL_PATH_CAPACITY)) }; - let n = self.path_cap.saturating_sub(src.len()); - if n > 0 { - src.reserve(n); - self.path = Some(path); - return Ok(None); - } while self.path_cap > 0 { trace!(self.path_cap, "decoding path element"); let Some(i) = Leb128DecoderU32.decode(src)? else { @@ -78,8 +71,7 @@ impl tokio_util::codec::Decoder for Decoder { return Ok(None); }; trace!(i, "decoded path element"); - let i = i - .try_into() + let i = usize::try_from(i) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; path.push(i); self.path_cap -= 1; @@ -100,8 +92,7 @@ impl tokio_util::codec::Decoder for Decoder { ), )); } - let n = n - .try_into() + let n = usize::try_from(n) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; self.data_len = n; if n == 0 { @@ -113,7 +104,7 @@ impl tokio_util::codec::Decoder for Decoder { } let n = self.data_len.saturating_sub(src.len()); if n > 0 { - src.reserve(n); + src.reserve(n.min(DEFAULT_MAX_INITIAL_CAPACITY)); self.path = Some(path); return Ok(None); } diff --git a/crates/transport/src/frame/conn/mod.rs b/crates/transport/src/frame/conn/mod.rs index 2b7c75b1e..b3218937e 100644 --- a/crates/transport/src/frame/conn/mod.rs +++ b/crates/transport/src/frame/conn/mod.rs @@ -5,7 +5,7 @@ use core::task::{Context, Poll, ready}; use std::sync::Arc; -use bytes::{Buf as _, BufMut as _, Bytes, BytesMut}; +use bytes::{Buf as _, Bytes, BytesMut}; use futures::Sink as _; use pin_project_lite::pin_project; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt as _}; @@ -16,7 +16,11 @@ use tokio_util::codec::Encoder; use tokio_util::io::StreamReader; use tokio_util::sync::PollSender; use tracing::{Instrument as _, Span, debug, error, instrument, trace}; -use wasm_tokio::{AsyncReadCore as _, AsyncReadLeb128 as _, Leb128Encoder}; +use wasm_tokio::{ + AsyncReadCore as _, AsyncReadLeb128 as _, DEFAULT_MAX_INITIAL_CAPACITY, Leb128Encoder, +}; + +use crate::frame::MAX_INITIAL_PATH_CAPACITY; mod client; mod server; @@ -581,14 +585,13 @@ async fn ingress( let n = AsyncReadExt::chain([b].as_slice(), &mut rx) .read_u32_leb128() .await?; - let n = n - .try_into() + let n = usize::try_from(n) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; trace!(n, "read path length"); let tx = if n == 0 { param_tx } else { - let mut path = Vec::with_capacity(n); + let mut path = Vec::with_capacity(n.min(MAX_INITIAL_PATH_CAPACITY)); for i in 0..n { trace!(i, "reading path element"); let p = rx.read_u32_leb128().await?; @@ -611,18 +614,24 @@ async fn ingress( }; trace!("reading data length"); let n = rx.read_u32_leb128().await?; - let n = n - .try_into() - .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; trace!(n, "read data length"); - let mut buf = BytesMut::with_capacity(n); - buf.put_bytes(0, n); - trace!("reading data"); - rx.read_exact(&mut buf).await?; - trace!(?buf, "read data"); - tx.send(Ok(buf.freeze())).await.map_err(|_| { - std::io::Error::new(std::io::ErrorKind::BrokenPipe, "stream receiver closed") - })?; + let mut data = (&mut rx).take(n.into()); + while data.limit() > 0 { + let k = usize::try_from(data.limit()) + .unwrap_or(usize::MAX) + .min(DEFAULT_MAX_INITIAL_CAPACITY); + let mut buf = BytesMut::with_capacity(k); + trace!(len = k, "reading data chunk"); + while buf.len() < k { + if data.read_buf(&mut buf).await? == 0 { + return Err(std::io::ErrorKind::UnexpectedEof.into()); + } + } + trace!(?buf, "read data chunk"); + tx.send(Ok(buf.freeze())).await.map_err(|_| { + std::io::Error::new(std::io::ErrorKind::BrokenPipe, "stream receiver closed") + })?; + } } } @@ -676,3 +685,48 @@ pub trait ConnHandler { } impl ConnHandler for () {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test_log::test(tokio::test)] + async fn ingress_truncated_path() { + let index = std::sync::Mutex::new(IndexTrie::Empty); + let (tx, _rx) = mpsc::channel(1); + let err = ingress([0x02, 0x00].as_slice(), &index, &tx) + .await + .unwrap_err(); + assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof); + } + + #[test_log::test(tokio::test)] + async fn ingress_chunked_data() { + let index = std::sync::Mutex::new(IndexTrie::Empty); + let (tx, mut rx) = mpsc::channel(4); + let n = DEFAULT_MAX_INITIAL_CAPACITY + 1; + let mut frame = vec![0x00, 0x81, 0x80, 0x40]; + frame.resize(frame.len() + n, 0x42); + ingress(frame.as_slice(), &index, &tx).await.unwrap(); + drop(tx); + let mut chunks = vec![]; + while let Some(chunk) = rx.recv().await { + chunks.push(chunk.unwrap().len()); + } + assert_eq!(chunks, [DEFAULT_MAX_INITIAL_CAPACITY, 1]); + } + + #[test_log::test(tokio::test)] + async fn ingress_truncated_data() { + let index = std::sync::Mutex::new(IndexTrie::Empty); + let (tx, _rx) = mpsc::channel(1); + let err = ingress( + [0x00, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x42].as_slice(), + &index, + &tx, + ) + .await + .unwrap_err(); + assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof); + } +} diff --git a/crates/transport/src/frame/mod.rs b/crates/transport/src/frame/mod.rs index 05b0319ce..b131be685 100644 --- a/crates/transport/src/frame/mod.rs +++ b/crates/transport/src/frame/mod.rs @@ -21,6 +21,8 @@ pub use oneshot::*; /// Framing protocol version pub const PROTOCOL: u8 = 0; +const MAX_INITIAL_PATH_CAPACITY: usize = 32; + /// Owned wRPC frame #[derive(Clone, Debug, Eq, PartialEq)] pub struct Frame { diff --git a/crates/transport/src/value.rs b/crates/transport/src/value.rs index 748596ef7..9eccf5c39 100644 --- a/crates/transport/src/value.rs +++ b/crates/transport/src/value.rs @@ -26,9 +26,9 @@ use wasm_tokio::cm::{ }; use wasm_tokio::{ CoreNameDecoder, CoreNameEncoder, CoreVecDecoder, CoreVecDecoderBytes, CoreVecEncoderBytes, - Leb128DecoderI8, Leb128DecoderI16, Leb128DecoderI32, Leb128DecoderI64, Leb128DecoderI128, - Leb128DecoderU8, Leb128DecoderU16, Leb128DecoderU32, Leb128DecoderU64, Leb128DecoderU128, - Leb128Encoder, Utf8Codec, + DEFAULT_MAX_INITIAL_CAPACITY, Leb128DecoderI8, Leb128DecoderI16, Leb128DecoderI32, + Leb128DecoderI64, Leb128DecoderI128, Leb128DecoderU8, Leb128DecoderU16, Leb128DecoderU32, + Leb128DecoderU64, Leb128DecoderU128, Leb128Encoder, Utf8Codec, }; use crate::BufferedIncoming; @@ -801,16 +801,27 @@ where if len == 0 { return Ok(Some(Vec::default())); } - let len = len - .try_into() + let len = usize::try_from(len) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; - self.ret = Vec::with_capacity(len); - self.deferred = Vec::with_capacity(len); + self.ret = Vec::with_capacity( + len.min(DEFAULT_MAX_INITIAL_CAPACITY / mem::size_of::().max(1)), + ); + self.deferred = Vec::with_capacity(len.min( + DEFAULT_MAX_INITIAL_CAPACITY + / mem::size_of::>>(), + )); self.cap = len; } while self.cap > 0 { - let Some(v) = self.dec.decode(src)? else { - return Ok(None); + let v = match self.dec.decode(src) { + Ok(Some(v)) => v, + Ok(None) => return Ok(None), + Err(err) => { + self.cap = 0; + self.ret.clear(); + self.deferred.clear(); + return Err(err); + } }; self.ret.push(v); self.deferred.push(self.dec.take_deferred()); @@ -1804,8 +1815,7 @@ where for (i, item) in zip(i.., chunk) { enc.encode(item, &mut buf)?; if let Some(f) = enc.take_deferred() { - let i = i - .try_into() + let i = usize::try_from(i) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; let w = w.index(&[i]).map_err(std::io::Error::other)?; trace!("spawning transmit task"); @@ -2265,9 +2275,37 @@ mod tests { use super::*; + #[test_log::test(tokio::test)] + async fn list_decoder_reset_on_error() -> anyhow::Result<()> { + let mut dec = > as Decode>::Decoder::default(); + let mut buf = BytesMut::from(&[0x02, 0x01, 0x01, b'a', 0x01, 0x01, 0xff][..]); + assert!(tokio_util::codec::Decoder::decode(&mut dec, &mut buf).is_err()); + let mut buf = BytesMut::from(&[0x01, 0x01, 0x01, b'b'][..]); + assert_eq!( + tokio_util::codec::Decoder::decode(&mut dec, &mut buf)?, + Some(vec![vec!["b".to_string()]]) + ); + Ok(()) + } + + #[test_log::test(tokio::test)] + async fn list_decoder_reset_deferred_on_error() -> anyhow::Result<()> { + let mut dec = + + Send>>> as Decode>::Decoder::default(); + let mut buf = BytesMut::from(&[0x02, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff][..]); + assert!(tokio_util::codec::Decoder::decode(&mut dec, &mut buf).is_err()); + let mut buf = BytesMut::from(&[0x00][..]); + let vs = tokio_util::codec::Decoder::decode(&mut dec, &mut buf)?; + assert!(vs.is_some_and(|vs| vs.is_empty())); + if let Some(_f) = Deferred::::take_deferred(&mut dec) { + bail!("no deferred read should have been returned"); + } + Ok(()) + } + #[test_log::test(tokio::test)] async fn codec() -> anyhow::Result<()> { - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); let mut enc = <(u8, u32) as Encode>::Encoder::default(); enc.encode((0x42, 0x42), &mut buf)?; if let Some(_f) = Deferred::::take_deferred(&mut enc) { @@ -2282,17 +2320,17 @@ mod tests { let mut enc = ::Encoder::default(); // A non-canonical (e.g. signalling) `NaN` is canonicalized on encode. - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); enc.encode(f32::from_bits(0x7f80_0001), &mut buf).unwrap(); assert_eq!(buf.as_ref(), CANONICAL_NAN_F32.to_le_bytes()); // A negative `NaN` is canonicalized to the (positive) canonical `NaN`. - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); enc.encode(f32::from_bits(0xffc0_0000), &mut buf).unwrap(); assert_eq!(buf.as_ref(), CANONICAL_NAN_F32.to_le_bytes()); // Non-`NaN` values are encoded unchanged. - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); enc.encode(1.5_f32, &mut buf).unwrap(); assert_eq!(buf.as_ref(), 1.5_f32.to_bits().to_le_bytes()); } @@ -2301,17 +2339,17 @@ mod tests { fn canonical_nan_f64() { let mut enc = ::Encoder::default(); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); enc.encode(f64::from_bits(0x7ff0_0000_0000_0001), &mut buf) .unwrap(); assert_eq!(buf.as_ref(), CANONICAL_NAN_F64.to_le_bytes()); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); enc.encode(f64::from_bits(0xfff8_0000_0000_0000), &mut buf) .unwrap(); assert_eq!(buf.as_ref(), CANONICAL_NAN_F64.to_le_bytes()); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); enc.encode(1.5_f64, &mut buf).unwrap(); assert_eq!(buf.as_ref(), 1.5_f64.to_bits().to_le_bytes()); } diff --git a/crates/wasmtime/src/codec.rs b/crates/wasmtime/src/codec.rs index b9f100f04..0a9dddf5f 100644 --- a/crates/wasmtime/src/codec.rs +++ b/crates/wasmtime/src/codec.rs @@ -1,4 +1,5 @@ use core::iter::zip; +use core::mem; use core::ops::{BitOrAssign, Shl}; use core::pin::{Pin, pin}; @@ -15,7 +16,7 @@ use uuid::Uuid; use wasm_tokio::cm::AsyncReadValue as _; use wasm_tokio::{ AsyncReadCore as _, AsyncReadLeb128 as _, AsyncReadUtf8 as _, CoreNameEncoder, - CoreVecEncoderBytes, Leb128Encoder, Utf8Codec, + CoreVecEncoderBytes, DEFAULT_MAX_INITIAL_CAPACITY, Leb128Encoder, Utf8Codec, }; use wasmtime::bail; use wasmtime::component::types::{Case, Field}; @@ -649,8 +650,9 @@ where } Type::List(ty) => { let n = r.read_u32_leb128().await?; - let n = n.try_into().unwrap_or(usize::MAX); - let mut vs = Vec::with_capacity(n); + let n = usize::try_from(n).unwrap_or(usize::MAX); + let mut vs = + Vec::with_capacity(n.min(DEFAULT_MAX_INITIAL_CAPACITY / mem::size_of::())); let ty = ty.ty(); let mut path = path.to_vec(); for i in 0..n { @@ -705,8 +707,7 @@ where } Type::Variant(ty) => { let discriminant = r.read_u32_leb128().await?; - let discriminant = discriminant - .try_into() + let discriminant = usize::try_from(discriminant) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; let Case { name, ty } = ty.cases().nth(discriminant).ok_or_else(|| { std::io::Error::new( @@ -730,8 +731,7 @@ where } Type::Enum(ty) => { let discriminant = r.read_u32_leb128().await?; - let discriminant = discriminant - .try_into() + let discriminant = usize::try_from(discriminant) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; let name = ty.names().nth(discriminant).ok_or_else(|| { std::io::Error::new( @@ -817,10 +817,7 @@ where let mut buf = vec![0; cap]; r.read_exact(&mut buf).await?; let mut vs = Vec::with_capacity( - buf.iter() - .map(|b| b.count_ones()) - .sum::() - .try_into() + usize::try_from(buf.iter().map(|b| b.count_ones()).sum::()) .unwrap_or(usize::MAX), ); for (i, name) in names.enumerate() { @@ -832,7 +829,8 @@ where return Ok(()); } }; - let mut vs = Vec::with_capacity(flags.count_ones().try_into().unwrap_or(usize::MAX)); + let mut vs = + Vec::with_capacity(usize::try_from(flags.count_ones()).unwrap_or(usize::MAX)); for (i, name) in zip(0.., names) { if flags & (1 << i) != 0 { vs.push(name.to_string()); @@ -906,10 +904,13 @@ where } else { let mut store = store.as_context_mut(); let n = r.read_u32_leb128().await?; + let mut r = r.take(n.into()); let n = usize::try_from(n) .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; - let mut buf = Vec::with_capacity(n); - r.read_to_end(&mut buf).await?; + let mut buf = Vec::default(); + if r.read_to_end(&mut buf).await? != n { + return Err(std::io::ErrorKind::UnexpectedEof.into()); + } let table = store.data_mut().wrpc().table; let resource = table .push(RemoteResource(buf.into())) diff --git a/crates/wave/Cargo.toml b/crates/wave/Cargo.toml index 4e729a455..87680a743 100644 --- a/crates/wave/Cargo.toml +++ b/crates/wave/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wrpc-wave" -version = "0.1.0" +version = "0.2.0" description = "wRPC runtime-agnostic encoding and decoding support for wasm-wave traits" authors.workspace = true @@ -28,4 +28,5 @@ wrpc-pack = { workspace = true, optional = true } wrpc-transport = { workspace = true } [dev-dependencies] +futures = { workspace = true, features = ["executor"] } wasmtime = { workspace = true, features = ["runtime", "wave"] } diff --git a/crates/wave/src/core/decode.rs b/crates/wave/src/core/decode.rs index d560a8806..f4dca5d34 100644 --- a/crates/wave/src/core/decode.rs +++ b/crates/wave/src/core/decode.rs @@ -16,18 +16,20 @@ //! //! # async fn example(reader: &mut R) -> std::io::Result<()> { //! let mut pinned = std::pin::pin!(reader); -//! let value = read_value(&mut pinned, &wasm_wave::value::Type::U32).await?; +//! let value = read_value(&mut pinned, &Type::U32).await?; //! # Ok(()) //! # } //! ``` +use core::mem; use core::pin::Pin; use std::borrow::Cow; use tokio::io::{AsyncRead, AsyncReadExt as _}; use wasm_tokio::{ - AsyncReadCore as _, AsyncReadLeb128 as _, AsyncReadUtf8 as _, cm::AsyncReadValue as _, + AsyncReadCore as _, AsyncReadLeb128 as _, AsyncReadUtf8 as _, DEFAULT_MAX_INITIAL_CAPACITY, + cm::AsyncReadValue as _, }; use wasm_wave::value::{Type, Value}; use wasm_wave::wasm::{WasmType, WasmTypeKind, WasmValue as _}; @@ -104,12 +106,13 @@ where } WasmTypeKind::List => { let n = r.read_u32_leb128().await?; - let n = n.try_into().unwrap_or(usize::MAX); + let n = usize::try_from(n).unwrap_or(usize::MAX); let element_type = ty .list_element_type() .ok_or_else(|| io_error("list type missing element type"))?; - let mut elements = Vec::with_capacity(n); + let mut elements = + Vec::with_capacity(n.min(DEFAULT_MAX_INITIAL_CAPACITY / mem::size_of::())); for _ in 0..n { let element = Box::pin(read_value(r, &element_type)).await?; elements.push(element); @@ -148,8 +151,7 @@ where let discriminant = r.read_u32_leb128().await?; let cases: Vec<_> = ty.variant_cases().collect(); - let discriminant_idx: usize = discriminant - .try_into() + let discriminant_idx = usize::try_from(discriminant) .map_err(|_| io_error("variant discriminant too large"))?; let (case_name, payload_type) = cases @@ -169,8 +171,7 @@ where let discriminant = r.read_u32_leb128().await?; let names: Vec<_> = ty.enum_cases().collect(); - let discriminant_idx: usize = discriminant - .try_into() + let discriminant_idx = usize::try_from(discriminant) .map_err(|_| io_error("enum discriminant too large"))?; let name = names @@ -180,9 +181,7 @@ where Value::make_enum(ty, name).map_err(io_error) } WasmTypeKind::Option => { - let ok = r.read_u8().await?; - - if ok != 0 { + if r.read_option_status().await? { let inner_type = ty .option_some_type() .ok_or_else(|| io_error("option type missing some type"))?; @@ -193,40 +192,34 @@ where } } WasmTypeKind::Result => { - let ok = r.read_u8().await?; + let ok = r.read_result_status().await?; let (ok_type, err_type) = ty .result_types() .ok_or_else(|| io_error("result type missing ok/err types"))?; - if ok == 0 { - // Ok variant + if ok { if let Some(ok_ty) = ok_type { let value = Box::pin(read_value(r, &ok_ty)).await?; Value::make_result(ty, Ok(Some(value))) } else { Value::make_result(ty, Ok(None)) } - } else if ok == 1 { - // Err variant - if let Some(err_ty) = err_type { - let value = Box::pin(read_value(r, &err_ty)).await?; - Value::make_result(ty, Err(Some(value))) - } else { - Value::make_result(ty, Err(None)) - } + } else if let Some(err_ty) = err_type { + let value = Box::pin(read_value(r, &err_ty)).await?; + Value::make_result(ty, Err(Some(value))) } else { - return Err(io_error(format!("invalid result discriminant: {ok}"))); + Value::make_result(ty, Err(None)) } .map_err(io_error) } WasmTypeKind::Flags => { let names: Vec<_> = ty.flags_names().collect(); - let byte_count = names.len().div_ceil(8); + let byte_count = names.len().div_ceil(8).max(1); let mut buf = vec![0u8; byte_count]; r.read_exact(&mut buf).await?; - let mut flag_names = Vec::new(); + let mut flag_names = Vec::default(); for (i, name) in names.iter().enumerate() { if buf[i / 8] & (1 << (i % 8)) != 0 { flag_names.push(name.as_ref()); @@ -235,8 +228,7 @@ where Value::make_flags(ty, flag_names).map_err(io_error) } - WasmTypeKind::Unsupported => Err(io_error("unsupported value type")), - _ => Err(io_error(format!("unsupported value type: {:?}", ty.kind()))), + kind => Err(io_error(format!("unsupported value type: {kind:?}"))), } } @@ -340,15 +332,15 @@ mod tests { // Float types #[test] fn test_decode_f32() -> anyhow::Result<()> { - let value = decode_sync(&Type::F32, &3.14f32.to_le_bytes())?; - assert!((value.unwrap_f32() - 3.14).abs() < 0.01); + let value = decode_sync(&Type::F32, &1.5f32.to_le_bytes())?; + assert_eq!(value.unwrap_f32(), 1.5); Ok(()) } #[test] fn test_decode_f64() -> anyhow::Result<()> { - let value = decode_sync(&Type::F64, &3.14159265359f64.to_le_bytes())?; - assert!((value.unwrap_f64() - 3.14159265359).abs() < 0.00000001); + let value = decode_sync(&Type::F64, &2.5f64.to_le_bytes())?; + assert_eq!(value.unwrap_f64(), 2.5); Ok(()) } @@ -586,6 +578,25 @@ mod tests { } // Error cases + #[test] + fn test_decode_option_invalid_status() { + let err = decode_sync(&Type::option(Type::U32), &[2, 42]).unwrap_err(); + assert_eq!(err.kind(), std::io::ErrorKind::InvalidData); + } + + #[test] + fn test_decode_result_invalid_status() { + let result_type = Type::result(Some(Type::U32), Some(Type::U32)); + let err = decode_sync(&result_type, &[2, 42]).unwrap_err(); + assert_eq!(err.kind(), std::io::ErrorKind::InvalidData); + } + + #[test] + fn test_decode_list_truncated() { + let err = decode_sync(&Type::list(Type::U8), &[0xff, 0xff, 0xff, 0xff, 0x0f]).unwrap_err(); + assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof); + } + #[test] fn test_incomplete_data_eof() { let result = decode_sync(&Type::U32, &[]); diff --git a/crates/wave/src/core/encode.rs b/crates/wave/src/core/encode.rs index 528432cc2..3f50ed663 100644 --- a/crates/wave/src/core/encode.rs +++ b/crates/wave/src/core/encode.rs @@ -18,7 +18,7 @@ use wasm_wave::wasm::{WasmType, WasmTypeKind, WasmValue}; /// /// ## Usage /// -/// ```no_run +/// ``` /// use wrpc_wave::WaveEncoder; /// use wasm_wave::value::{Value, Type}; /// use wasm_wave::wasm::WasmValue; @@ -28,8 +28,9 @@ use wasm_wave::wasm::{WasmType, WasmTypeKind, WasmValue}; /// let value = Value::make_u32(42); /// let ty = Type::U32; /// let mut encoder = WaveEncoder::new(&ty); -/// let mut buf = BytesMut::new(); +/// let mut buf = BytesMut::default(); /// encoder.encode(&value, &mut buf).unwrap(); +/// assert_eq!(buf.as_ref(), [42]); /// ``` /// /// Note: @@ -47,50 +48,43 @@ impl<'a, T: WasmType> WaveEncoder<'a, T> { pub fn new(ty: &'a T) -> Self { Self { ty } } - - /// Creates a new encoder with a different type, reusing the current encoder's context. - /// This is useful for encoding nested values with different types. - #[must_use] - pub fn with_type<'b>(&'b mut self, ty: &'b T) -> WaveEncoder<'b, T> { - WaveEncoder { ty } - } } + fn find_enum_discriminant<'a, T>( iter: impl IntoIterator, - names: impl IntoIterator, + names: impl IntoIterator>, discriminant: &str, ) -> anyhow::Result { - zip(iter, names) - .find_map(|(i, name)| (name == discriminant).then_some(i)) + zip(names, iter) + .find_map(|(name, i)| (name == discriminant).then_some(i)) .context("unknown enum discriminant") } -fn find_variant_discriminant<'a, T>( +fn find_variant_discriminant<'a, T, U>( iter: impl IntoIterator, - cases: impl IntoIterator)>, + cases: impl IntoIterator, Option)>, discriminant: &str, -) -> anyhow::Result<(T, Option<&'a str>)> { - zip(iter, cases) - .find_map(|(i, (name, ty))| (name == discriminant).then_some((i, ty))) +) -> anyhow::Result<(T, Option)> { + zip(cases, iter) + .find_map(|((name, ty), i)| (name == discriminant).then_some((i, ty))) .context("unknown variant discriminant") } #[inline] fn flag_bits<'a, T: BitOrAssign + Shl + From>( - names: impl IntoIterator, - flags: impl IntoIterator, + names: &[Cow<'_, str>], + flags: impl IntoIterator>, ) -> T { let mut v = T::from(0); - let flags: HashSet<&str> = flags.into_iter().collect(); + let flags: HashSet> = flags.into_iter().collect(); for (i, name) in zip(0u8.., names) { - if flags.contains(name) { + if flags.contains(name.as_ref()) { v |= T::from(1) << i; } } v } -// Generic implementation for any type implementing WasmValue and WasmType impl Encoder<&V> for WaveEncoder<'_, T> where V: WasmValue, @@ -99,7 +93,7 @@ where type Error = anyhow::Error; #[allow(clippy::too_many_lines)] - #[instrument(level = "trace", skip(self, val))] + #[instrument(level = "trace", skip_all)] fn encode(&mut self, val: &V, dst: &mut BytesMut) -> Result<(), Self::Error> { match val.kind() { WasmTypeKind::Bool => { @@ -152,129 +146,101 @@ where .encode(val.unwrap_string().as_ref(), dst) .context("failed to encode string"), WasmTypeKind::List => { - let elements: Vec<_> = val.unwrap_list().map(Cow::into_owned).collect(); + let elements: Vec<_> = val.unwrap_list().collect(); let n = u32::try_from(elements.len()).context("list length does not fit in u32")?; dst.reserve(5 + elements.len()); Leb128Encoder .encode(n, dst) .context("failed to encode list length")?; - let element_type = self + let ty = self .ty .list_element_type() .context("list type should have element type")?; for element in elements { - let mut enc = self.with_type(&element_type); - enc.encode(&element, dst) + WaveEncoder::new(&ty) + .encode(&*element, dst) .context("failed to encode list element")?; } Ok(()) } WasmTypeKind::Record => { - let fields: Vec<_> = val - .unwrap_record() - .map(|(name, v)| (name.into_owned(), v.into_owned())) - .collect(); - let field_types: Vec<_> = self.ty.record_fields().map(|(_, ty)| ty).collect(); - dst.reserve(fields.len()); - for ((_name, field_value), field_type) in fields.iter().zip(field_types.iter()) { - let mut enc = self.with_type(field_type); - enc.encode(field_value, dst) - .context("failed to encode record field")?; + for ((name, v), (_, ty)) in zip(val.unwrap_record(), self.ty.record_fields()) { + WaveEncoder::new(&ty) + .encode(&*v, dst) + .with_context(|| format!("failed to encode `{name}` field"))?; } Ok(()) } WasmTypeKind::Tuple => { - let elements: Vec<_> = val.unwrap_tuple().map(Cow::into_owned).collect(); - let element_types: Vec<_> = self.ty.tuple_element_types().collect(); - dst.reserve(elements.len()); - for (element, element_type) in elements.iter().zip(element_types.iter()) { - let mut enc = self.with_type(element_type); - enc.encode(element, dst) + for (v, ty) in zip(val.unwrap_tuple(), self.ty.tuple_element_types()) { + WaveEncoder::new(&ty) + .encode(&*v, dst) .context("failed to encode tuple element")?; } Ok(()) } WasmTypeKind::Variant => { - let (case_name, payload) = val.unwrap_variant(); - let case_name = case_name.into_owned(); - - // Get the type to find the discriminant index - let cases: Vec<_> = self - .ty - .variant_cases() - .map(|(name, payload_ty)| (name.into_owned(), payload_ty)) - .collect(); - - let (discriminant_idx, _case_ty) = find_variant_discriminant( - 0u32.., - cases - .iter() - .map(|(name, _ty)| (name.as_str(), None::<&str>)), - case_name.as_str(), - )?; - - match cases.len() { + let (case, payload) = val.unwrap_variant(); + let cases: Vec<_> = self.ty.variant_cases().collect(); + let ty = match cases.len() { ..=0x0000_00ff => { + let (discriminant, ty) = find_variant_discriminant(0u8.., cases, &case)?; dst.reserve(2 + usize::from(payload.is_some())); - Leb128Encoder.encode(discriminant_idx as u8, dst)?; + Leb128Encoder.encode(discriminant, dst)?; + ty } 0x0000_0100..=0x0000_ffff => { + let (discriminant, ty) = find_variant_discriminant(0u16.., cases, &case)?; dst.reserve(3 + usize::from(payload.is_some())); - Leb128Encoder.encode(discriminant_idx as u16, dst)?; + Leb128Encoder.encode(discriminant, dst)?; + ty } 0x0001_0000..=0x00ff_ffff => { + let (discriminant, ty) = find_variant_discriminant(0u32.., cases, &case)?; dst.reserve(4 + usize::from(payload.is_some())); - Leb128Encoder.encode(discriminant_idx, dst)?; + Leb128Encoder.encode(discriminant, dst)?; + ty } 0x0100_0000..=0xffff_ffff => { + let (discriminant, ty) = find_variant_discriminant(0u32.., cases, &case)?; dst.reserve(5 + usize::from(payload.is_some())); - Leb128Encoder.encode(discriminant_idx, dst)?; + Leb128Encoder.encode(discriminant, dst)?; + ty } _ => bail!("case count does not fit in u32"), + }; + match (payload, ty) { + (Some(v), Some(ty)) => WaveEncoder::new(&ty) + .encode(&*v, dst) + .with_context(|| format!("failed to encode `{case}` payload")), + (None, None) => Ok(()), + (Some(_), None) => bail!("variant case `{case}` does not have a payload"), + (None, Some(_)) => bail!("variant case `{case}` payload missing"), } - - if let Some(payload_val) = payload { - // Find the payload type for this variant case - let payload_type = self - .ty - .variant_cases() - .find_map(|(name, payload_ty)| (name == case_name).then_some(payload_ty)) - .flatten() - .context("variant case should have payload type")?; - let mut enc = self.with_type(&payload_type); - enc.encode(&*payload_val, dst) - .context("failed to encode variant payload")?; - } - Ok(()) } WasmTypeKind::Enum => { - let case_name = val.unwrap_enum().into_owned(); - - // Get the type to find the discriminant index - let names: Vec<_> = self.ty.enum_cases().map(Cow::into_owned).collect(); - - let discriminant_idx = find_enum_discriminant( - 0u32.., - names.iter().map(String::as_str), - case_name.as_str(), - )?; - + let case = val.unwrap_enum(); + let names: Vec<_> = self.ty.enum_cases().collect(); match names.len() { ..=0x0000_00ff => { + let discriminant = find_enum_discriminant(0u8.., names, &case)?; dst.reserve(2); - Leb128Encoder.encode(discriminant_idx as u8, dst)?; + Leb128Encoder.encode(discriminant, dst)?; } 0x0000_0100..=0x0000_ffff => { + let discriminant = find_enum_discriminant(0u16.., names, &case)?; dst.reserve(3); - Leb128Encoder.encode(discriminant_idx as u16, dst)?; + Leb128Encoder.encode(discriminant, dst)?; } 0x0001_0000..=0x00ff_ffff => { + let discriminant = find_enum_discriminant(0u32.., names, &case)?; dst.reserve(4); - Leb128Encoder.encode(discriminant_idx, dst)?; + Leb128Encoder.encode(discriminant, dst)?; } 0x0100_0000..=0xffff_ffff => { + let discriminant = find_enum_discriminant(0u32.., names, &case)?; dst.reserve(5); - Leb128Encoder.encode(discriminant_idx, dst)?; + Leb128Encoder.encode(discriminant, dst)?; } _ => bail!("name count does not fit in u32"), } @@ -286,178 +252,131 @@ where dst.put_u8(0); Ok(()) } - Some(inner) => { + Some(v) => { dst.reserve(2); dst.put_u8(1); - let inner_type = self + let ty = self .ty .option_some_type() .context("option type should have some type")?; - let mut enc = self.with_type(&inner_type); - enc.encode(&*inner, dst) - .context("failed to encode `option::some` value")?; - Ok(()) + WaveEncoder::new(&ty) + .encode(&*v, dst) + .context("failed to encode `option::some` value") } }, WasmTypeKind::Result => { - let (ok_type, err_type) = self + let (ok_ty, err_ty) = self .ty .result_types() .context("result type should have ok and err types")?; match val.unwrap_result() { - Ok(ok_val) => { - match ok_val { - Some(val) => { - dst.reserve(2); - dst.put_u8(0); - if let Some(ok_ty) = ok_type { - let mut enc = self.with_type(&ok_ty); - enc.encode(&*val, dst) - .context("failed to encode `result::ok` value")?; - } - } - None => { - dst.reserve(1); - dst.put_u8(0); - } + Ok(v) => match (v, ok_ty) { + (Some(v), Some(ty)) => { + dst.reserve(2); + dst.put_u8(0); + WaveEncoder::new(&ty) + .encode(&*v, dst) + .context("failed to encode `result::ok` value") } - Ok(()) - } - Err(err_val) => { - match err_val { - Some(val) => { - dst.reserve(2); - dst.put_u8(1); - if let Some(err_ty) = err_type { - let mut enc = self.with_type(&err_ty); - enc.encode(&*val, dst) - .context("failed to encode `result::err` value")?; - } - } - None => { - dst.reserve(1); - dst.put_u8(1); - } + (None, None) => { + dst.reserve(1); + dst.put_u8(0); + Ok(()) } - Ok(()) - } + (Some(_), None) => bail!("`result::ok` value of unknown type"), + (None, Some(_)) => bail!("`result::ok` value missing"), + }, + Err(v) => match (v, err_ty) { + (Some(v), Some(ty)) => { + dst.reserve(2); + dst.put_u8(1); + WaveEncoder::new(&ty) + .encode(&*v, dst) + .context("failed to encode `result::err` value") + } + (None, None) => { + dst.reserve(1); + dst.put_u8(1); + Ok(()) + } + (Some(_), None) => bail!("`result::err` value of unknown type"), + (None, Some(_)) => bail!("`result::err` value missing"), + }, } } WasmTypeKind::Flags => { - let flag_names: Vec<_> = val.unwrap_flags().map(Cow::into_owned).collect(); - - // Get the type to know all possible flag names for bit encoding - let all_names: Vec<_> = self.ty.flags_names().map(Cow::into_owned).collect(); - - let flags_set: HashSet<&str> = flag_names.iter().map(String::as_str).collect(); - let vs = flag_names.iter().map(String::as_str); - - match all_names.len() { + let names: Vec<_> = self.ty.flags_names().collect(); + let vs = val.unwrap_flags(); + match names.len() { ..=8 => { dst.reserve(1); - dst.put_u8(flag_bits(all_names.iter().map(String::as_str), vs)); + dst.put_u8(flag_bits(&names, vs)); } 9..=16 => { dst.reserve(2); - dst.put_u16_le(flag_bits(all_names.iter().map(String::as_str), vs)); + dst.put_u16_le(flag_bits(&names, vs)); } 17..=24 => { dst.reserve(3); - dst.put_slice( - &u32::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..3], - ); + dst.put_slice(&u32::to_le_bytes(flag_bits(&names, vs))[..3]); } 25..=32 => { dst.reserve(4); - dst.put_u32_le(flag_bits(all_names.iter().map(String::as_str), vs)); + dst.put_u32_le(flag_bits(&names, vs)); } 33..=40 => { dst.reserve(5); - dst.put_slice( - &u64::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..5], - ); + dst.put_slice(&u64::to_le_bytes(flag_bits(&names, vs))[..5]); } 41..=48 => { dst.reserve(6); - dst.put_slice( - &u64::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..6], - ); + dst.put_slice(&u64::to_le_bytes(flag_bits(&names, vs))[..6]); } 49..=56 => { dst.reserve(7); - dst.put_slice( - &u64::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..7], - ); + dst.put_slice(&u64::to_le_bytes(flag_bits(&names, vs))[..7]); } 57..=64 => { dst.reserve(8); - dst.put_u64_le(flag_bits(all_names.iter().map(String::as_str), vs)); + dst.put_u64_le(flag_bits(&names, vs)); } 65..=72 => { dst.reserve(9); - dst.put_slice( - &u128::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..9], - ); + dst.put_slice(&u128::to_le_bytes(flag_bits(&names, vs))[..9]); } 73..=80 => { dst.reserve(10); - dst.put_slice( - &u128::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..10], - ); + dst.put_slice(&u128::to_le_bytes(flag_bits(&names, vs))[..10]); } 81..=88 => { dst.reserve(11); - dst.put_slice( - &u128::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..11], - ); + dst.put_slice(&u128::to_le_bytes(flag_bits(&names, vs))[..11]); } 89..=96 => { dst.reserve(12); - dst.put_slice( - &u128::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..12], - ); + dst.put_slice(&u128::to_le_bytes(flag_bits(&names, vs))[..12]); } 97..=104 => { dst.reserve(13); - dst.put_slice( - &u128::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..13], - ); + dst.put_slice(&u128::to_le_bytes(flag_bits(&names, vs))[..13]); } 105..=112 => { dst.reserve(14); - dst.put_slice( - &u128::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..14], - ); + dst.put_slice(&u128::to_le_bytes(flag_bits(&names, vs))[..14]); } 113..=120 => { dst.reserve(15); - dst.put_slice( - &u128::to_le_bytes(flag_bits(all_names.iter().map(String::as_str), vs)) - [..15], - ); + dst.put_slice(&u128::to_le_bytes(flag_bits(&names, vs))[..15]); } 121..=128 => { dst.reserve(16); - dst.put_u128_le(flag_bits(all_names.iter().map(String::as_str), vs)); + dst.put_u128_le(flag_bits(&names, vs)); } bits @ 129.. => { - let mut cap = bits / 8; - if bits % 8 != 0 { - cap = cap.saturating_add(1); - } - let mut buf = vec![0; cap]; - for (i, name) in all_names.iter().enumerate() { - if flags_set.contains(name.as_str()) { + let mut buf = vec![0; bits.div_ceil(8)]; + let flags: HashSet> = vs.collect(); + for (i, name) in names.iter().enumerate() { + if flags.contains(name.as_ref()) { buf[i / 8] |= 1 << (i % 8); } } @@ -466,10 +385,7 @@ where } Ok(()) } - WasmTypeKind::Unsupported => { - bail!("unsupported value type") - } - _ => bail!("unsupported value type: {:?}", val.kind()), + kind => bail!("unsupported value type: {kind:?}"), } } } @@ -484,7 +400,7 @@ mod tests { let value = Value::make_bool(true); let ty = Type::BOOL; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; assert_eq!(buf.as_ref(), &[1u8]); @@ -496,7 +412,7 @@ mod tests { let value = Value::make_bool(false); let ty = Type::BOOL; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; assert_eq!(buf.as_ref(), &[0u8]); @@ -510,7 +426,7 @@ mod tests { let value = Value::make_s8(42); let ty = Type::S8; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; assert_eq!(buf.as_ref(), &[42u8]); @@ -522,7 +438,7 @@ mod tests { let value = Value::make_s8(-42); let ty = Type::S8; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; assert_eq!(buf.as_ref(), &[(-42i8) as u8]); @@ -534,7 +450,7 @@ mod tests { let value = Value::make_u8(255); let ty = Type::U8; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; assert_eq!(buf.as_ref(), &[255u8]); @@ -546,7 +462,7 @@ mod tests { let value = Value::make_s16(1000); let ty = Type::S16; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; // 1000 in LEB128: 0xE8 0x07 @@ -559,7 +475,7 @@ mod tests { let value = Value::make_u16(1000); let ty = Type::U16; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; // 1000 in LEB128: 0xE8 0x07 @@ -572,7 +488,7 @@ mod tests { let value = Value::make_s32(100000); let ty = Type::S32; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; // 100000 in LEB128: 0xA0 0x8D 0x06 @@ -585,7 +501,7 @@ mod tests { let value = Value::make_u32(42); let ty = Type::U32; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; // 42 in LEB128 is just [42] @@ -598,7 +514,7 @@ mod tests { let value = Value::make_s64(-1); let ty = Type::S64; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; // -1 in LEB128: 0x7F @@ -611,7 +527,7 @@ mod tests { let value = Value::make_u64(12345); let ty = Type::U64; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; // 12345 in LEB128: 0xB9 0x60 @@ -623,25 +539,25 @@ mod tests { #[test] fn test_encode_f32() -> anyhow::Result<()> { - let value = Value::make_f32(3.14); + let value = Value::make_f32(1.5); let ty = Type::F32; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; - assert_eq!(buf.as_ref(), &3.14f32.to_le_bytes()); + assert_eq!(buf.as_ref(), &1.5f32.to_le_bytes()); Ok(()) } #[test] fn test_encode_f64() -> anyhow::Result<()> { - let value = Value::make_f64(3.14159265359); + let value = Value::make_f64(2.5); let ty = Type::F64; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; - assert_eq!(buf.as_ref(), &3.14159265359f64.to_le_bytes()); + assert_eq!(buf.as_ref(), &2.5f64.to_le_bytes()); Ok(()) } @@ -652,7 +568,7 @@ mod tests { let value = Value::make_char('A'); let ty = Type::CHAR; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; // UTF-8 encoding of 'A' (0x41) @@ -665,7 +581,7 @@ mod tests { let value = Value::make_char('😀'); let ty = Type::CHAR; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; // UTF-8 encoding of '😀' (U+1F600): 0xF0 0x9F 0x98 0x80 @@ -679,7 +595,7 @@ mod tests { let value = Value::make_string(Cow::Borrowed("hello")); let ty = Type::STRING; let mut encoder = WaveEncoder::new(&ty); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&value, &mut buf)?; // String encoding: length (5 in LEB128) + "hello" @@ -697,7 +613,7 @@ mod tests { let list_value = Value::make_list(&list_type, values)?; let mut encoder = WaveEncoder::new(&list_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&list_value, &mut buf)?; // Empty list: length 0 @@ -713,7 +629,7 @@ mod tests { let list_value = Value::make_list(&list_type, values)?; let mut encoder = WaveEncoder::new(&list_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&list_value, &mut buf)?; // List encoding: length (3) + elements (1, 2, 3 in LEB128) @@ -733,7 +649,7 @@ mod tests { )?; let mut encoder = WaveEncoder::new(&record_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&record_value, &mut buf)?; // Record with x=10, y=20 (LEB128) @@ -751,7 +667,7 @@ mod tests { Value::make_tuple(&tuple_type, [Value::make_u32(42), Value::make_bool(true)])?; let mut encoder = WaveEncoder::new(&tuple_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&tuple_value, &mut buf)?; // Tuple with (42, true) @@ -768,7 +684,7 @@ mod tests { let variant_value = Value::make_variant(&variant_type, "none", None)?; let mut encoder = WaveEncoder::new(&variant_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&variant_value, &mut buf)?; // Variant "none" (discriminant 0, no payload) @@ -783,7 +699,7 @@ mod tests { let variant_value = Value::make_variant(&variant_type, "some", Some(Value::make_u32(42)))?; let mut encoder = WaveEncoder::new(&variant_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&variant_value, &mut buf)?; // Variant "some" with payload 42 (discriminant 1, payload 42) @@ -799,7 +715,7 @@ mod tests { let enum_value = Value::make_enum(&enum_type, "red")?; let mut encoder = WaveEncoder::new(&enum_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&enum_value, &mut buf)?; // First enum case should encode as discriminant 0 @@ -816,7 +732,7 @@ mod tests { let option_value = Value::make_option(&option_type, None)?; let mut encoder = WaveEncoder::new(&option_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&option_value, &mut buf)?; // None is encoded as 0 @@ -832,7 +748,7 @@ mod tests { let option_value = Value::make_option(&option_type, Some(inner_value))?; let mut encoder = WaveEncoder::new(&option_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&option_value, &mut buf)?; // Some(42) is encoded as 1 (discriminant) + 42 (value) @@ -848,7 +764,7 @@ mod tests { let result_value = Value::make_result(&result_type, Ok(Some(Value::make_u32(42))))?; let mut encoder = WaveEncoder::new(&result_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&result_value, &mut buf)?; // Ok(42): discriminant 0, value 42 @@ -862,7 +778,7 @@ mod tests { let result_value = Value::make_result(&result_type, Ok(None))?; let mut encoder = WaveEncoder::new(&result_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&result_value, &mut buf)?; // Ok(none): discriminant 0 @@ -880,7 +796,7 @@ mod tests { )?; let mut encoder = WaveEncoder::new(&result_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&result_value, &mut buf)?; // Err("fail"): discriminant 1, length 4, "fail" @@ -894,7 +810,7 @@ mod tests { let result_value = Value::make_result(&result_type, Err(None))?; let mut encoder = WaveEncoder::new(&result_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&result_value, &mut buf)?; // Err(none): discriminant 1 @@ -911,7 +827,7 @@ mod tests { let flags_value = Value::make_flags(&flags_type, [])?; let mut encoder = WaveEncoder::new(&flags_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&flags_value, &mut buf)?; // No flags set: 0b00000000 = 0 @@ -926,7 +842,7 @@ mod tests { let flags_value = Value::make_flags(&flags_type, ["read", "write"])?; let mut encoder = WaveEncoder::new(&flags_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&flags_value, &mut buf)?; // read=bit0, write=bit1 -> 0b00000011 = 3 @@ -943,7 +859,7 @@ mod tests { let flags_value = Value::make_flags(&flags_type, ["f0", "f9"])?; let mut encoder = WaveEncoder::new(&flags_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&flags_value, &mut buf)?; // f0 and f9 set: bit 0 in byte 0, bit 1 in byte 1 @@ -963,7 +879,7 @@ mod tests { let flags_value = Value::make_flags(&flags_type, ["f0", "f8", "f16"])?; let mut encoder = WaveEncoder::new(&flags_type); - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); encoder.encode(&flags_value, &mut buf)?; // f0, f8, f16 set: bit 0 in byte 0, bit 0 in byte 1, bit 0 in byte 2 diff --git a/crates/wave/src/lib.rs b/crates/wave/src/lib.rs index 16ffacfb7..c653000d7 100644 --- a/crates/wave/src/lib.rs +++ b/crates/wave/src/lib.rs @@ -9,7 +9,7 @@ //! This approach mirrors the `ValEncoder` from `wrpc-wasmtime`, //! where you create an encoder with a type reference and then encode values directly: //! -//! ```no_run +//! ``` //! use wrpc_wave::WaveEncoder; //! use wasm_wave::value::{Value, Type}; //! use wasm_wave::wasm::WasmValue; @@ -19,8 +19,9 @@ //! let value = Value::make_u32(42); //! let ty = Type::U32; //! let mut encoder = WaveEncoder::new(&ty); -//! let mut buf = BytesMut::new(); +//! let mut buf = BytesMut::default(); //! encoder.encode(&value, &mut buf).unwrap(); +//! assert_eq!(buf.as_ref(), [42]); //! ``` //! //! ### 2. `wrpc-pack` compatibility layer @@ -38,7 +39,7 @@ //! //! let value = Value::make_u32(42); //! let typed_value = WasmTypedValue(value, Type::U32); -//! let mut buf = BytesMut::new(); +//! let mut buf = BytesMut::default(); //! pack(typed_value, &mut buf).unwrap(); //! # } //! ``` @@ -51,16 +52,18 @@ //! //! Use [`read_value`] for async decoding from any `AsyncRead` source: //! -//! ```no_run +//! ``` //! use wrpc_wave::read_value; //! use wasm_wave::value::Type; +//! use wasm_wave::wasm::WasmValue; //! -//! # async fn example() -> std::io::Result<()> { +//! # futures::executor::block_on(async { //! let data = [42u8]; //! let mut reader = std::pin::pin!(&data[..]); //! let value = read_value(&mut reader, &Type::U8).await?; -//! # Ok(()) -//! # } +//! assert_eq!(value.unwrap_u8(), 42); +//! # Ok::<_, std::io::Error>(()) +//! # }).unwrap(); //! ``` //! //! ### 2. Sync API diff --git a/crates/wave/src/pack.rs b/crates/wave/src/pack.rs index f63f3de4f..97646f3db 100644 --- a/crates/wave/src/pack.rs +++ b/crates/wave/src/pack.rs @@ -114,13 +114,13 @@ mod tests { #[test] fn test_pack_bool() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(true, &mut params_bytes_regular)?; let value = Value::make_bool(true); let ty = Type::BOOL; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -133,13 +133,13 @@ mod tests { #[test] fn test_pack_s8() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(42i8, &mut params_bytes_regular)?; let value = Value::make_s8(42); let ty = Type::S8; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -152,13 +152,13 @@ mod tests { #[test] fn test_pack_u8() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(42u8, &mut params_bytes_regular)?; let value = Value::make_u8(42); let ty = Type::U8; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -171,13 +171,13 @@ mod tests { #[test] fn test_pack_s16() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(1234i16, &mut params_bytes_regular)?; let value = Value::make_s16(1234); let ty = Type::S16; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -190,13 +190,13 @@ mod tests { #[test] fn test_pack_u16() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(1234u16, &mut params_bytes_regular)?; let value = Value::make_u16(1234); let ty = Type::U16; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -209,13 +209,13 @@ mod tests { #[test] fn test_pack_s32() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(123456i32, &mut params_bytes_regular)?; let value = Value::make_s32(123456); let ty = Type::S32; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -228,13 +228,13 @@ mod tests { #[test] fn test_pack_u32() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(123456u32, &mut params_bytes_regular)?; let value = Value::make_u32(123456); let ty = Type::U32; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -247,13 +247,13 @@ mod tests { #[test] fn test_pack_s64() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(1234567890i64, &mut params_bytes_regular)?; let value = Value::make_s64(1234567890); let ty = Type::S64; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -266,13 +266,13 @@ mod tests { #[test] fn test_pack_u64() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(1234567890u64, &mut params_bytes_regular)?; let value = Value::make_u64(1234567890); let ty = Type::U64; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -285,13 +285,13 @@ mod tests { #[test] fn test_pack_f32() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); - pack(3.14f32, &mut params_bytes_regular)?; + let mut params_bytes_regular = BytesMut::default(); + pack(1.5f32, &mut params_bytes_regular)?; - let value = Value::make_f32(3.14); + let value = Value::make_f32(1.5); let ty = Type::F32; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -304,13 +304,13 @@ mod tests { #[test] fn test_pack_f64() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); - pack(3.14159f64, &mut params_bytes_regular)?; + let mut params_bytes_regular = BytesMut::default(); + pack(2.5f64, &mut params_bytes_regular)?; - let value = Value::make_f64(3.14159); + let value = Value::make_f64(2.5); let ty = Type::F64; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -323,13 +323,13 @@ mod tests { #[test] fn test_pack_char() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack('A', &mut params_bytes_regular)?; let value = Value::make_char('A'); let ty = Type::CHAR; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -342,14 +342,14 @@ mod tests { #[test] fn test_pack_string() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack("hello", &mut params_bytes_regular)?; use std::borrow::Cow; let value = Value::make_string(Cow::Borrowed("hello")); let ty = Type::STRING; let wrpc_value = WasmTypedValue(value, ty); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -362,7 +362,7 @@ mod tests { #[test] fn test_pack_list() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(vec![1u32, 2u32, 3u32], &mut params_bytes_regular)?; let element_type = Type::U32; @@ -370,7 +370,7 @@ mod tests { let values = vec![Value::make_u32(1), Value::make_u32(2), Value::make_u32(3)]; let list_value = Value::make_list(&list_type, values)?; let wrpc_value = WasmTypedValue(list_value, list_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -386,7 +386,7 @@ mod tests { // Records are encoded as tuples (just values, no field names) // So we'll test with a record that has two u32 fields // This should encode the same as a tuple (u32, u32) - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack((42u32, 100u32), &mut params_bytes_regular)?; let record_type = Type::record([("a", Type::U32), ("b", Type::U32)]) @@ -396,7 +396,7 @@ mod tests { [("a", Value::make_u32(42)), ("b", Value::make_u32(100))], )?; let wrpc_value = WasmTypedValue(record_value, record_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -410,7 +410,7 @@ mod tests { #[test] fn test_pack_tuple() -> anyhow::Result<()> { // Test 1: Pack (5,) using regular pack - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack((5u32,), &mut params_bytes_regular)?; // Test 2: Pack a wasm-wave Value representing (5,) as a tuple @@ -418,7 +418,7 @@ mod tests { let tuple_type = Type::tuple([Type::U32]).expect("tuple type should be valid"); let tuple_value = Value::make_tuple(&tuple_type, [value_5])?; let wrpc_tuple_value = WasmTypedValue(tuple_value, tuple_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_tuple_value, &mut params_bytes_wave)?; // Test 3: Verify both produce the same bytes @@ -441,12 +441,12 @@ mod tests { .expect("variant type should be valid"); // For comparison, we'll use Option::Some(42) which encodes similarly - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(Some(42u32), &mut params_bytes_regular)?; let variant_value = Value::make_variant(&variant_type, "some", Some(Value::make_u32(42)))?; let wrpc_value = WasmTypedValue(variant_value, variant_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; // Note: Variant encoding is similar to Option but uses discriminant index @@ -470,7 +470,7 @@ mod tests { // So we'll just test that it encodes without error and produces some bytes let enum_value = Value::make_enum(&enum_type, "red")?; let wrpc_value = WasmTypedValue(enum_value, enum_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; // Enum should encode as a single byte (discriminant 0 for first case in small enum) @@ -493,14 +493,14 @@ mod tests { #[test] fn test_pack_option_none() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(Option::::None, &mut params_bytes_regular)?; let inner_type = Type::U32; let option_type = Type::option(inner_type); let option_value = Value::make_option(&option_type, None)?; let wrpc_value = WasmTypedValue(option_value, option_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -513,7 +513,7 @@ mod tests { #[test] fn test_pack_option_some() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(Some(42u32), &mut params_bytes_regular)?; let inner_type = Type::U32; @@ -521,7 +521,7 @@ mod tests { let inner_value = Value::make_u32(42); let option_value = Value::make_option(&option_type, Some(inner_value))?; let wrpc_value = WasmTypedValue(option_value, option_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -534,7 +534,7 @@ mod tests { #[test] fn test_pack_result_ok() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack(Result::::Ok(42), &mut params_bytes_regular)?; let ok_type = Some(Type::U32); @@ -543,7 +543,7 @@ mod tests { let ok_value = Value::make_u32(42); let result_value = Value::make_result(&result_type, Ok(Some(ok_value)))?; let wrpc_value = WasmTypedValue(result_value, result_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -556,7 +556,7 @@ mod tests { #[test] fn test_pack_result_err() -> anyhow::Result<()> { - let mut params_bytes_regular = BytesMut::new(); + let mut params_bytes_regular = BytesMut::default(); pack( Result::::Err("error".to_string()), &mut params_bytes_regular, @@ -569,7 +569,7 @@ mod tests { let err_value = Value::make_string(Cow::Borrowed("error")); let result_value = Value::make_result(&result_type, Err(Some(err_value)))?; let wrpc_value = WasmTypedValue(result_value, result_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; assert_eq!( @@ -592,7 +592,7 @@ mod tests { // So we'll test that it encodes correctly let flags_value = Value::make_flags(&flags_type, ["read", "write"])?; let wrpc_value = WasmTypedValue(flags_value, flags_type); - let mut params_bytes_wave = BytesMut::new(); + let mut params_bytes_wave = BytesMut::default(); pack(wrpc_value, &mut params_bytes_wave)?; // For 3 flags, it should use u8 encoding (1 byte) diff --git a/crates/wave/src/sync/decode.rs b/crates/wave/src/sync/decode.rs index 27621b49a..1a169b12c 100644 --- a/crates/wave/src/sync/decode.rs +++ b/crates/wave/src/sync/decode.rs @@ -49,15 +49,13 @@ use crate::core::decode::read_value; /// ``` pub fn read_value_sync(ty: &Type, data: &[u8]) -> anyhow::Result { let mut cursor = Cursor::new(data); - let value = block_on(async { let mut pinned = std::pin::pin!(&mut cursor); read_value(&mut pinned, ty).await }) .context("failed to decode value")?; - // Safety: Error on unconsumed data - let consumed = cursor.position() as usize; + let consumed = usize::try_from(cursor.position()).context("position does not fit in usize")?; if consumed < data.len() { bail!( "unconsumed data: {} of {} bytes remain", diff --git a/crates/wave/tests/wasmtime.rs b/crates/wave/tests/wasmtime.rs index eecdbfce7..d35792d34 100644 --- a/crates/wave/tests/wasmtime.rs +++ b/crates/wave/tests/wasmtime.rs @@ -7,7 +7,7 @@ use wrpc_wave::{WaveEncoder, read_value_sync}; #[test] fn wasmtime_compat() -> anyhow::Result<()> { - let mut buf = BytesMut::new(); + let mut buf = BytesMut::default(); WaveEncoder::new(&wasmtime::component::Type::U32).encode(&Val::U32(42), &mut buf)?; let v = read_value_sync(&Type::U32, &buf)?; assert_eq!(v.unwrap_u32(), 42); diff --git a/crates/wit-bindgen-go/src/interface.rs b/crates/wit-bindgen-go/src/interface.rs index a6d6d1267..89650c159 100644 --- a/crates/wit-bindgen-go/src/interface.rs +++ b/crates/wit-bindgen-go/src/interface.rs @@ -412,8 +412,7 @@ impl InterfaceGenerator<'_> { r#"func(r {io}.Reader) (float32, error) {{ var b [4]byte {slog}.Debug("reading f32 bytes") - _, err := r.Read(b[:]) - if err != nil {{ + if _, err := {io}.ReadFull(r, b[:]); err != nil {{ return 0, {fmt}.Errorf("failed to read f32: %w", err) }} return {math}.Float32frombits({binary}.LittleEndian.Uint32(b[:])), nil @@ -432,8 +431,7 @@ impl InterfaceGenerator<'_> { r#"func(r {io}.Reader) (float64, error) {{ var b [8]byte {slog}.Debug("reading f64 bytes") - _, err := r.Read(b[:]) - if err != nil {{ + if _, err := {io}.ReadFull(r, b[:]); err != nil {{ return 0, {fmt}.Errorf("failed to read f64: %w", err) }} return {math}.Float64frombits({binary}.LittleEndian.Uint64(b[:])), nil @@ -485,6 +483,7 @@ impl InterfaceGenerator<'_> { let fmt = self.deps.fmt(); let io = self.deps.io(); let slog = self.deps.slog(); + let wrpc = self.deps.wrpc(); let utf8 = self.deps.utf8(); uwrite!( self.src, @@ -508,9 +507,8 @@ impl InterfaceGenerator<'_> { if x == 0 {{ return "", nil }} - buf := make([]byte, x) {slog}.Debug("reading string bytes", "len", x) - _, err = r.Read(buf) + buf, err := {wrpc}.ReadBytes(r, x) if err != nil {{ return "", {fmt}.Errorf("failed to read string bytes: %w", err) }} @@ -532,6 +530,7 @@ impl InterfaceGenerator<'_> { let fmt = self.deps.fmt(); let io = self.deps.io(); let slog = self.deps.slog(); + let wrpc = self.deps.wrpc(); uwrite!( self.src, r#"func(r interface {{ {io}.ByteReader; {io}.Reader }}) ([]byte, error) {{ @@ -554,9 +553,8 @@ impl InterfaceGenerator<'_> { if x == 0 {{ return nil, nil }} - buf := make([]byte, x) {slog}.Debug("reading byte list contents", "len", x) - _, err = {io}.ReadFull(r, buf) + buf, err := {wrpc}.ReadBytes(r, x) if err != nil {{ return nil, {fmt}.Errorf("failed to read byte list contents: %w", err) }} @@ -604,15 +602,15 @@ impl InterfaceGenerator<'_> { if x == 0 {{ return nil, nil }} - vs := make("#, + vs := {wrpc}.NewSlice["#, ); self.print_list(ty); uwrite!( self.src, - r#", x) - for i := range vs {{ + r#"](x) + for i := range x {{ {slog}.Debug("reading list element", "i", i) - vs[i], err = "#, + v, err := "#, ); self.print_read_ty(ty, "r", "append(path, uint32(i))"); self.push_str("\n"); @@ -621,6 +619,7 @@ impl InterfaceGenerator<'_> { r#"if err != nil {{ return nil, {fmt}.Errorf("failed to read list element %d: %w", i, err) }} + vs = append(vs, v) }} return vs, nil }} @@ -1202,13 +1201,13 @@ impl InterfaceGenerator<'_> { if {math}.MaxUint32 - n < total {{ return nil, {errors}.New("total incoming pending stream element count would overflow a 32-bit unsigned integer") }} - vs := make("# + vs := {wrpc}.NewSlice["# ); self.print_list(ty); uwrite!( self.src, - r#", n) - for i := range vs {{ + r#"](n) + for i := range n {{ {slog}.Debug("reading pending stream element", "i", total) v, err := "# ); @@ -1219,7 +1218,7 @@ impl InterfaceGenerator<'_> { if err != nil {{ return nil, {fmt}.Errorf("failed to read pending stream chunk element %d: %w", i, err) }} - vs[i] = v + vs = append(vs, v) total++ }} return vs, nil @@ -1258,6 +1257,7 @@ impl InterfaceGenerator<'_> { let fmt = self.deps.fmt(); let io = self.deps.io(); let slog = self.deps.slog(); + let wrpc = self.deps.wrpc(); uwrite!( self.src, "func(r interface {{ {io}.ByteReader; {io}.Reader }}) (", @@ -1282,9 +1282,8 @@ impl InterfaceGenerator<'_> { return nil, {errors}.New("owned resource handle length overflows a 32-bit integer") }} x = x | uint32(b)< { let fmt = self.deps.fmt(); let io = self.deps.io(); let slog = self.deps.slog(); + let wrpc = self.deps.wrpc(); uwrite!( self.src, "func(r interface {{ {io}.ByteReader; {io}.Reader }}) (", @@ -1332,9 +1332,8 @@ impl InterfaceGenerator<'_> { return nil, {errors}.New("borrowed resource handle length overflows a 32-bit integer") }} x = x | uint32(b)< 0 { + k := int(min(n, maxInitialCapacity)) + b = slices.Grow(b, k) + if _, err := io.ReadFull(r, b[len(b):len(b)+k]); err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + return nil, err + } + b = b[:len(b)+k] + n -= uint32(k) + } + return b, nil +} + func Slice[T any](v []T) *[]T { if v == nil { return nil @@ -65,15 +96,11 @@ func ReadByteList(r ByteReader) ([]byte, error) { return nil, fmt.Errorf("failed to read list length: %w", err) } - b := make([]byte, n) slog.Debug("reading bytes", "len", n) - rn, err := r.Read(b) + b, err := ReadBytes(r, n) if err != nil { return nil, fmt.Errorf("failed to read list bytes: %w", err) } - if rn > int(n) { - return nil, fmt.Errorf("invalid amount of list bytes read, expected %d, got %d", n, rn) - } return b, nil } @@ -84,15 +111,15 @@ func ReadList[T any](r IndexReader, f func(IndexReader) (T, error)) ([]T, error) if err != nil { return nil, fmt.Errorf("failed to read list length: %w", err) } - vs := make([]T, n) + vs := NewSlice[[]T](n) slog.Debug("reading list elements", "len", n) - for i := range vs { + for i := range n { slog.Debug("reading list element", "index", i) v, err := f(r) if err != nil { return nil, fmt.Errorf("failed to read list element %d: %w", i, err) } - vs[i] = v + vs = append(vs, v) } return vs, nil } diff --git a/go/list_test.go b/go/list_test.go new file mode 100644 index 000000000..1356d8dc3 --- /dev/null +++ b/go/list_test.go @@ -0,0 +1,28 @@ +package wrpc_test + +import ( + "bytes" + "errors" + "io" + "math" + "testing" + + wrpc "wrpc.io/go" +) + +func TestUntrustedLength(t *testing.T) { + want := bytes.Repeat([]byte{0x42}, 1<<20+1) + got, err := wrpc.ReadBytes(bytes.NewReader(want), uint32(len(want))) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, want) { + t.Fatal("read bytes do not match") + } + if _, err := wrpc.ReadBytes(bytes.NewReader(nil), math.MaxUint32); !errors.Is(err, io.ErrUnexpectedEOF) { + t.Fatalf("expected %v, got %v", io.ErrUnexpectedEOF, err) + } + if v := wrpc.NewSlice[[]uint64](math.MaxUint32); len(v) != 0 || cap(v) > 1<<20 { + t.Fatalf("unexpected slice length %d and capacity %d", len(v), cap(v)) + } +} diff --git a/go/primitive.go b/go/primitive.go index 579e0e8e1..167406b48 100644 --- a/go/primitive.go +++ b/go/primitive.go @@ -103,15 +103,11 @@ func ReadString(r ByteReader) (string, error) { return "", fmt.Errorf("failed to read string length: %w", err) } - b := make([]byte, int(n)) slog.Debug("reading string bytes", "len", n) - rn, err := r.Read(b) + b, err := ReadBytes(r, n) if err != nil { return "", fmt.Errorf("failed to read string: %w", err) } - if rn > int(n) { - return "", fmt.Errorf("invalid amount of string bytes read, expected %d, got %d", n, rn) - } slog.Debug("read string bytes", "buf", b) return string(b), nil } diff --git a/go/stream.go b/go/stream.go index c0cf5e05a..64f239a3f 100644 --- a/go/stream.go +++ b/go/stream.go @@ -168,13 +168,13 @@ func ReadStream[T any](r IndexReader, f func(IndexReader) (T, error), path ...ui slog.Debug("pending stream EOF chunk received") return nil, io.EOF } - vs := make([]T, n) - for i := range vs { + vs := NewSlice[[]T](n) + for i := range n { v, err := f(r) if err != nil { return nil, fmt.Errorf("failed to read pending stream chunk element %d: %w", i, err) } - vs[i] = v + vs = append(vs, v) } return vs, nil }), nil diff --git a/js/src/mux.js b/js/src/mux.js index 1815e16ef..7c62b5995 100644 --- a/js/src/mux.js +++ b/js/src/mux.js @@ -8,7 +8,7 @@ // `path` is a `list` index identifying the (sub-)stream; incoming frames // are demultiplexed back to the reader registered for their path. -import { AsyncReader, Chan, Writer } from "./io.js"; +import { AsyncReader, Chan, UNEXPECTED_EOF, Writer } from "./io.js"; /** The wRPC framing protocol version byte. */ export const PROTOCOL = 0x00; @@ -111,11 +111,16 @@ export class Mux { // Stop cleanly at a frame boundary once the peer closes the stream. if (m.remaining().length === 0 && !(await m.more())) break; const depth = Number(await m.varU()); - const path = new Array(depth); - for (let i = 0; i < depth; i++) path[i] = Number(await m.varU()); - const len = Number(await m.varU()); - const data = await m.take(len); - this._route(path, data); + const path = []; + for (let i = 0; i < depth; i++) path.push(Number(await m.varU())); + let len = Number(await m.varU()); + while (len > 0) { + if (m.remaining().length === 0 && !(await m.more())) throw new RangeError(UNEXPECTED_EOF); + const data = m.remaining().slice(0, len); + m.consume(data.length); + len -= data.length; + this._route(path, data); + } } this._closeInbound(); } diff --git a/js/src/session.js b/js/src/session.js index 283dc489b..729fccbbc 100644 --- a/js/src/session.js +++ b/js/src/session.js @@ -194,10 +194,10 @@ async function readStream(ar, elem, queue) { if (elem.kind === "u8") { queue.push(await ar.take(len)); } else { - const chunk = new Array(len); + const chunk = []; for (let k = 0; k < len; k++) { const { value, sink } = await decodeOne(ar, elem); - chunk[k] = value; + chunk.push(value); for (const e of sink) tasks.push(readDeferred(ar.index([base + k, ...e.path]), e)); } queue.push(chunk); diff --git a/js/src/value.js b/js/src/value.js index 10103961f..1a93d1701 100644 --- a/js/src/value.js +++ b/js/src/value.js @@ -80,8 +80,8 @@ function checkInt(v, kind, min, max) { */ function readChunk(r, elem, n, sink, path) { if (elem.kind === "u8") return r.take(n).slice(); - const out = new Array(n); - for (let i = 0; i < n; i++) out[i] = decodeValue(r, elem, sink, childPath(sink, path, i)); + const out = []; + for (let i = 0; i < n; i++) out.push(decodeValue(r, elem, sink, childPath(sink, path, i))); return out; } diff --git a/js/test/session.test.js b/js/test/session.test.js index ff574bf8a..faf3ef28e 100644 --- a/js/test/session.test.js +++ b/js/test/session.test.js @@ -105,6 +105,19 @@ test("an inbound transport failure surfaces to a sub-stream reader", async () => await assert.rejects(pending, /connection reset/); }); +test("frame data is routed as it arrives", async () => { + const bytes = [0x01, 0x00, 0x03, 0x0a, 0x14, 0x1e, 0x01, 0x00, 0x02, 0x28]; + const transport = { + read: async () => (bytes.length > 0 ? new Uint8Array(bytes.splice(0, 1)) : null), + write: () => {}, + }; + const mux = new Mux(transport); + mux.start(); + const reader = mux.incoming([0]); + assert.deepEqual([...(await reader.take(4))], [10, 20, 30, 40]); + await assert.rejects(reader.take(1), /unexpected end of input/); +}); + const futures = "wrpc-test:async/futures"; const fut = t.future(t.string);