Skip to content

Commit 1e24384

Browse files
committed
Fix: nint deserialization below i64::MIN
cbor_event's `Deserializer::negative_integer()` panics when it encounters a number below i64::MIN despite numbers down to -u64::MAX - 1 being valid for a CBOR nint. This fix covers both `BigInt` as well as `Int`.
1 parent c0a8f9d commit 1e24384

File tree

1 file changed

+60
-20
lines changed

1 file changed

+60
-20
lines changed

rust/src/utils.rs

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,8 @@ impl Deserialize for Value {
479479
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
480480
pub struct Int(pub (crate) i128);
481481

482+
to_from_bytes!(Int);
483+
482484
#[wasm_bindgen]
483485
impl Int {
484486
pub fn new(x: &BigNum) -> Self {
@@ -573,13 +575,28 @@ impl Deserialize for Int {
573575
(|| -> Result<_, DeserializeError> {
574576
match raw.cbor_type()? {
575577
cbor_event::Type::UnsignedInteger => Ok(Self(raw.unsigned_integer()? as i128)),
576-
cbor_event::Type::NegativeInteger => Ok(Self(raw.negative_integer()? as i128)),
578+
cbor_event::Type::NegativeInteger => Ok(Self(read_nint(raw)?)),
577579
_ => Err(DeserializeFailure::NoVariantMatched.into()),
578580
}
579581
})().map_err(|e| e.annotate("Int"))
580582
}
581583
}
582584

585+
fn read_nint<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result <i128, DeserializeError> {
586+
let found = raw.cbor_type()?;
587+
if found != cbor_event::Type::NegativeInteger {
588+
return Err(cbor_event::Error::Expected(cbor_event::Type::NegativeInteger, found).into());
589+
}
590+
let (len, len_sz) = raw.cbor_len()?;
591+
match len {
592+
cbor_event::Len::Indefinite => Err(cbor_event::Error::IndefiniteLenNotSupported(cbor_event::Type::NegativeInteger).into()),
593+
cbor_event::Len::Len(v) => {
594+
raw.advance(1 + len_sz)?;
595+
Ok(-(v as i128) - 1)
596+
}
597+
}
598+
}
599+
583600
const BOUNDED_BYTES_CHUNK_SIZE: usize = 64;
584601

585602
pub (crate) fn write_bounded_bytes<'se, W: Write>(serializer: &'se mut Serializer<W>, bytes: &[u8]) -> cbor_event::Result<&'se mut Serializer<W>> {
@@ -706,22 +723,28 @@ impl cbor_event::se::Serialize for BigInt {
706723
num_bigint::Sign::Minus => serializer.write_negative_integer(-(*u64_digits.first().unwrap() as i128) as i64),
707724
},
708725
_ => {
726+
// Small edge case: nint's minimum is -18446744073709551616 but in this bigint lib
727+
// that takes 2 u64 bytes so we put that as a special case here:
728+
if sign == num_bigint::Sign::Minus && u64_digits == vec![0, 1] {
729+
serializer.write_negative_integer(-18446744073709551616i128 as i64)
730+
} else {
709731
let (sign, bytes) = self.0.to_bytes_be();
710-
match sign {
711-
// positive bigint
712-
num_bigint::Sign::Plus |
713-
num_bigint::Sign::NoSign => {
714-
serializer.write_tag(2u64)?;
715-
write_bounded_bytes(serializer, &bytes)
716-
},
717-
// negative bigint
718-
num_bigint::Sign::Minus => {
719-
serializer.write_tag(3u64)?;
720-
use std::ops::Neg;
721-
// CBOR RFC defines this as the bytes of -n -1
722-
let adjusted = self.0.clone().neg().checked_sub(&num_bigint::BigInt::from(1u32)).unwrap().to_biguint().unwrap();
723-
write_bounded_bytes(serializer, &adjusted.to_bytes_be())
724-
},
732+
match sign {
733+
// positive bigint
734+
num_bigint::Sign::Plus |
735+
num_bigint::Sign::NoSign => {
736+
serializer.write_tag(2u64)?;
737+
write_bounded_bytes(serializer, &bytes)
738+
},
739+
// negative bigint
740+
num_bigint::Sign::Minus => {
741+
serializer.write_tag(3u64)?;
742+
use std::ops::Neg;
743+
// CBOR RFC defines this as the bytes of -n -1
744+
let adjusted = self.0.clone().neg().checked_sub(&num_bigint::BigInt::from(1u32)).unwrap().to_biguint().unwrap();
745+
write_bounded_bytes(serializer, &adjusted.to_bytes_be())
746+
},
747+
}
725748
}
726749
},
727750
}
@@ -753,7 +776,7 @@ impl Deserialize for BigInt {
753776
// uint
754777
CBORType::UnsignedInteger => Ok(Self(num_bigint::BigInt::from(raw.unsigned_integer()?))),
755778
// nint
756-
CBORType::NegativeInteger => Ok(Self(num_bigint::BigInt::from(raw.negative_integer()?))),
779+
CBORType::NegativeInteger => Ok(Self(num_bigint::BigInt::from(read_nint(raw)?))),
757780
_ => return Err(DeserializeFailure::NoVariantMatched.into()),
758781
}
759782
})().map_err(|e| e.annotate("BigInt"))
@@ -2085,9 +2108,8 @@ mod tests {
20852108
assert_eq!(hex::decode("c249010000000000000000").unwrap(), BigInt::from_str("18446744073709551616").unwrap().to_bytes());
20862109
// uint
20872110
assert_eq!(hex::decode("1b000000e8d4a51000").unwrap(), BigInt::from_str("1000000000000").unwrap().to_bytes());
2088-
// nint
2089-
// we can't use this due to cbor_event actually not supporting the full NINT spectrum as it uses an i64 for some reason...
2090-
//assert_eq!(hex::decode("3bffffffffffffffff").unwrap(), BigInt::from_str("-18446744073709551616").unwrap().to_bytes());
2111+
// nint (lowest possible - used to be unsupported but works now)
2112+
assert_eq!(hex::decode("3bffffffffffffffff").unwrap(), BigInt::from_str("-18446744073709551616").unwrap().to_bytes());
20912113
// this one fits in an i64 though
20922114
assert_eq!(hex::decode("3903e7").unwrap(), BigInt::from_str("-1000").unwrap().to_bytes());
20932115

@@ -2299,4 +2321,22 @@ mod tests {
22992321
assert_eq!(Int::new_i32(42).as_i32_or_fail().unwrap(), 42);
23002322
assert_eq!(Int::new_i32(-42).as_i32_or_fail().unwrap(), -42);
23012323
}
2324+
2325+
#[test]
2326+
fn int_full_range() {
2327+
// cbor_event's nint API worked via i64 but we now have a workaround for it
2328+
// so these tests are here to make sure that workaround works.
2329+
2330+
// first nint below of i64::MIN
2331+
let bytes_x = vec![0x3b, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
2332+
let x = Int::from_bytes(bytes_x.clone()).unwrap();
2333+
assert_eq!(x.to_str(), "-9223372036854775809");
2334+
assert_eq!(bytes_x, x.to_bytes());
2335+
2336+
// smallest possible nint which is -u64::MAX - 1
2337+
let bytes_y = vec![0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
2338+
let y = Int::from_bytes(bytes_y.clone()).unwrap();
2339+
assert_eq!(y.to_str(), "-18446744073709551616");
2340+
assert_eq!(bytes_y, y.to_bytes());
2341+
}
23022342
}

0 commit comments

Comments
 (0)