Skip to main content

cbor_core/ext/
rug.rs

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