1use num_bigint::{BigInt, Sign};
2
3extern crate alloc;
4
5use crate::{Datum, Datum::*, MapDatumEntry};
6
7use minicbor::{
8 data::{IanaTag, Tag},
9 encode,
10 encode::{Encoder, Write},
11};
12
13impl<C> encode::Encode<C> for Datum {
14 fn encode<W: Write>(
15 &self,
16 e: &mut Encoder<W>,
17 ctx: &mut C,
18 ) -> Result<(), encode::Error<W::Error>> {
19 encode_datum(self, e, ctx)
20 }
21}
22
23fn encode_datum<C, W: Write>(
24 datum: &Datum,
25 e: &mut Encoder<W>,
26 ctx: &mut C,
27) -> Result<(), encode::Error<W::Error>> {
28 match datum {
29 IntegerDatum(bi) => encode_integer(bi, e, ctx),
30 ByteStringDatum(bytes) => encode_bytestring(bytes, e, ctx),
31 ConstructorDatum { constructor, fields } => {
32 encode_constructor(*constructor, fields, e, ctx)
33 },
34 ListDatum(items) => encode_linear_seq(items, e, ctx),
35 MapDatum(entries) => encode_entries(entries, e, ctx),
36 }
37}
38
39fn encode_integer<W: Write, C>(
40 i: &BigInt,
41 e: &mut Encoder<W>,
42 ctx: &mut C,
43) -> Result<(), encode::Error<W::Error>> {
44 match i64::try_from(i) {
45 Ok(num) => {
46 e.i64(num)?;
47 Ok(())
48 },
49 _ => {
50 let (sign, bytes) = i.to_bytes_be();
51 match sign {
52 Sign::Plus => e.tag(IanaTag::PosBignum)?,
53 Sign::Minus => e.tag(IanaTag::NegBignum)?,
54 Sign::NoSign => unreachable!("NoSign is not possible here, since 0 is a valid i64"),
55 };
56 encode_bytestring(&bytes, e, ctx)
57 },
58 }
59}
60
61const BYTES_CHUNK_SIZE_LIMIT: usize = 64;
62
63fn encode_bytestring<W: Write, C>(
64 bytes: &[u8],
65 e: &mut Encoder<W>,
66 _ctx: &mut C,
67) -> Result<(), encode::Error<W::Error>> {
68 if bytes.len() <= BYTES_CHUNK_SIZE_LIMIT {
69 e.bytes(bytes)?;
70 Ok(())
71 } else {
72 e.begin_bytes()?;
73 for chunk in bytes.chunks(BYTES_CHUNK_SIZE_LIMIT) {
74 e.bytes(chunk)?;
75 }
76 e.end()?;
77 Ok(())
78 }
79}
80
81fn encode_constructor<W: Write, C>(
84 constructor: u64,
85 fields: &[Datum],
86 e: &mut Encoder<W>,
87 ctx: &mut C,
88) -> Result<(), encode::Error<W::Error>> {
89 if constructor < 7 {
90 e.tag(constructor_small_tag(constructor))?;
91 encode_linear_seq(fields, e, ctx)
92 } else if (7..128).contains(&constructor) {
93 e.tag(constructor_big_tag(constructor))?;
94 encode_linear_seq(fields, e, ctx)
95 } else {
96 e.tag(CONSTRUCTOR_OVER_LIMIT_TAG)?;
97 encode_constructor_over_limit_fields(constructor, fields, e, ctx)
98 }
99}
100
101fn encode_linear_seq<W: Write, C>(
102 ds: &[Datum],
103 e: &mut Encoder<W>,
104 ctx: &mut C,
105) -> Result<(), encode::Error<W::Error>> {
106 if ds.is_empty() {
109 e.array(0)?;
110 } else {
111 e.begin_array()?;
112 for d in ds {
113 encode_datum(d, e, ctx)?;
114 }
115 e.end()?;
116 }
117 Ok(())
118}
119
120fn encode_constructor_over_limit_fields<W: Write, C>(
121 constructor: u64,
122 ds: &[Datum],
123 e: &mut Encoder<W>,
124 ctx: &mut C,
125) -> Result<(), encode::Error<W::Error>> {
126 e.array(2)?;
128 e.u64(constructor)?;
129 encode_linear_seq(ds, e, ctx)
130}
131
132fn encode_entries<W: Write, C>(
133 entries: &[MapDatumEntry],
134 e: &mut Encoder<W>,
135 ctx: &mut C,
136) -> Result<(), encode::Error<W::Error>> {
137 let len = entries.len();
139 e.map(len as u64)?;
140 for entry in entries {
141 encode_datum(&entry.key, e, ctx)?;
142 encode_datum(&entry.value, e, ctx)?;
143 }
144 Ok(())
145}
146
147fn constructor_small_tag(value: u64) -> Tag {
148 Tag::new(value + 121)
149}
150
151fn constructor_big_tag(value: u64) -> Tag {
152 Tag::new(1280 + value - 7)
153}
154
155const CONSTRUCTOR_OVER_LIMIT_TAG: Tag = Tag::new(102);
156
157#[cfg(test)]
158mod tests {
159 use crate::{Datum, Datum::*, MapDatumEntry};
160 pub use alloc::{vec, vec::Vec};
161 use num_bigint::BigInt;
162 use num_traits::Num;
163
164 #[test]
165 fn integer_datums_to_cbor() {
166 let small_int_narrow = IntegerDatum(BigInt::from(7u8));
167 let small_int_wide = IntegerDatum(BigInt::from(7i64));
168 let small_int_negative = IntegerDatum(BigInt::from(-9i64));
169 let big_int_positive =
170 IntegerDatum(BigInt::from_str_radix("123456789123456789", 10).unwrap());
171 let big_int_negative =
172 IntegerDatum(BigInt::from_str_radix("-123456789123456789", 10).unwrap());
173
174 test(small_int_narrow, "07");
175 test(small_int_wide, "07");
176 test(small_int_negative, "28");
177 test(big_int_positive, "1b01b69b4bacd05f15");
178 test(big_int_negative, "3b01b69b4bacd05f14");
179 }
180
181 #[test]
182 fn bytestring_datums_to_cbor() {
183 let empty = ByteStringDatum(Vec::new());
184 let shorter_than_64 = ByteStringDatum(Vec::from([65u8; 10]));
185 let just_64 = ByteStringDatum(Vec::from([66u8; 64]));
186 let longer_than_64 = ByteStringDatum(Vec::from([67u8; 65]));
187
188 test(empty, "40");
189 test(shorter_than_64, "4a41414141414141414141");
190 test(
191 just_64,
192 "584042424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242",
193 );
194 test(
195 longer_than_64,
196 "5f5840434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434143ff",
197 );
198 }
199
200 #[test]
201 fn constructor_datums_to_cbor() {
202 fn make_fields(num: u64) -> Vec<Datum> {
203 let mut v = Vec::new();
204 for i in 1..(num + 1) {
205 v.push(IntegerDatum(BigInt::from(i)))
206 }
207 v
208 }
209
210 let small_empty = ConstructorDatum { constructor: 2, fields: Vec::new() };
211 let small_non_empty = ConstructorDatum { constructor: 2, fields: make_fields(3) };
212 let big_empty = ConstructorDatum { constructor: 34, fields: Vec::new() };
213 let big_non_empty = ConstructorDatum { constructor: 34, fields: make_fields(2) };
214 let over_limit_empty = ConstructorDatum { constructor: 130, fields: Vec::new() };
215 let over_limit_non_empty = ConstructorDatum { constructor: 130, fields: make_fields(4) };
216
217 test(small_empty, "d87b80");
218 test(small_non_empty, "d87b9f010203ff");
219 test(big_empty, "d9051b80");
220 test(big_non_empty, "d9051b9f0102ff");
221 test(over_limit_empty, "d86682188280");
222 test(over_limit_non_empty, "d8668218829f01020304ff");
223 }
224
225 #[test]
226 fn list_datums_to_cbor() {
227 test(ListDatum(vec![]), "80");
228 test(ListDatum(vec![ByteStringDatum(hex::decode("deadbeef").unwrap())]), "9f44deadbeefff");
229 test(
230 ListDatum(vec![
231 ByteStringDatum(hex::decode("deadbeef").unwrap()),
232 Datum::integer(13),
233 ListDatum(vec![
234 Datum::integer(42),
235 ByteStringDatum(hex::decode("0102030405").unwrap()),
236 ]),
237 ]),
238 "9f44deadbeef0d9f182a450102030405ffff",
239 );
240 }
241
242 #[test]
243 fn map_datums_to_cbor() {
244 test(MapDatum(vec![]), "a0");
245 test(
246 MapDatum(vec![
247 MapDatumEntry { key: Datum::integer(99), value: Datum::integer(101) },
248 MapDatumEntry {
249 key: ByteStringDatum(hex::decode("deadbeef").unwrap()),
250 value: Datum::integer(3),
251 },
252 ]),
253 "a21863186544deadbeef03",
254 );
255 test(
256 MapDatum(
257 (1..14)
258 .map(|i| MapDatumEntry { key: Datum::integer(i), value: Datum::integer(1) })
259 .collect(),
260 ),
261 "ad0101020103010401050106010701080109010a010b010c010d01",
262 );
263 }
264
265 fn test(d: Datum, expected_hex: &str) {
266 assert_eq!(hex::encode(minicbor::to_vec(d).unwrap()), expected_hex);
267 }
268}