1use std::{
2 ascii, cmp,
3 convert::TryFrom,
4 i64, iter,
5 net::{Ipv4Addr, Ipv6Addr},
6};
7
8use super::Encoding;
9use chrono::{DateTime, NaiveDate, NaiveDateTime};
10use half::f16;
11use num_bigint::{BigInt, BigUint, Sign};
12use num_rational::{BigRational, Ratio};
13use num_traits::pow::pow;
14use separator::Separatable;
15use url::Url;
16use uuid::Uuid;
17
18use crate::{parse_bytes, ByteString, DataItem, FloatWidth, IntegerWidth, Simple, Tag, TextString};
19
20struct Context {
21 encoding: Option<Encoding>,
22 reference_count: u64,
23}
24
25impl Context {
26 fn with_encoding<T>(
27 &mut self,
28 encoding: Option<Encoding>,
29 f: impl FnOnce(&mut Self) -> T,
30 ) -> T {
31 let encoding = std::mem::replace(&mut self.encoding, encoding);
32 let value = f(self);
33 self.encoding = encoding;
34 value
35 }
36}
37
38struct Line {
39 hex: String,
40 comment: String,
41 sublines: Vec<Line>,
42}
43
44impl Line {
45 fn new(hex: impl Into<String>, comment: impl Into<String>) -> Line {
46 Line {
47 hex: hex.into(),
48 comment: comment.into(),
49 sublines: Vec::new(),
50 }
51 }
52
53 fn from_value(context: &mut Context, value: &DataItem) -> Line {
54 match *value {
55 DataItem::Integer { value, bitwidth } => integer_to_hex(value, bitwidth),
56 DataItem::Negative { value, bitwidth } => negative_to_hex(value, bitwidth),
57 DataItem::ByteString(ref bytestring) => {
58 definite_bytestring_to_hex(context.encoding, bytestring)
59 }
60 DataItem::IndefiniteByteString(ref bytestrings) => {
61 indefinite_string_to_hex(0x02, "bytes", bytestrings, |bytestring| {
62 definite_bytestring_to_hex(context.encoding, bytestring)
63 })
64 }
65 DataItem::TextString(ref textstring) => definite_textstring_to_hex(textstring),
66 DataItem::IndefiniteTextString(ref textstrings) => {
67 indefinite_string_to_hex(0x03, "text", textstrings, definite_textstring_to_hex)
68 }
69 DataItem::Array { ref data, bitwidth } => array_to_hex(context, data, bitwidth),
70 DataItem::Map { ref data, bitwidth } => map_to_hex(context, data, bitwidth),
71 DataItem::Tag {
72 tag,
73 bitwidth,
74 ref value,
75 } => tagged_to_hex(context, tag, bitwidth, value),
76 DataItem::Float { value, bitwidth } => float_to_hex(value, bitwidth),
77 DataItem::Simple(simple) => simple_to_hex(simple),
78 }
79 }
80
81 fn merge(self) -> String {
82 let hex_width = self.hex_width();
83 let mut output = String::with_capacity(128);
84 self.do_merge(hex_width as isize, 0, &mut output);
85 output
86 }
87
88 fn do_merge(self, hex_width: isize, indent_level: usize, output: &mut String) {
89 use std::fmt::Write;
90
91 let (hex_indent, width) = if hex_width < 0 {
92 (indent_level * 3 - hex_width.unsigned_abs(), 0)
93 } else {
94 (indent_level * 3, hex_width as usize)
95 };
96
97 writeln!(
98 output,
99 "{blank:hex_indent$}{hex:width$} # {blank:comment_indent$}{comment}",
100 blank = "",
101 hex_indent = hex_indent,
102 comment_indent = indent_level * 2,
103 hex = self.hex,
104 width = width,
105 comment = self.comment
106 )
107 .unwrap();
108
109 for line in self.sublines {
110 line.do_merge(hex_width - 3, indent_level + 1, output);
111 }
112 }
113
114 fn hex_width(&self) -> usize {
115 cmp::max(
116 self.hex.len(),
117 self.sublines
118 .iter()
119 .map(|line| {
120 let subwidth = line.hex_width();
121 if subwidth == 0 {
122 0
123 } else {
124 subwidth + 3
125 }
126 })
127 .max()
128 .unwrap_or(0),
129 )
130 }
131}
132
133fn integer_to_hex(value: u64, mut bitwidth: IntegerWidth) -> Line {
134 if bitwidth == IntegerWidth::Unknown {
135 bitwidth = if value < 24 {
136 IntegerWidth::Zero
137 } else if value <= u64::from(u8::max_value()) {
138 IntegerWidth::Eight
139 } else if value <= u64::from(u16::max_value()) {
140 IntegerWidth::Sixteen
141 } else if value <= u64::from(u32::max_value()) {
142 IntegerWidth::ThirtyTwo
143 } else {
144 IntegerWidth::SixtyFour
145 };
146 }
147
148 let hex = match bitwidth {
149 IntegerWidth::Unknown => unreachable!(),
150 IntegerWidth::Zero => format!("{value:02x}"),
151 IntegerWidth::Eight => format!("18 {value:02x}"),
152 IntegerWidth::Sixteen => format!("19 {value:04x}"),
153 IntegerWidth::ThirtyTwo => format!("1a {value:08x}"),
154 IntegerWidth::SixtyFour => format!("1b {value:016x}"),
155 };
156
157 let comment = format!("unsigned({})", value.separated_string());
158
159 Line::new(hex, comment)
160}
161
162fn negative_to_hex(value: u64, mut bitwidth: IntegerWidth) -> Line {
163 if bitwidth == IntegerWidth::Unknown {
164 bitwidth = if value < 24 {
165 IntegerWidth::Zero
166 } else if value <= u64::from(u8::max_value()) {
167 IntegerWidth::Eight
168 } else if value <= u64::from(u16::max_value()) {
169 IntegerWidth::Sixteen
170 } else if value <= u64::from(u32::max_value()) {
171 IntegerWidth::ThirtyTwo
172 } else {
173 IntegerWidth::SixtyFour
174 };
175 }
176
177 let hex = match bitwidth {
178 IntegerWidth::Unknown => unreachable!(),
179 IntegerWidth::Zero => format!("{:02x}", value + 0x20),
180 IntegerWidth::Eight => format!("38 {value:02x}"),
181 IntegerWidth::Sixteen => format!("39 {value:04x}"),
182 IntegerWidth::ThirtyTwo => format!("3a {value:08x}"),
183 IntegerWidth::SixtyFour => format!("3b {value:016x}"),
184 };
185
186 let comment = format!("negative({})", (-1 - i128::from(value)).separated_string());
187
188 Line::new(hex, comment)
189}
190
191fn length_to_hex(
192 length: Option<usize>,
193 mut bitwidth: Option<IntegerWidth>,
194 major: u8,
195 kind: &str,
196) -> Line {
197 if bitwidth == Some(IntegerWidth::Unknown) {
200 bitwidth = if length.unwrap() < 24 {
201 Some(IntegerWidth::Zero)
202 } else if length.unwrap() < usize::from(u8::max_value()) {
203 Some(IntegerWidth::Eight)
204 } else if length.unwrap() < usize::from(u16::max_value()) {
205 Some(IntegerWidth::Sixteen)
206 } else if length.unwrap() < u32::max_value() as usize {
207 Some(IntegerWidth::ThirtyTwo)
208 } else {
209 Some(IntegerWidth::SixtyFour)
210 };
211 }
212
213 let hex = match bitwidth {
214 Some(IntegerWidth::Unknown) => unreachable!(),
215 Some(IntegerWidth::Zero) => format!("{:02x}", (length.unwrap() as u8) + (major << 5)),
216 Some(IntegerWidth::Eight) => format!("{:02x} {:02x}", (major << 5) | 0x18, length.unwrap()),
217 Some(IntegerWidth::Sixteen) => {
218 format!("{:02x} {:04x}", (major << 5) | 0x19, length.unwrap())
219 }
220 Some(IntegerWidth::ThirtyTwo) => {
221 format!("{:02x} {:08x}", (major << 5) | 0x1a, length.unwrap())
222 }
223 Some(IntegerWidth::SixtyFour) => {
224 format!("{:02x} {:016x}", (major << 5) | 0x1b, length.unwrap())
225 }
226 None => format!("{:02x}", (major << 5) | 0x1F),
227 };
228
229 let comment = format!(
230 "{kind}({length})",
231 kind = kind,
232 length = if bitwidth.is_some() {
233 length.unwrap().to_string()
234 } else {
235 "*".to_owned()
236 },
237 );
238
239 Line::new(hex, comment)
240}
241
242fn bytes_to_hex(encoding: Option<Encoding>, data: &[u8]) -> impl Iterator<Item = Line> + '_ {
243 data.chunks(16).map(move |datum| {
244 let hex = data_encoding::HEXLOWER.encode(datum);
245 let comment = match encoding {
246 Some(Encoding::Base64Url) => {
247 let mut comment = "b64'".to_owned();
248 data_encoding::BASE64URL_NOPAD.encode_append(data, &mut comment);
249 comment.push('\'');
250 comment
251 }
252 Some(Encoding::Base64) => {
253 let mut comment = "b64'".to_owned();
254 data_encoding::BASE64.encode_append(data, &mut comment);
255 comment.push('\'');
256 comment
257 }
258 Some(Encoding::Base16) => format!("h'{hex}'"),
259 None => {
260 let text: String = datum
261 .iter()
262 .cloned()
263 .flat_map(ascii::escape_default)
264 .map(char::from)
265 .collect();
266 format!(r#""{text}""#)
267 }
268 };
269 Line::new(hex, comment)
270 })
271}
272
273fn definite_bytestring_to_hex(encoding: Option<Encoding>, bytestring: &ByteString) -> Line {
274 let ByteString { ref data, bitwidth } = *bytestring;
275
276 let mut line = length_to_hex(Some(data.len()), Some(bitwidth), 2, "bytes");
277
278 if data.is_empty() {
279 line.sublines.push(Line::new("", "\"\""));
280 } else {
281 line.sublines.extend(bytes_to_hex(encoding, data))
282 }
283
284 line
285}
286
287fn definite_textstring_to_hex(textstring: &TextString) -> Line {
288 let TextString { ref data, bitwidth } = *textstring;
289
290 let mut line = length_to_hex(Some(data.len()), Some(bitwidth), 3, "text");
291
292 if data.is_empty() {
293 line.sublines.push(Line::new("", "\"\""));
294 } else {
295 let mut push_line = |datum: &str| {
296 let hex = data_encoding::HEXLOWER.encode(datum.as_bytes());
297 let mut comment = String::with_capacity(datum.len());
298 comment.push('"');
299 for c in datum.chars() {
300 if c == '\"' || c == '\\' || c.is_control() {
301 for c in c.escape_default() {
302 comment.push(c);
303 }
304 } else {
305 comment.push(c);
306 }
307 }
308 comment.push('"');
309 line.sublines.push(Line::new(hex, comment));
310 };
311
312 if data.len() <= 24 {
313 push_line(data);
314 } else {
315 let mut data = data.as_str();
316 while !data.is_empty() {
317 let mut split = 16;
318 while !data.is_char_boundary(split) {
319 split -= 1;
320 }
321 let (datum, new_data) = data.split_at(split);
322 data = new_data;
323 push_line(datum);
324 }
325 }
326 }
327
328 line
329}
330
331fn indefinite_string_to_hex<T>(
332 major: u8,
333 name: &str,
334 strings: &[T],
335 definite_string_to_hex: impl Fn(&T) -> Line,
336) -> Line {
337 let mut line = length_to_hex(None, None, major, name);
338
339 line.sublines
340 .extend(strings.iter().map(definite_string_to_hex));
341 line.sublines.push(Line::new("ff", "break"));
342
343 line
344}
345
346fn array_to_hex(context: &mut Context, array: &[DataItem], bitwidth: Option<IntegerWidth>) -> Line {
347 let mut line = length_to_hex(Some(array.len()), bitwidth, 4, "array");
348
349 line.sublines
350 .extend(array.iter().map(|value| Line::from_value(context, value)));
351
352 if bitwidth.is_none() {
353 line.sublines.push(Line::new("ff", "break"));
354 }
355
356 line
357}
358
359fn map_to_hex(
360 context: &mut Context,
361 values: &[(DataItem, DataItem)],
362 bitwidth: Option<IntegerWidth>,
363) -> Line {
364 let mut line = length_to_hex(Some(values.len()), bitwidth, 5, "map");
365
366 line.sublines.extend(
367 values
368 .iter()
369 .flat_map(|(v1, v2)| iter::once(v1).chain(iter::once(v2)))
370 .map(|value| Line::from_value(context, value)),
371 );
372
373 if bitwidth.is_none() {
374 line.sublines.push(Line::new("ff", "break"));
375 }
376
377 line
378}
379
380fn tagged_to_hex(
381 context: &mut Context,
382 tag: Tag,
383 mut bitwidth: IntegerWidth,
384 value: &DataItem,
385) -> Line {
386 let tag_value = tag.0;
387 if bitwidth == IntegerWidth::Unknown {
388 bitwidth = if tag_value < 24 {
389 IntegerWidth::Zero
390 } else if tag_value < u64::from(u8::max_value()) {
391 IntegerWidth::Eight
392 } else if tag_value < u64::from(u16::max_value()) {
393 IntegerWidth::Sixteen
394 } else if tag_value < u64::from(u32::max_value()) {
395 IntegerWidth::ThirtyTwo
396 } else {
397 IntegerWidth::SixtyFour
398 };
399 }
400
401 let hex = match bitwidth {
402 IntegerWidth::Unknown => unreachable!(),
403 IntegerWidth::Zero => format!("{:02x}", 0xc0 | tag_value),
404 IntegerWidth::Eight => format!("d8 {tag_value:02x}"),
405 IntegerWidth::Sixteen => format!("d9 {tag_value:04x}"),
406 IntegerWidth::ThirtyTwo => format!("da {tag_value:08x}"),
407 IntegerWidth::SixtyFour => format!("db {tag_value:016x}"),
408 };
409
410 let extra = match tag {
411 Tag::DATETIME => Some("standard datetime string"),
412 Tag::EPOCH_DATETIME => Some("epoch datetime value"),
413 Tag::POSITIVE_BIGNUM => Some("positive bignum"),
414 Tag::NEGATIVE_BIGNUM => Some("negative bignum"),
415 Tag::DECIMAL_FRACTION => Some("decimal fraction"),
416 Tag::BIGFLOAT => Some("bigfloat"),
417 Tag::ENCODED_BASE64URL => Some("suggested base64url encoding"),
418 Tag::ENCODED_BASE64 => Some("suggested base64 encoding"),
419 Tag::ENCODED_BASE16 => Some("suggested base16 encoding"),
420 Tag::ENCODED_CBOR => Some("encoded cbor data item"),
421 Tag::ENCODED_CBOR_SEQ => Some("encoded cbor sequence"),
422 Tag::URI => Some("uri"),
423 Tag::BASE64URL => Some("base64url encoded text"),
424 Tag::BASE64 => Some("base64 encoded text"),
425 Tag::REGEX => Some("regex"),
426 Tag::MIME => Some("mime message"),
427 Tag::UUID => Some("uuid"),
428 Tag::NETWORK_ADDRESS => Some("network address"),
429 Tag::SELF_DESCRIBE_CBOR => Some("self describe cbor"),
430 Tag::EPOCH_DATE => Some("epoch date value"),
431 Tag::DATE => Some("standard date string"),
432 Tag::SHAREABLE => Some("shareable value"),
433 Tag::SHARED_REF => Some("reference to shared value"),
434 Tag::IPV4 => Some("ipv4 address and/or prefix"),
435 Tag::IPV6 => Some("ipv6 address and/or prefix"),
436 Tag::TYPED_ARRAY_U8 => Some("typed array of u8"),
437 Tag::TYPED_ARRAY_U16_LITTLE_ENDIAN => Some("typed array of u16, little endian"),
438 Tag::TYPED_ARRAY_U32_LITTLE_ENDIAN => Some("typed array of u32, little endian"),
439 Tag::TYPED_ARRAY_U64_LITTLE_ENDIAN => Some("typed array of u64, little endian"),
440 Tag::TYPED_ARRAY_U8_CLAMPED => Some("typed array of u8, clamped"),
441 Tag::TYPED_ARRAY_U16_BIG_ENDIAN => Some("typed array of u16, big endian"),
442 Tag::TYPED_ARRAY_U32_BIG_ENDIAN => Some("typed array of u32, big endian"),
443 Tag::TYPED_ARRAY_U64_BIG_ENDIAN => Some("typed array of u64, big endian"),
444 Tag::TYPED_ARRAY_I8 => Some("typed array of u8"),
445 Tag::TYPED_ARRAY_I16_LITTLE_ENDIAN => {
446 Some("typed array of i16, little endian, twos-complement")
447 }
448 Tag::TYPED_ARRAY_I32_LITTLE_ENDIAN => {
449 Some("typed array of i32, little endian, twos-complement")
450 }
451 Tag::TYPED_ARRAY_I64_LITTLE_ENDIAN => {
452 Some("typed array of i64, little endian, twos-complement")
453 }
454 Tag::TYPED_ARRAY_I16_BIG_ENDIAN => Some("typed array of i16, big endian, twos-complement"),
455 Tag::TYPED_ARRAY_I32_BIG_ENDIAN => Some("typed array of i32, big endian, twos-complement"),
456 Tag::TYPED_ARRAY_I64_BIG_ENDIAN => Some("typed array of i64, big endian, twos-complement"),
457 Tag::TYPED_ARRAY_F16_LITTLE_ENDIAN => Some("typed array of f16, little endian"),
458 Tag::TYPED_ARRAY_F32_LITTLE_ENDIAN => Some("typed array of f32, little endian"),
459 Tag::TYPED_ARRAY_F64_LITTLE_ENDIAN => Some("typed array of f64, little endian"),
460 Tag::TYPED_ARRAY_F128_LITTLE_ENDIAN => Some("typed array of f128, little endian"),
461 Tag::TYPED_ARRAY_F16_BIG_ENDIAN => Some("typed array of f16, big endian"),
462 Tag::TYPED_ARRAY_F32_BIG_ENDIAN => Some("typed array of f32, big endian"),
463 Tag::TYPED_ARRAY_F64_BIG_ENDIAN => Some("typed array of f64, big endian"),
464 Tag::TYPED_ARRAY_F128_BIG_ENDIAN => Some("typed array of f128, big endian"),
465 _ => None,
466 };
467
468 let extra_lines = match tag {
469 Tag::DATETIME => vec![datetime_epoch(value)],
470 Tag::EPOCH_DATETIME => vec![epoch_datetime(value)],
471 Tag::POSITIVE_BIGNUM => vec![positive_bignum(value)],
472 Tag::NEGATIVE_BIGNUM => vec![negative_bignum(value)],
473 Tag::DECIMAL_FRACTION => vec![decimal_fraction(value)],
474 Tag::BIGFLOAT => vec![bigfloat(value)],
475 Tag::URI => vec![uri(value)],
476 Tag::BASE64URL => vec![base64url(value)],
477 Tag::BASE64 => vec![base64(value)],
478 Tag::ENCODED_CBOR => vec![encoded_cbor(value)],
479 Tag::ENCODED_CBOR_SEQ => encoded_cbor_seq(value),
480 Tag::NETWORK_ADDRESS => vec![network_address(value)],
481 Tag::UUID => vec![uuid(value)],
482 Tag::EPOCH_DATE => vec![epoch_date(value)],
483 Tag::DATE => vec![date_epoch(value)],
484 Tag::SHAREABLE => {
485 let line = format!("reference({})", context.reference_count.separated_string());
486 context.reference_count += 1;
487 vec![Line::new("", line)]
488 }
489 Tag::SHARED_REF => vec![shared_ref(value, context.reference_count)],
490 Tag::IPV4 => vec![ipv4_address_or_prefix(value)],
491 Tag::IPV6 => vec![ipv6_address_or_prefix(value)],
492 _ => vec![],
493 };
494
495 let sublines = match tag {
496 Tag::ENCODED_BASE64URL => context.with_encoding(Some(Encoding::Base64Url), |context| {
497 vec![Line::from_value(context, value)]
498 }),
499 Tag::ENCODED_BASE64 => context.with_encoding(Some(Encoding::Base64), |context| {
500 vec![Line::from_value(context, value)]
501 }),
502 Tag::ENCODED_BASE16 | Tag::NETWORK_ADDRESS | Tag::UUID | Tag::IPV4 | Tag::IPV6 => context
503 .with_encoding(Some(Encoding::Base16), |context| {
504 vec![Line::from_value(context, value)]
505 }),
506 Tag::TYPED_ARRAY_U8 | Tag::TYPED_ARRAY_U8_CLAMPED => {
507 typed_array::<1>(context, value, "unsigned", |[byte]| byte.to_string())
508 }
509 Tag::TYPED_ARRAY_U16_LITTLE_ENDIAN => {
510 typed_array::<2>(context, value, "unsigned", |bytes| {
511 u16::from_le_bytes(bytes).separated_string()
512 })
513 }
514 Tag::TYPED_ARRAY_U32_LITTLE_ENDIAN => {
515 typed_array::<4>(context, value, "unsigned", |bytes| {
516 u32::from_le_bytes(bytes).separated_string()
517 })
518 }
519 Tag::TYPED_ARRAY_U64_LITTLE_ENDIAN => {
520 typed_array::<8>(context, value, "unsigned", |bytes| {
521 u64::from_le_bytes(bytes).separated_string()
522 })
523 }
524 Tag::TYPED_ARRAY_U16_BIG_ENDIAN => typed_array::<2>(context, value, "unsigned", |bytes| {
525 u16::from_be_bytes(bytes).separated_string()
526 }),
527 Tag::TYPED_ARRAY_U32_BIG_ENDIAN => typed_array::<4>(context, value, "unsigned", |bytes| {
528 u32::from_be_bytes(bytes).separated_string()
529 }),
530 Tag::TYPED_ARRAY_U64_BIG_ENDIAN => typed_array::<8>(context, value, "unsigned", |bytes| {
531 u64::from_be_bytes(bytes).separated_string()
532 }),
533 Tag::TYPED_ARRAY_I8 => {
534 typed_array::<1>(context, value, "signed", |[byte]| (byte as i8).to_string())
535 }
536 Tag::TYPED_ARRAY_I16_LITTLE_ENDIAN => typed_array::<2>(context, value, "signed", |bytes| {
537 i16::from_le_bytes(bytes).separated_string()
538 }),
539 Tag::TYPED_ARRAY_I32_LITTLE_ENDIAN => typed_array::<4>(context, value, "signed", |bytes| {
540 i32::from_le_bytes(bytes).separated_string()
541 }),
542 Tag::TYPED_ARRAY_I64_LITTLE_ENDIAN => typed_array::<8>(context, value, "signed", |bytes| {
543 i64::from_le_bytes(bytes).separated_string()
544 }),
545 Tag::TYPED_ARRAY_I16_BIG_ENDIAN => typed_array::<2>(context, value, "signed", |bytes| {
546 i16::from_be_bytes(bytes).separated_string()
547 }),
548 Tag::TYPED_ARRAY_I32_BIG_ENDIAN => typed_array::<4>(context, value, "signed", |bytes| {
549 i32::from_be_bytes(bytes).separated_string()
550 }),
551 Tag::TYPED_ARRAY_I64_BIG_ENDIAN => typed_array::<8>(context, value, "signed", |bytes| {
552 i64::from_be_bytes(bytes).separated_string()
553 }),
554 Tag::TYPED_ARRAY_F16_BIG_ENDIAN => typed_array::<2>(context, value, "float", |bytes| {
555 f16::from_be_bytes(bytes).to_f64().separated_string()
556 }),
557 Tag::TYPED_ARRAY_F32_BIG_ENDIAN => typed_array::<4>(context, value, "float", |bytes| {
558 f32::from_be_bytes(bytes).separated_string()
559 }),
560 Tag::TYPED_ARRAY_F64_BIG_ENDIAN => typed_array::<8>(context, value, "float", |bytes| {
561 f64::from_be_bytes(bytes).separated_string()
562 }),
563 Tag::TYPED_ARRAY_F128_BIG_ENDIAN => {
564 typed_array::<16>(context, value, "float", |_| "TODO: f128 unsupported".into())
565 }
566 Tag::TYPED_ARRAY_F16_LITTLE_ENDIAN => typed_array::<2>(context, value, "float", |bytes| {
567 f16::from_le_bytes(bytes).to_f64().separated_string()
568 }),
569 Tag::TYPED_ARRAY_F32_LITTLE_ENDIAN => typed_array::<4>(context, value, "float", |bytes| {
570 f32::from_le_bytes(bytes).separated_string()
571 }),
572 Tag::TYPED_ARRAY_F64_LITTLE_ENDIAN => typed_array::<8>(context, value, "float", |bytes| {
573 f64::from_le_bytes(bytes).separated_string()
574 }),
575 Tag::TYPED_ARRAY_F128_LITTLE_ENDIAN => {
576 typed_array::<16>(context, value, "float", |_| "TODO: f128 unsupported".into())
577 }
578 _ => {
579 vec![Line::from_value(context, value)]
580 }
581 }
582 .into_iter()
583 .chain(extra_lines)
584 .collect();
585
586 let comment = if let Some(extra) = extra {
587 format!("{extra}, tag({tag_value})")
588 } else {
589 format!("tag({tag_value})")
590 };
591
592 Line {
593 hex,
594 comment,
595 sublines,
596 }
597}
598
599fn datetime_epoch(value: &DataItem) -> Line {
600 let date = if let DataItem::TextString(TextString { data, .. }) = value {
601 match DateTime::parse_from_rfc3339(data) {
602 Ok(value) => value,
603 Err(err) => {
604 return Line::new("", format!("error parsing datetime: {err}"));
605 }
606 }
607 } else {
608 return Line::new("", "invalid type for datetime");
609 };
610
611 Line::new("", format!("epoch({})", date.format("%s%.f")))
612}
613
614fn epoch_datetime(value: &DataItem) -> Line {
615 let date = match *value {
616 DataItem::Integer { value, .. } => {
617 if value >= (i64::max_value() as u64) {
618 None
619 } else {
620 NaiveDateTime::from_timestamp_opt(value as i64, 0)
621 }
622 }
623
624 DataItem::Negative { value, .. } => {
625 if value >= (i64::max_value() as u64) {
626 None
627 } else if let Some(value) = (-1i64).checked_sub(value as i64) {
628 NaiveDateTime::from_timestamp_opt(value, 0)
629 } else {
630 None
631 }
632 }
633
634 DataItem::Float { value, .. } => {
635 if value - 1.0 <= (i64::min_value() as f64) || value >= (i64::max_value() as f64) {
636 None
637 } else {
638 let (value, fract) = if value < 0.0 {
639 (value - 1.0, (1.0 + value.fract()) * 1_000_000_000.0)
640 } else {
641 (value, value.fract() * 1_000_000_000.0)
642 };
643 NaiveDateTime::from_timestamp_opt(value as i64, fract as u32)
644 }
645 }
646
647 DataItem::ByteString(..)
648 | DataItem::IndefiniteByteString(..)
649 | DataItem::TextString(..)
650 | DataItem::IndefiniteTextString(..)
651 | DataItem::Array { .. }
652 | DataItem::Map { .. }
653 | DataItem::Tag { .. }
654 | DataItem::Simple(..) => {
655 return Line::new("", "invalid type for epoch datetime");
656 }
657 };
658
659 if let Some(date) = date {
660 Line::new("", format!("datetime({})", date.format("%FT%T%.fZ")))
661 } else {
662 Line::new("", "offset is too large")
663 }
664}
665
666fn date_epoch(value: &DataItem) -> Line {
667 let date = if let DataItem::TextString(TextString { data, .. }) = value {
668 match NaiveDate::parse_from_str(data, "%Y-%m-%d") {
669 Ok(value) => value,
670 Err(err) => {
671 return Line::new("", format!("error parsing date: {err}"));
672 }
673 }
674 } else {
675 return Line::new("", "invalid type for date");
676 };
677
678 Line::new(
679 "",
680 format!(
681 "epoch({})",
682 date.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())
683 .num_days()
684 .separated_string()
685 ),
686 )
687}
688
689fn epoch_date(value: &DataItem) -> Line {
690 let days = match *value {
691 DataItem::Integer { value, .. } => i64::try_from(value).ok(),
692
693 DataItem::Negative { value, .. } => i64::try_from(value)
694 .ok()
695 .and_then(|value| (-1i64).checked_sub(value)),
696
697 _ => {
698 return Line::new("", "invalid type for epoch date");
699 }
700 };
701
702 let date = days
703 .and_then(|days| days.checked_mul(24 * 60 * 60 * 1000))
704 .map(chrono::Duration::milliseconds)
706 .and_then(|duration| {
707 NaiveDate::from_ymd_opt(1970, 1, 1)
708 .unwrap()
709 .checked_add_signed(duration)
710 });
711
712 if let Some(date) = date {
713 Line::new("", format!("date({})", date.format("%F")))
714 } else {
715 Line::new("", "date offset is too large for this tool")
716 }
717}
718
719fn shared_ref(value: &DataItem, known_references: u64) -> Line {
720 match *value {
721 DataItem::Integer { value, .. } => {
722 if value < known_references {
723 Line::new("", format!("reference-to({})", value.separated_string()))
724 } else {
725 Line::new(
726 "",
727 format!(
728 "reference-to({}), not previously shared",
729 value.separated_string()
730 ),
731 )
732 }
733 }
734 _ => Line::new("", "invalid type for shared ref"),
735 }
736}
737
738fn extract_positive_bignum(value: &DataItem) -> Option<BigUint> {
739 if let DataItem::ByteString(ByteString { data, .. }) = value {
740 Some(BigUint::from_bytes_be(data))
741 } else {
742 None
743 }
744}
745
746fn positive_bignum(value: &DataItem) -> Line {
747 extract_positive_bignum(value)
748 .map(|num| Line::new("", format!("bignum({num})")))
749 .unwrap_or_else(|| Line::new("", "invalid type for bignum"))
750}
751
752fn extract_negative_bignum(value: &DataItem) -> Option<BigInt> {
753 if let DataItem::ByteString(ByteString { data, .. }) = value {
754 Some(BigInt::from(-1) - BigInt::from_bytes_be(Sign::Plus, data))
755 } else {
756 None
757 }
758}
759
760fn negative_bignum(value: &DataItem) -> Line {
761 extract_negative_bignum(value)
762 .map(|num| Line::new("", format!("bignum({num})")))
763 .unwrap_or_else(|| Line::new("", "invalid type for bignum"))
764}
765
766fn extract_fraction(value: &DataItem, base: usize) -> Result<BigRational, &'static str> {
767 Ok(match value {
768 DataItem::Array { data, .. } => {
769 if data.len() != 2 {
770 return Err("invalid type");
771 }
772 let (exponent, positive_exponent) = match data[0] {
773 DataItem::Integer { value, .. } => {
774 if value <= usize::max_value() as u64 {
775 (value as usize, true)
776 } else {
777 return Err("exponent is too large");
778 }
779 }
780 DataItem::Negative { value, .. } => {
781 if value < usize::max_value() as u64 {
782 (value as usize + 1, false)
783 } else {
784 return Err("exponent is too large");
785 }
786 }
787 _ => return Err("invalid type"),
788 };
789 let mantissa = match data[1] {
790 DataItem::Integer { value, .. } => BigInt::from(value),
791 DataItem::Negative { value, .. } => BigInt::from(-1) - BigInt::from(value),
792 DataItem::Tag {
793 tag: Tag::POSITIVE_BIGNUM,
794 ref value,
795 ..
796 } => match extract_positive_bignum(value) {
797 Some(value) => BigInt::from_biguint(Sign::Plus, value),
798 _ => return Err("invalid type"),
799 },
800 DataItem::Tag {
801 tag: Tag::NEGATIVE_BIGNUM,
802 ref value,
803 ..
804 } => match extract_negative_bignum(value) {
805 Some(value) => value,
806 _ => return Err("invalid type"),
807 },
808 _ => return Err("invalid type"),
809 };
810 let multiplier = if positive_exponent {
811 Ratio::from_integer(pow(BigInt::from(base), exponent))
812 } else {
813 Ratio::new(BigInt::from(1), pow(BigInt::from(base), exponent))
814 };
815 Ratio::from_integer(mantissa) * multiplier
816 }
817 _ => return Err("invalid type"),
818 })
819}
820
821fn decimal_fraction(value: &DataItem) -> Line {
822 extract_fraction(value, 10)
824 .map(|fraction| Line::new("", format!("decimal fraction({fraction})")))
825 .unwrap_or_else(|err| Line::new("", format!("{err} for decimal fraction")))
826}
827
828fn bigfloat(value: &DataItem) -> Line {
829 extract_fraction(value, 2)
831 .map(|fraction| Line::new("", format!("bigfloat({fraction})")))
832 .unwrap_or_else(|err| Line::new("", format!("{err} for bigfloat")))
833}
834
835fn uri(value: &DataItem) -> Line {
836 if let DataItem::TextString(TextString { data, .. }) = value {
837 Line::new(
838 "",
839 if Url::parse(data).is_ok() {
840 "valid URL (checked against URL Standard, not RFC 3986)"
841 } else {
842 "invalid URL (checked against URL Standard, not RFC 3986)"
843 },
844 )
845 } else {
846 Line::new("", "invalid type for uri")
847 }
848}
849
850fn base64_base(
851 value: &DataItem,
852 encoding: data_encoding::Encoding,
853) -> Result<impl Iterator<Item = Line>, String> {
854 if let DataItem::TextString(TextString { data, .. }) = value {
855 let data = encoding
856 .decode(data.as_bytes())
857 .map_err(|err| format!("{err}"))?;
858 let mut line = Line::new("", "");
859 line.sublines.extend(bytes_to_hex(None, &data));
860 let merged = line.merge();
861 Ok(merged
862 .lines()
863 .skip(1)
864 .map(|line| Line::new("", line.split_at(3).1.replace("# ", "#")))
865 .collect::<Vec<_>>()
866 .into_iter())
867 } else {
868 Err("invalid type".into())
869 }
870}
871
872fn base64url(value: &DataItem) -> Line {
873 base64_base(value, data_encoding::BASE64URL_NOPAD)
874 .map(|lines| {
875 let mut line = Line::new("", "base64url decoded");
876 line.sublines.extend(lines);
877 line
878 })
879 .unwrap_or_else(|err| Line::new("", format!("{err} for base64url")))
880}
881
882fn base64(value: &DataItem) -> Line {
883 base64_base(value, data_encoding::BASE64)
884 .map(|lines| {
885 let mut line = Line::new("", "base64 decoded");
886 line.sublines.extend(lines);
887 line
888 })
889 .unwrap_or_else(|err| Line::new("", format!("{err} for base64")))
890}
891
892fn encoded_cbor(value: &DataItem) -> Line {
893 if let DataItem::ByteString(ByteString { data, .. }) = value {
894 match parse_bytes(data) {
895 Ok(value) => {
896 let mut line = Line::new("", "encoded cbor data item");
897 line.sublines
898 .extend(value.to_hex().lines().map(|line| Line::new("", line)));
899 line
900 }
901 Err(err) => {
902 let mut line = Line::new("", "failed to parse encoded cbor data item");
903 line.sublines.push(Line::new("", format!("{err:?}")));
904 line
905 }
906 }
907 } else {
908 Line::new("", "invalid type for encoded cbor data item")
909 }
910}
911
912fn encoded_cbor_seq(value: &DataItem) -> Vec<Line> {
913 if let DataItem::ByteString(ByteString { data, .. }) = value {
914 let mut data = data.as_slice();
915 let mut lines = Vec::new();
916 while let Ok(Some((item, len))) = crate::parse_bytes_partial(data) {
917 let (_, rest) = data.split_at(len);
918 data = rest;
919 let mut line = Line::new("", "encoded cbor data item");
920 line.sublines
921 .extend(item.to_hex().lines().map(|line| Line::new("", line)));
922 lines.push(line);
923 }
924 if !data.is_empty() {
925 let err = parse_bytes(data).unwrap_err();
926 let mut line = Line::new("", "failed to parse remaining encoded cbor sequence");
927 line.sublines.push(Line::new("", format!("{err:?}")));
928 lines.push(line);
929 }
930 lines
931 } else {
932 vec![Line::new("", "invalid type for encoded cbor sequence")]
933 }
934}
935
936fn uuid(value: &DataItem) -> Line {
937 if let DataItem::ByteString(ByteString { data, .. }) = value {
938 if let Ok(uuid) = Uuid::from_slice(data) {
939 let version = uuid
940 .get_version()
941 .map(|v| format!("{v:?}"))
942 .unwrap_or_else(|| "Unknown".into());
943
944 let variant = format!("{:?}", uuid.get_variant());
945
946 let uuid_base58 = bs58::encode(uuid.as_bytes()).into_string();
947 let uuid_base64 = data_encoding::BASE64_NOPAD.encode(uuid.as_bytes());
948 let version_num = uuid.get_version_num();
949 let mut line = Line::new(
950 "",
951 format!("uuid(variant({variant}), version({version_num}, {version}))"),
952 );
953 line.sublines.extend(vec![
954 Line::new("", format!("base16({uuid})")),
955 Line::new("", format!("base58({uuid_base58})")),
956 Line::new("", format!("base64({uuid_base64})")),
957 ]);
958 line
959 } else {
960 Line::new("", "invalid data length for uuid")
961 }
962 } else {
963 Line::new("", "invalid type for uuid")
964 }
965}
966
967fn network_address(value: &DataItem) -> Line {
968 if let DataItem::ByteString(ByteString { data, .. }) = value {
969 match data.len() {
970 4 => {
971 let addr = Ipv4Addr::from([data[0], data[1], data[2], data[3]]);
972 Line::new("", format!("IPv4 address({addr})"))
973 }
974 6 => {
975 let addr = format!(
976 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
977 data[0], data[1], data[2], data[3], data[4], data[5]
978 );
979 Line::new("", format!("MAC address({addr})"))
980 }
981 16 => {
982 let addr = Ipv6Addr::from([
983 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
984 data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
985 ]);
986 Line::new("", format!("IPv6 address({addr})"))
987 }
988 _ => Line::new("", "invalid data length for network address"),
989 }
990 } else {
991 Line::new("", "invalid type for network address")
992 }
993}
994
995fn ipv4_address_or_prefix(value: &DataItem) -> Line {
996 match value {
997 DataItem::ByteString(ByteString { data, .. }) => {
998 if let Ok(bytes) = <[_; 4]>::try_from(data.as_slice()) {
999 Line::new("", format!("IPv4 address({})", Ipv4Addr::from(bytes)))
1000 } else {
1001 Line::new("", "invalid data length for IPv4 address")
1002 }
1003 }
1004 DataItem::Array { data, .. } => {
1005 match data.get(0) {
1006 Some(DataItem::Integer { value: length, .. }) => {
1007 if let Some(DataItem::ByteString(ByteString { data: prefix, .. })) = data.get(1)
1008 {
1009 if prefix.ends_with(&[0]) {
1010 return Line::new("", "invalid prefix, ends with zero byte");
1011 }
1012 let mut bytes = [0; 4];
1015 bytes[..prefix.len()].copy_from_slice(prefix);
1016 let addr = Ipv4Addr::from(bytes);
1017 Line::new("", format!("IPv4 prefix({addr}/{length})"))
1018 } else {
1019 Line::new("", "invalid type for network address")
1020 }
1021 }
1022 Some(DataItem::ByteString(ByteString { data: address, .. })) => {
1023 let address = if let Ok(address) = <[_; 4]>::try_from(address.as_slice()) {
1024 Ipv4Addr::from(address)
1025 } else {
1026 return Line::new("", "invalid data length for IPv4 address");
1027 };
1028 let length = match data.get(1) {
1029 Some(DataItem::Integer { value, .. }) => Some(value),
1030 Some(DataItem::Simple(Simple::NULL)) => None,
1031 _ => {
1032 return Line::new("", "invalid type for network address");
1033 }
1034 };
1035 let zone = match data.get(2) {
1036 Some(DataItem::Integer { value, .. }) => Some(value.to_string()),
1037 Some(DataItem::TextString(TextString { data, .. })) => Some(data.clone()),
1038 None => None,
1039 _ => {
1040 return Line::new("", "invalid type for network address");
1041 }
1042 };
1043 match (length, zone) {
1044 (Some(length), Some(zone)) => Line::new(
1045 "",
1046 format!("IPv4 address-and-zone-and-prefix({address}%{zone}/{length})"),
1047 ),
1048 (Some(length), None) => {
1049 Line::new("", format!("IPv4 address-and-prefix({address}/{length})"))
1050 }
1051 (None, Some(zone)) => {
1052 Line::new("", format!("IPv4 address-and-zone({address}%{zone})"))
1053 }
1054 (None, None) => Line::new("", "invalid type for network address"),
1055 }
1056 }
1057 _ => Line::new("", "invalid type for network address"),
1058 }
1059 }
1060 _ => Line::new("", "invalid type for network address"),
1061 }
1062}
1063
1064fn ipv6_address_or_prefix(value: &DataItem) -> Line {
1065 match value {
1066 DataItem::ByteString(ByteString { data, .. }) => {
1067 if let Ok(bytes) = <[_; 16]>::try_from(data.as_slice()) {
1068 let addr = Ipv6Addr::from(bytes);
1069 Line::new("", format!("IPv6 address({addr})"))
1070 } else {
1071 Line::new("", "invalid data length for IPv6 address")
1072 }
1073 }
1074 DataItem::Array { data, .. } => {
1075 match data.get(0) {
1076 Some(DataItem::Integer { value: length, .. }) => {
1077 if let Some(DataItem::ByteString(ByteString { data: prefix, .. })) = data.get(1)
1078 {
1079 if prefix.ends_with(&[0]) {
1080 return Line::new("", "invalid prefix, ends with zero byte");
1081 }
1082 let mut bytes = [0; 16];
1085 bytes[..prefix.len()].copy_from_slice(prefix);
1086 let addr = Ipv6Addr::from(bytes);
1087 Line::new("", format!("IPv6 prefix({addr}/{length})"))
1088 } else {
1089 Line::new("", "invalid type for network address")
1090 }
1091 }
1092 Some(DataItem::ByteString(ByteString { data: address, .. })) => {
1093 let address = if let Ok(address) = <[_; 16]>::try_from(address.as_slice()) {
1094 Ipv6Addr::from(address)
1095 } else {
1096 return Line::new("", "invalid data length for IPv6 address");
1097 };
1098 let length = match data.get(1) {
1099 Some(DataItem::Integer { value, .. }) => Some(value),
1100 Some(DataItem::Simple(Simple::NULL)) => None,
1101 _ => {
1102 return Line::new("", "invalid type for network address");
1103 }
1104 };
1105 let zone = match data.get(2) {
1106 Some(DataItem::Integer { value, .. }) => Some(value.to_string()),
1107 Some(DataItem::TextString(TextString { data, .. })) => Some(data.clone()),
1108 None => None,
1109 _ => {
1110 return Line::new("", "invalid type for network address");
1111 }
1112 };
1113 match (length, zone) {
1114 (Some(length), Some(zone)) => Line::new(
1115 "",
1116 format!("IPv6 address-and-zone-and-prefix({address}%{zone}/{length})"),
1117 ),
1118 (Some(length), None) => {
1119 Line::new("", format!("IPv6 address-and-prefix({address}/{length})"))
1120 }
1121 (None, Some(zone)) => {
1122 Line::new("", format!("IPv6 address-and-zone({address}%{zone})"))
1123 }
1124 (None, None) => Line::new("", "invalid type for network address"),
1125 }
1126 }
1127 _ => Line::new("", "invalid type for network address"),
1128 }
1129 }
1130 _ => Line::new("", "invalid type for network address"),
1131 }
1132}
1133
1134fn typed_array<const LEN: usize>(
1135 context: &mut Context,
1136 value: &DataItem,
1137 name: &str,
1138 convert: impl Fn([u8; LEN]) -> String,
1139) -> Vec<Line> {
1140 if let DataItem::ByteString(ByteString { data, bitwidth }) = value {
1141 if data.len() % LEN == 0 {
1142 let mut line = length_to_hex(Some(data.len()), Some(*bitwidth), 2, "bytes");
1143 line.sublines.extend(
1145 data.chunks_exact(LEN)
1146 .map(|chunk| <[_; LEN]>::try_from(chunk).unwrap())
1147 .map(|chunk| {
1148 let value = convert(chunk);
1149 let hex = data_encoding::HEXLOWER.encode(&chunk);
1150 Line::new(hex, format!("{name}({value})"))
1151 }),
1152 );
1153 vec![line]
1154 } else {
1155 vec![
1156 Line::from_value(context, value),
1157 Line::new("", "invalid data length for typed array"),
1158 ]
1159 }
1160 } else {
1161 vec![
1162 Line::from_value(context, value),
1163 Line::new("", "invalid type for typed array"),
1164 ]
1165 }
1166}
1167
1168fn float_to_hex(value: f64, mut bitwidth: FloatWidth) -> Line {
1169 if bitwidth == FloatWidth::Unknown {
1170 bitwidth = FloatWidth::SixtyFour;
1171 }
1172
1173 let hex = match bitwidth {
1174 FloatWidth::Unknown => unreachable!(),
1175 FloatWidth::Sixteen => format!("f9 {:04x}", f16::from_f64(value).to_bits()),
1176 FloatWidth::ThirtyTwo => format!("fa {:08x}", (value as f32).to_bits()),
1177 FloatWidth::SixtyFour => format!("fb {:016x}", value.to_bits()),
1178 };
1179
1180 let comment = format!(
1181 "float({})",
1182 if value.is_nan() {
1183 "NaN".to_owned()
1184 } else if value.is_infinite() {
1185 if value.is_sign_negative() {
1186 "-Infinity".to_owned()
1187 } else {
1188 "Infinity".to_owned()
1189 }
1190 } else {
1191 value.separated_string()
1192 }
1193 );
1194
1195 Line::new(hex, comment)
1196}
1197
1198fn simple_to_hex(simple: Simple) -> Line {
1199 let Simple(value) = simple;
1200
1201 let hex = if value < 24 {
1202 format!("{:02x}", 0b1110_0000 | value)
1203 } else {
1204 format!("f8 {value:02x}")
1205 };
1206
1207 let extra = match simple {
1208 Simple::FALSE => "false, ",
1209 Simple::TRUE => "true, ",
1210 Simple::NULL => "null, ",
1211 Simple::UNDEFINED => "undefined, ",
1212 Simple(24..=32) => "reserved, ",
1213 _ => "unassigned, ",
1214 };
1215
1216 let comment = format!("{extra}simple({value})");
1217
1218 Line::new(hex, comment)
1219}
1220
1221impl DataItem {
1222 pub fn to_hex(&self) -> String {
1223 let mut context = Context {
1224 encoding: None,
1225 reference_count: 0,
1226 };
1227 Line::from_value(&mut context, self).merge()
1228 }
1229}