Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions minicbor-serde/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::de::{
value::{BorrowedStrDeserializer, U64Deserializer},
};

use minicbor::data::{Tag, Type};
use minicbor::data::{IanaTag, Tag, Type};
use minicbor::decode::{Decoder, Error};

use crate::{
Expand Down Expand Up @@ -121,10 +121,22 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> {
t @ (Type::BytesIndef | Type::StringIndef) =>
Err(Error::type_mismatch(t).with_message("unexpected type").at(self.decoder.position()).into()),

Type::Int => self.deserialize_i128(visitor),

Type::Tag => {
let tag = self.decoder.probe().tag()?;
match tag.try_into() {
Ok(IanaTag::PosBignum) => self.deserialize_u128(visitor),
Ok(IanaTag::NegBignum) => self.deserialize_i128(visitor),
_ => Err(Error::type_mismatch(Type::Tag)
.with_message("unexpected type")
.at(self.decoder.position())
.into())
}
}

t @ (
| Type::Undefined
| Type::Tag
| Type::Int
| Type::Simple
| Type::Break
| Type::Unknown(_)
Expand Down Expand Up @@ -168,6 +180,14 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> {
visitor.visit_u64(self.decoder.u64()?)
}

fn deserialize_i128<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_i128(self.decoder.i128()?)
}

fn deserialize_u128<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_u128(self.decoder.u128()?)
}

fn deserialize_f32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_f32(self.decoder.f32()?)
}
Expand Down
10 changes: 10 additions & 0 deletions minicbor-serde/src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ where
Ok(())
}

fn serialize_i128(self, v: i128) -> Result<Self::Ok, Self::Error> {
self.encoder.i128(v)?;
Ok(())
}

fn serialize_u128(self, v: u128) -> Result<Self::Ok, Self::Error> {
self.encoder.u128(v)?;
Ok(())
}

fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
self.encoder.f32(v)?;
Ok(())
Expand Down
113 changes: 113 additions & 0 deletions minicbor-serde/tests/i128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#![cfg(feature = "alloc")]

use serde::{Deserialize, Serialize};

fn roundtrip_u128(n: u128) {
let v = minicbor_serde::to_vec(n).unwrap();
let m: u128 = minicbor_serde::from_slice(&v).unwrap();
assert_eq!(n, m, "u128 round-trip failed for {n}");

// Wire format must match the raw `minicbor` encoding.
let direct = minicbor::to_vec(n).unwrap();
assert_eq!(v, direct, "serde encoding diverges from minicbor for u128 {n}");
}

fn roundtrip_i128(n: i128) {
let v = minicbor_serde::to_vec(n).unwrap();
let m: i128 = minicbor_serde::from_slice(&v).unwrap();
assert_eq!(n, m, "i128 round-trip failed for {n}");

let direct = minicbor::to_vec(n).unwrap();
assert_eq!(v, direct, "serde encoding diverges from minicbor for i128 {n}");
}

#[test]
fn u128_roundtrip() {
roundtrip_u128(0);
roundtrip_u128(1);
roundtrip_u128(u64::MAX as u128);
roundtrip_u128(u64::MAX as u128 + 1);
roundtrip_u128(u128::MAX);
}

#[test]
fn i128_roundtrip() {
roundtrip_i128(0);
roundtrip_i128(1);
roundtrip_i128(-1);
roundtrip_i128(i64::MIN as i128);
roundtrip_i128(i64::MAX as i128);
roundtrip_i128(u64::MAX as i128);
roundtrip_i128(-(u64::MAX as i128) - 1);
roundtrip_i128(u64::MAX as i128 + 1);
roundtrip_i128(-(u64::MAX as i128) - 2);
roundtrip_i128(i128::MAX);
roundtrip_i128(i128::MIN);
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Wrap {
u: u128,
i: i128,
}

#[test]
fn struct_with_128_fields() {
let w = Wrap { u: u128::MAX, i: i128::MIN };
let v = minicbor_serde::to_vec(&w).unwrap();
let r: Wrap = minicbor_serde::from_slice(&v).unwrap();
assert_eq!(w, r);
}

#[test]
fn deserialize_any_routes_bignums() {
// Custom value type that calls `deserialize_any` and supports the full
// 128-bit range. (Serde's built-in `Content` buffer used by untagged enums
// only stores up to 64 bits — see serde-rs/serde#2912 — so we can't test
// bignum dispatch through an untagged enum here.)
use serde::de;
use std::fmt;

#[derive(Debug, PartialEq)]
enum Any {
U128(u128),
I128(i128),
}

struct AnyVisitor;
impl<'de> de::Visitor<'de> for AnyVisitor {
type Value = Any;

fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a 128-bit integer")
}

fn visit_u128<E: de::Error>(self, v: u128) -> Result<Any, E> { Ok(Any::U128(v)) }
fn visit_i128<E: de::Error>(self, v: i128) -> Result<Any, E> { Ok(Any::I128(v)) }
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Any, E> { Ok(Any::U128(u128::from(v))) }
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Any, E> { Ok(Any::I128(i128::from(v))) }
fn visit_u32<E: de::Error>(self, v: u32) -> Result<Any, E> { Ok(Any::U128(u128::from(v))) }
fn visit_u16<E: de::Error>(self, v: u16) -> Result<Any, E> { Ok(Any::U128(u128::from(v))) }
fn visit_u8<E: de::Error>(self, v: u8) -> Result<Any, E> { Ok(Any::U128(u128::from(v))) }
}

impl<'de> Deserialize<'de> for Any {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
d.deserialize_any(AnyVisitor)
}
}

// Large positive: encoded as positive bignum, deserialize_any must route to visit_u128.
let n = u64::MAX as u128 + 12345;
let v = minicbor::to_vec(n).unwrap();
assert_eq!(Any::U128(n), minicbor_serde::from_slice::<Any>(&v).unwrap());

// Large negative: encoded as negative bignum, deserialize_any must route to visit_i128.
let n: i128 = -(u64::MAX as i128) - 100;
let v = minicbor::to_vec(n).unwrap();
assert_eq!(Any::I128(n), minicbor_serde::from_slice::<Any>(&v).unwrap());

// Small unsigned: encoded as native CBOR u8, must reach visit_u8/u64.
let v = minicbor::to_vec(7u128).unwrap();
assert_eq!(Any::U128(7), minicbor_serde::from_slice::<Any>(&v).unwrap());
}
27 changes: 27 additions & 0 deletions minicbor-tests/tests/identities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ fn i64() {
quickcheck(identity as fn(i64) -> bool)
}

#[test]
fn u128() {
quickcheck(identity as fn(u128) -> bool)
}

#[test]
fn i128() {
quickcheck(identity as fn(i128) -> bool)
}

#[test]
fn int() {
assert!(identity(Int::try_from(-2_i128.pow(64)).unwrap()));
Expand Down Expand Up @@ -87,6 +97,23 @@ fn nonzero_u64() {
quickcheck(identity as fn(core::num::NonZeroU64) -> bool)
}

#[test]
fn nonzero_u128() {
quickcheck(identity as fn(core::num::NonZeroU128) -> bool)
}

#[test]
fn nonzero_i128() {
// quickcheck has no `Arbitrary` impl for `NonZeroI128`, so combine two `i64`s
// to cover the full `i128` range (including the bignum path).
fn property(hi: i64, lo: u64) -> bool {
let raw = (i128::from(hi) << 64) | i128::from(lo);
let n = core::num::NonZeroI128::new(raw).unwrap_or(core::num::NonZeroI128::new(1).unwrap());
identity(n)
}
quickcheck(property as fn(i64, u64) -> bool)
}

#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
#[test]
fn nonzero_usize() {
Expand Down
115 changes: 115 additions & 0 deletions minicbor-tests/tests/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,119 @@ quickcheck! {
let j = minicbor::decode::<Int>(&v).unwrap();
TestResult::from_bool(n == i128::from(j))
}

fn u128_id(n: u128) -> bool {
let v = minicbor::to_vec(n).unwrap();
assert_eq!(minicbor::len(n), v.len());
let m = minicbor::decode::<u128>(&v).unwrap();
n == m
}

fn i128_id(n: i128) -> bool {
let v = minicbor::to_vec(n).unwrap();
assert_eq!(minicbor::len(n), v.len());
let m = minicbor::decode::<i128>(&v).unwrap();
n == m
}

fn u128_compat_u64(n: u64) -> bool {
// u64 encoded values must round-trip through u128 decoding.
let v = minicbor::to_vec(n).unwrap();
let m = minicbor::decode::<u128>(&v).unwrap();
u128::from(n) == m
}

fn i128_compat_i64(n: i64) -> bool {
let v = minicbor::to_vec(n).unwrap();
let m = minicbor::decode::<i128>(&v).unwrap();
i128::from(n) == m
}
}

#[test]
fn u128_edges() {
fn roundtrip(n: u128) {
let v = minicbor::to_vec(n).unwrap();
assert_eq!(minicbor::len(n), v.len());
assert_eq!(n, minicbor::decode::<u128>(&v).unwrap());
}
roundtrip(0);
roundtrip(1);
roundtrip(u64::MAX as u128);
roundtrip(u64::MAX as u128 + 1);
roundtrip(u128::MAX);
}

#[test]
fn i128_edges() {
fn roundtrip(n: i128) {
let v = minicbor::to_vec(n).unwrap();
assert_eq!(minicbor::len(n), v.len());
assert_eq!(n, minicbor::decode::<i128>(&v).unwrap());
}
roundtrip(0);
roundtrip(1);
roundtrip(-1);
roundtrip(i64::MAX as i128);
roundtrip(i64::MIN as i128);
roundtrip(u64::MAX as i128);
roundtrip(-(u64::MAX as i128) - 1); // -2^64, lowest Int-representable value
roundtrip(u64::MAX as i128 + 1); // first value requiring bignum
roundtrip(-(u64::MAX as i128) - 2); // first negative value requiring bignum
roundtrip(i128::MAX);
roundtrip(i128::MIN);
}

#[test]
fn u128_bignum_encoding() {
// u128::MAX -> tag 2 (0xc2), bytes(16) -> 0x50, then 16 x 0xff
let v = minicbor::to_vec(u128::MAX).unwrap();
assert_eq!(v[0], 0xc2);
assert_eq!(v[1], 0x50);
assert_eq!(&v[2..], &[0xff; 16]);
}

#[test]
fn i128_negative_bignum_encoding() {
// i128::MIN = -2^127. -1 - i128::MIN = 2^127 - 1.
// Encoded as tag 3 (0xc3) + bstr(16) + big-endian bytes of (2^127 - 1).
let v = minicbor::to_vec(i128::MIN).unwrap();
assert_eq!(v[0], 0xc3);
assert_eq!(v[1], 0x50);
let mut expected = [0xff_u8; 16];
expected[0] = 0x7f;
assert_eq!(&v[2..], &expected);
}

#[test]
fn u128_decodes_with_leading_zero_bytes() {
// Non-canonical encoding: tag 2 + bstr containing 16 bytes, first three zero.
let mut v = vec![0xc2, 0x50, 0x00, 0x00, 0x00];
v.extend_from_slice(&[0xab; 13]);
let n = minicbor::decode::<u128>(&v).unwrap();
let mut expected = [0_u8; 16];
expected[3..].copy_from_slice(&[0xab; 13]);
assert_eq!(n, u128::from_be_bytes(expected));
}

#[test]
fn u128_rejects_negative_bignum() {
let v = minicbor::to_vec(-1i128).unwrap();
assert_eq!(v[0], 0x20); // -1 encodes as native signed, not bignum
// Force-construct a negative bignum and confirm u128 rejects it.
let v = vec![0xc3, 0x42, 0x01, 0x00];
assert!(minicbor::decode::<u128>(&v).is_err());
}

#[test]
fn i128_rejects_overflowing_positive_bignum() {
// 17-byte bignum, all 0xff, overflows u128 capacity.
let mut v = vec![0xc2, 0x51];
v.extend_from_slice(&[0xff; 17]);
assert!(minicbor::decode::<i128>(&v).is_err());
// 16-byte positive bignum equal to 2^127 must overflow i128.
let mut v = vec![0xc2, 0x50];
v.push(0x80);
v.extend_from_slice(&[0; 15]);
assert!(minicbor::decode::<i128>(&v).is_err());
}
12 changes: 12 additions & 0 deletions minicbor/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ impl IanaTag {
}
}

impl PartialEq<Tag> for IanaTag {
fn eq(&self, other: &Tag) -> bool {
self.tag() == *other
}
}

impl PartialEq<IanaTag> for Tag {
fn eq(&self, other: &IanaTag) -> bool {
*self == other.tag()
}
}

impl TryFrom<Tag> for IanaTag {
type Error = UnknownTag;

Expand Down
20 changes: 11 additions & 9 deletions minicbor/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ macro_rules! decode_basic {
}
}

decode_basic!(u8 i8 u16 i16 u32 i32 u64 i64 bool f32 f64 char);
decode_basic!(u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 bool f32 f64 char);

macro_rules! decode_nonzero {
($($t:ty, $msg:expr)*) => {
Expand All @@ -357,14 +357,16 @@ macro_rules! decode_nonzero {
}

decode_nonzero! {
core::num::NonZeroU8, "unexpected 0 when decoding a `NonZeroU8`"
core::num::NonZeroU16, "unexpected 0 when decoding a `NonZeroU16`"
core::num::NonZeroU32, "unexpected 0 when decoding a `NonZeroU32`"
core::num::NonZeroU64, "unexpected 0 when decoding a `NonZeroU64`"
core::num::NonZeroI8, "unexpected 0 when decoding a `NonZeroI8`"
core::num::NonZeroI16, "unexpected 0 when decoding a `NonZeroI16`"
core::num::NonZeroI32, "unexpected 0 when decoding a `NonZeroI32`"
core::num::NonZeroI64, "unexpected 0 when decoding a `NonZeroI64`"
core::num::NonZeroU8, "unexpected 0 when decoding a `NonZeroU8`"
core::num::NonZeroU16, "unexpected 0 when decoding a `NonZeroU16`"
core::num::NonZeroU32, "unexpected 0 when decoding a `NonZeroU32`"
core::num::NonZeroU64, "unexpected 0 when decoding a `NonZeroU64`"
core::num::NonZeroU128, "unexpected 0 when decoding a `NonZeroU128`"
core::num::NonZeroI8, "unexpected 0 when decoding a `NonZeroI8`"
core::num::NonZeroI16, "unexpected 0 when decoding a `NonZeroI16`"
core::num::NonZeroI32, "unexpected 0 when decoding a `NonZeroI32`"
core::num::NonZeroI64, "unexpected 0 when decoding a `NonZeroI64`"
core::num::NonZeroI128, "unexpected 0 when decoding a `NonZeroI128`"
}

#[cfg(any(target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))]
Expand Down
Loading
Loading