Skip to main content

cbor_core/ext/
rug.rs

1use rug::Integer;
2use rug::integer::Order;
3
4use crate::{
5    Error, Result, Value,
6    integer::IntegerBytes,
7    tag,
8    util::{trim_leading_zeros, u64_from_slice},
9};
10
11// ---------------------------------------------------------------------------
12// Integer → Value
13// ---------------------------------------------------------------------------
14
15impl From<Integer> for Value {
16    /// Encodes a `rug::Integer` as a CBOR integer.
17    ///
18    /// Values that fit in a `u64`/`i64` are encoded as plain integers.
19    /// Larger values are encoded as tag-2/tag-3 big integers.
20    fn from(value: Integer) -> Self {
21        from_rug_integer(&value)
22    }
23}
24
25impl From<&Integer> for Value {
26    fn from(value: &Integer) -> Self {
27        from_rug_integer(value)
28    }
29}
30
31fn from_rug_integer(value: &Integer) -> Value {
32    use std::cmp::Ordering;
33
34    match value.cmp0() {
35        Ordering::Equal => Value::Unsigned(0),
36        Ordering::Greater => {
37            let bytes: Vec<u8> = value.to_digits(Order::MsfBe);
38            let trimmed = trim_leading_zeros(&bytes);
39
40            if let Ok(number) = u64_from_slice(trimmed) {
41                Value::Unsigned(number)
42            } else if bytes.len() == trimmed.len() {
43                Value::tag(tag::POS_BIG_INT, bytes)
44            } else {
45                Value::tag(tag::POS_BIG_INT, trimmed)
46            }
47        }
48        Ordering::Less => {
49            // CBOR negative: -1 - payload, so payload = magnitude - 1
50            let magnitude = Integer::from(-value);
51            let payload = magnitude - 1_u32;
52            let bytes: Vec<u8> = payload.to_digits(Order::MsfBe);
53            let trimmed = trim_leading_zeros(&bytes);
54
55            if let Ok(number) = u64_from_slice(trimmed) {
56                Value::Negative(number)
57            } else if bytes.len() == trimmed.len() {
58                Value::tag(tag::NEG_BIG_INT, bytes)
59            } else {
60                Value::tag(tag::NEG_BIG_INT, trimmed)
61            }
62        }
63    }
64}
65
66// ---------------------------------------------------------------------------
67// Value → Integer
68// ---------------------------------------------------------------------------
69
70impl TryFrom<Value> for Integer {
71    type Error = Error;
72
73    /// Extracts a `rug::Integer` from a CBOR integer value.
74    ///
75    /// Returns `Err(IncompatibleType)` for non-integer values.
76    fn try_from(value: Value) -> Result<Self> {
77        to_rug_integer(&value)
78    }
79}
80
81impl TryFrom<&Value> for Integer {
82    type Error = Error;
83
84    fn try_from(value: &Value) -> Result<Self> {
85        to_rug_integer(value)
86    }
87}
88
89fn to_rug_integer(value: &Value) -> Result<Integer> {
90    match value.as_integer_bytes()? {
91        IntegerBytes::UnsignedOwned(bytes) => Ok(Integer::from(u64::from_be_bytes(bytes))),
92        IntegerBytes::NegativeOwned(bytes) => Ok(Integer::from(!u64::from_be_bytes(bytes) as i64)),
93        IntegerBytes::UnsignedBorrowed(bytes) => Ok(Integer::from_digits(bytes, Order::MsfBe)),
94        IntegerBytes::NegativeBorrowed(bytes) => {
95            // payload = magnitude - 1, so actual = -(payload + 1)
96            let payload = Integer::from_digits(bytes, Order::MsfBe);
97            Ok(-(payload + 1_u32))
98        }
99    }
100}
101
102// ---------------------------------------------------------------------------
103// Tests
104// ---------------------------------------------------------------------------
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::DataType;
110
111    fn roundtrip(n: Integer) -> Integer {
112        let encoded = Value::from(&n).encode();
113        let decoded = Value::decode(encoded).unwrap();
114        Integer::try_from(decoded).unwrap()
115    }
116
117    // ---- Positive / unsigned ----
118
119    #[test]
120    fn zero() {
121        assert_eq!(roundtrip(Integer::ZERO), Integer::ZERO);
122    }
123
124    #[test]
125    fn small_positive() {
126        let n = Integer::from(42);
127        assert_eq!(roundtrip(n.clone()), n);
128    }
129
130    #[test]
131    fn u64_max() {
132        let n = Integer::from(u64::MAX);
133        let v = Value::from(&n);
134        assert!(matches!(v, Value::Unsigned(_)));
135        assert_eq!(Integer::try_from(v).unwrap(), n);
136    }
137
138    #[test]
139    fn u128_max() {
140        let n = Integer::from(u128::MAX);
141        let v = Value::from(&n);
142        assert!(matches!(&v, Value::Tag(2, _)));
143        assert_eq!(Integer::try_from(v).unwrap(), n);
144    }
145
146    #[test]
147    fn from_u128_roundtrip() {
148        for x in [0_u128, 1, 42, u64::MAX as u128, u64::MAX as u128 + 1, u128::MAX] {
149            let expected = Integer::from(x);
150            let via_value = Value::from(x);
151            assert_eq!(Integer::try_from(via_value).unwrap(), expected, "u128={x}");
152        }
153    }
154
155    // ---- Negative ----
156
157    #[test]
158    fn negative_one() {
159        let n = Integer::from(-1);
160        assert_eq!(roundtrip(n.clone()), n);
161    }
162
163    #[test]
164    fn i64_min() {
165        let n = Integer::from(i64::MIN);
166        assert_eq!(roundtrip(n.clone()), n);
167    }
168
169    #[test]
170    fn i128_min() {
171        let n = Integer::from(i128::MIN);
172        let v = Value::from(&n);
173        assert!(matches!(&v, Value::Tag(3, _)));
174        assert_eq!(Integer::try_from(v).unwrap(), n);
175    }
176
177    #[test]
178    fn i128_max() {
179        let n = Integer::from(i128::MAX);
180        let v = Value::from(&n);
181        assert_eq!(Integer::try_from(v).unwrap(), n);
182    }
183
184    #[test]
185    fn from_i128_roundtrip() {
186        for x in [
187            0_i128,
188            1,
189            -1,
190            42,
191            -42,
192            i64::MIN as i128,
193            i64::MAX as i128,
194            i128::MIN,
195            i128::MAX,
196        ] {
197            let expected = Integer::from(x);
198            let via_value = Value::from(x);
199            assert_eq!(Integer::try_from(via_value).unwrap(), expected, "i128={x}");
200        }
201    }
202
203    #[test]
204    fn large_negative() {
205        // -(u128::MAX) — requires tag-3 big integer
206        let n = -Integer::from(u128::MAX);
207        let v = Value::from(&n);
208        assert!(matches!(&v, Value::Tag(3, _)));
209        assert_eq!(Integer::try_from(v).unwrap(), n);
210    }
211
212    // ---- Error cases ----
213
214    #[test]
215    fn non_integer_errors() {
216        assert_eq!(
217            Integer::try_from(Value::from(0.5)),
218            Err(Error::IncompatibleType(DataType::Float16))
219        );
220        assert_eq!(
221            Integer::try_from(Value::null()),
222            Err(Error::IncompatibleType(DataType::Null))
223        );
224    }
225}