Skip to main content

cbor_core/ext/
rug.rs

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