cbor_diag/encode/
hex.rs

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    // TODO: Rearrange the data to remove the unwraps.
198
199    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        // This is the only non-panicking constructor for `chrono::Duration`
705        .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    // TODO: https://github.com/rust-num/num-rational/issues/10
823    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    // TODO: https://github.com/rust-num/num-rational/issues/10
830    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                        // TODO: check that the defined prefix has all zero bits after the given length
1013                        // https://www.rfc-editor.org/rfc/rfc9164.html#section-4.3
1014                        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                        // TODO: check that the defined prefix has all zero bits after the given length
1083                        // https://www.rfc-editor.org/rfc/rfc9164.html#section-4.3
1084                        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            // TODO: Use slice::array_chunks when stable
1144            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}