Skip to content

Commit f28d2fd

Browse files
committed
transport: canonicalize NaN floats on encode
1 parent 4f23366 commit f28d2fd

1 file changed

Lines changed: 105 additions & 2 deletions

File tree

crates/transport/src/value.rs

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,70 @@ macro_rules! impl_copy_codec {
931931
};
932932
}
933933

934+
// The Component Model canonical ABI mandates a single canonical `NaN`
935+
// representation for floating point values. Encoding canonicalizes `NaN`s to
936+
// match; decoding is lenient and accepts any `NaN` representation.
937+
//
938+
// See `canonicalize_nan{32,64}` in
939+
// <https://github.com/WebAssembly/component-model/blob/main/design/mvp/canonical-abi/definitions.py>.
940+
const CANONICAL_NAN_F32: u32 = 0x7fc0_0000;
941+
const CANONICAL_NAN_F64: u64 = 0x7ff8_0000_0000_0000;
942+
943+
/// Defines a floating-point codec that canonicalizes `NaN` values on encode to
944+
/// match the Component Model canonical ABI, delegating the actual byte encoding
945+
/// and decoding to the wrapped `wasm-tokio` codec.
946+
macro_rules! impl_canonical_nan_codec {
947+
($name:ident, $inner:ty, $t:ty, $canon:expr) => {
948+
#[doc = concat!("Canonicalizes `NaN`s on encode, wrapping [`", stringify!($inner), "`].")]
949+
#[derive(Debug, Default)]
950+
pub struct $name($inner);
951+
952+
impl tokio_util::codec::Encoder<$t> for $name {
953+
type Error = std::io::Error;
954+
955+
fn encode(&mut self, item: $t, dst: &mut BytesMut) -> Result<(), Self::Error> {
956+
let item = if item.is_nan() {
957+
<$t>::from_bits($canon)
958+
} else {
959+
item
960+
};
961+
self.0.encode(item, dst)
962+
}
963+
}
964+
965+
impl tokio_util::codec::Encoder<&$t> for $name {
966+
type Error = std::io::Error;
967+
968+
fn encode(&mut self, item: &$t, dst: &mut BytesMut) -> Result<(), Self::Error> {
969+
tokio_util::codec::Encoder::<$t>::encode(self, *item, dst)
970+
}
971+
}
972+
973+
impl tokio_util::codec::Encoder<&&$t> for $name {
974+
type Error = std::io::Error;
975+
976+
fn encode(&mut self, item: &&$t, dst: &mut BytesMut) -> Result<(), Self::Error> {
977+
tokio_util::codec::Encoder::<$t>::encode(self, **item, dst)
978+
}
979+
}
980+
981+
impl tokio_util::codec::Decoder for $name {
982+
type Item = $t;
983+
type Error = std::io::Error;
984+
985+
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<$t>, Self::Error> {
986+
self.0.decode(src)
987+
}
988+
}
989+
990+
impl_deferred_sync!($name);
991+
impl_deferred_sync!(CoreVecDecoder<$name>);
992+
};
993+
}
994+
995+
impl_canonical_nan_codec!(CanonicalNanF32Codec, F32Codec, f32, CANONICAL_NAN_F32);
996+
impl_canonical_nan_codec!(CanonicalNanF64Codec, F64Codec, f64, CANONICAL_NAN_F64);
997+
934998
impl_copy_codec!(bool, BoolCodec);
935999
impl_copy_codec!(i8, S8Codec);
9361000
impl_copy_codec!(i16, S16Codec);
@@ -939,8 +1003,8 @@ impl_copy_codec!(i32, S32Codec);
9391003
impl_copy_codec!(u32, U32Codec);
9401004
impl_copy_codec!(i64, S64Codec);
9411005
impl_copy_codec!(u64, U64Codec);
942-
impl_copy_codec!(f32, F32Codec);
943-
impl_copy_codec!(f64, F64Codec);
1006+
impl_copy_codec!(f32, CanonicalNanF32Codec);
1007+
impl_copy_codec!(f64, CanonicalNanF64Codec);
9441008
impl_copy_codec!(char, Utf8Codec);
9451009

9461010
impl<T> Encode<T> for u8 {
@@ -2295,4 +2359,43 @@ mod tests {
22952359
assert_eq!(buf.as_ref(), b"\x42\x42");
22962360
Ok(())
22972361
}
2362+
2363+
#[test]
2364+
fn canonical_nan_f32() {
2365+
let mut enc = <f32 as Encode<NoopStream>>::Encoder::default();
2366+
2367+
// A non-canonical (e.g. signalling) `NaN` is canonicalized on encode.
2368+
let mut buf = BytesMut::new();
2369+
enc.encode(f32::from_bits(0x7f80_0001), &mut buf).unwrap();
2370+
assert_eq!(buf.as_ref(), CANONICAL_NAN_F32.to_le_bytes());
2371+
2372+
// A negative `NaN` is canonicalized to the (positive) canonical `NaN`.
2373+
let mut buf = BytesMut::new();
2374+
enc.encode(f32::from_bits(0xffc0_0000), &mut buf).unwrap();
2375+
assert_eq!(buf.as_ref(), CANONICAL_NAN_F32.to_le_bytes());
2376+
2377+
// Non-`NaN` values are encoded unchanged.
2378+
let mut buf = BytesMut::new();
2379+
enc.encode(1.5_f32, &mut buf).unwrap();
2380+
assert_eq!(buf.as_ref(), 1.5_f32.to_bits().to_le_bytes());
2381+
}
2382+
2383+
#[test]
2384+
fn canonical_nan_f64() {
2385+
let mut enc = <f64 as Encode<NoopStream>>::Encoder::default();
2386+
2387+
let mut buf = BytesMut::new();
2388+
enc.encode(f64::from_bits(0x7ff0_0000_0000_0001), &mut buf)
2389+
.unwrap();
2390+
assert_eq!(buf.as_ref(), CANONICAL_NAN_F64.to_le_bytes());
2391+
2392+
let mut buf = BytesMut::new();
2393+
enc.encode(f64::from_bits(0xfff8_0000_0000_0000), &mut buf)
2394+
.unwrap();
2395+
assert_eq!(buf.as_ref(), CANONICAL_NAN_F64.to_le_bytes());
2396+
2397+
let mut buf = BytesMut::new();
2398+
enc.encode(1.5_f64, &mut buf).unwrap();
2399+
assert_eq!(buf.as_ref(), 1.5_f64.to_bits().to_le_bytes());
2400+
}
22982401
}

0 commit comments

Comments
 (0)