dcbor/
dump.rs

1import_stdlib!();
2
3use super::{
4    string_util::{flanked, sanitized},
5    varint::{EncodeVarInt, MajorType},
6};
7use crate::{
8    CBOR, CBORCase, TagsStoreOpt, tags_store::TagsStoreTrait, with_tags,
9};
10
11#[derive(Default)]
12pub struct HexFormatOpts<'a> {
13    annotate: bool,
14    tags: TagsStoreOpt<'a>,
15}
16
17impl<'a> HexFormatOpts<'a> {
18    /// Sets whether to annotate the hex dump with tags.
19    pub fn annotate(mut self, annotate: bool) -> Self {
20        self.annotate = annotate;
21        self
22    }
23
24    /// Sets the formatting context for the hex dump.
25    pub fn context(mut self, tags: TagsStoreOpt<'a>) -> Self {
26        self.tags = tags;
27        self
28    }
29}
30
31/// Affordances for viewing the encoded binary representation of CBOR as
32/// hexadecimal.
33impl CBOR {
34    /// Returns the encoded hexadecimal representation of this CBOR.
35    pub fn hex(&self) -> String { hex::encode(self.to_cbor_data()) }
36
37    /// Returns the encoded hexadecimal representation of this CBOR.
38    ///
39    /// Optionally annotates the output, e.g. breaking the output up into
40    /// semantically meaningful lines, formatting dates, and adding names of
41    /// known tags.
42    pub fn hex_opt(&self, opts: &HexFormatOpts<'_>) -> String {
43        if !opts.annotate {
44            return self.hex();
45        }
46        let items = self.dump_items(0, opts);
47        let note_column = items.iter().fold(0, |largest, item| {
48            largest.max(item.format_first_column().len())
49        });
50        // Round up to nearest multiple of 4
51        let note_column = ((note_column + 4) & !3) - 1;
52        let lines: Vec<_> =
53            items.iter().map(|x| x.format(note_column)).collect();
54        lines.join("\n")
55    }
56
57    /// Returns the encoded hexadecimal representation of this CBOR, with
58    /// annotations.
59    pub fn hex_annotated(&self) -> String {
60        self.hex_opt(&HexFormatOpts::default().annotate(true))
61    }
62
63    fn dump_items(
64        &self,
65        level: usize,
66        opts: &HexFormatOpts<'_>,
67    ) -> Vec<DumpItem> {
68        match self.as_case() {
69            CBORCase::Unsigned(n) => vec![DumpItem::new(
70                level,
71                vec![self.to_cbor_data()],
72                Some(format!("unsigned({})", n)),
73            )],
74            CBORCase::Negative(n) => vec![DumpItem::new(
75                level,
76                vec![self.to_cbor_data()],
77                Some(format!("negative({})", -1 - (*n as i128))),
78            )],
79            CBORCase::ByteString(d) => {
80                let mut items = vec![DumpItem::new(
81                    level,
82                    vec![d.len().encode_varint(MajorType::ByteString)],
83                    Some(format!("bytes({})", d.len())),
84                )];
85                if !d.is_empty() {
86                    let mut note: Option<String> = None;
87                    if let Ok(a) = str::from_utf8(d)
88                        && let Some(b) = sanitized(a)
89                    {
90                        note = Some(flanked(&b, "\"", "\""));
91                    }
92                    items.push(DumpItem::new(
93                        level + 1,
94                        vec![d.to_vec()],
95                        note,
96                    ));
97                }
98                items
99            }
100            CBORCase::Text(s) => {
101                let header = s.len().encode_varint(MajorType::Text);
102                let header_data = vec![vec![header[0]], header[1..].to_vec()];
103                let utf8_data = s.as_bytes().to_vec();
104                vec![
105                    DumpItem::new(
106                        level,
107                        header_data,
108                        Some(format!("text({})", utf8_data.len())),
109                    ),
110                    DumpItem::new(
111                        level + 1,
112                        vec![utf8_data],
113                        Some(flanked(s, "\"", "\"")),
114                    ),
115                ]
116            }
117            CBORCase::Simple(v) => {
118                let data = v.cbor_data();
119                let note = format!("{}", v);
120                vec![DumpItem::new(level, vec![data], Some(note))]
121            }
122            CBORCase::Tagged(tag, item) => {
123                let header = tag.value().encode_varint(MajorType::Tagged);
124                let header_data = vec![vec![header[0]], header[1..].to_vec()];
125                let mut note_components: Vec<String> =
126                    vec![format!("tag({})", tag.value())];
127                match opts.tags {
128                    TagsStoreOpt::None => {}
129                    TagsStoreOpt::Global => {
130                        with_tags!(|tags_store: &dyn TagsStoreTrait| {
131                            if let Some(name) =
132                                tags_store.assigned_name_for_tag(tag)
133                            {
134                                note_components.push(name);
135                            }
136                        })
137                    }
138                    TagsStoreOpt::Custom(tags_store_trait) => {
139                        if let Some(name) =
140                            tags_store_trait.assigned_name_for_tag(tag)
141                        {
142                            note_components.push(name);
143                        }
144                    }
145                }
146                let tag_note = note_components.join(" ");
147                vec![
148                    vec![DumpItem::new(level, header_data, Some(tag_note))],
149                    item.dump_items(level + 1, opts),
150                ]
151                .into_iter()
152                .flatten()
153                .collect()
154            }
155            CBORCase::Array(array) => {
156                let header = array.len().encode_varint(MajorType::Array);
157                let header_data = vec![vec![header[0]], header[1..].to_vec()];
158                vec![
159                    vec![DumpItem::new(
160                        level,
161                        header_data,
162                        Some(format!("array({})", array.len())),
163                    )],
164                    array
165                        .iter()
166                        .flat_map(|x| x.dump_items(level + 1, opts))
167                        .collect(),
168                ]
169                .into_iter()
170                .flatten()
171                .collect()
172            }
173            CBORCase::Map(m) => {
174                let header = m.len().encode_varint(MajorType::Map);
175                let header_data = vec![vec![header[0]], header[1..].to_vec()];
176                vec![
177                    vec![DumpItem::new(
178                        level,
179                        header_data,
180                        Some(format!("map({})", m.len())),
181                    )],
182                    m.iter()
183                        .flat_map(|x| {
184                            vec![
185                                x.0.dump_items(level + 1, opts),
186                                x.1.dump_items(level + 1, opts),
187                            ]
188                            .into_iter()
189                            .flatten()
190                            .collect::<Vec<DumpItem>>()
191                        })
192                        .collect(),
193                ]
194                .into_iter()
195                .flatten()
196                .collect()
197            }
198        }
199    }
200}
201
202#[derive(Debug)]
203struct DumpItem {
204    level: usize,
205    data: Vec<Vec<u8>>,
206    note: Option<String>,
207}
208
209impl DumpItem {
210    fn new(level: usize, data: Vec<Vec<u8>>, note: Option<String>) -> DumpItem {
211        DumpItem { level, data, note }
212    }
213
214    fn format(&self, note_column: usize) -> String {
215        let column_1 = self.format_first_column();
216        let (column_2, padding) = {
217            if let Some(note) = &self.note {
218                let padding_count = 1.max(
219                    39.min(note_column as i64) - (column_1.len() as i64) + 1,
220                );
221                let padding = " ".repeat(padding_count.try_into().unwrap());
222                let column_2 = format!("# {}", note);
223                (column_2, padding)
224            } else {
225                ("".to_string(), "".to_string())
226            }
227        };
228        column_1 + &padding + &column_2
229    }
230
231    fn format_first_column(&self) -> String {
232        let indent = " ".repeat(self.level * 4);
233        let hex: Vec<_> = self
234            .data
235            .iter()
236            .map(hex::encode)
237            .filter(|x| !x.is_empty())
238            .collect();
239        let hex = hex.join(" ");
240        indent + &hex
241    }
242}