1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::{ |
4 | fmt, |
5 | ops::{Bound, RangeBounds}, |
6 | }; |
7 | |
8 | pub trait ByteSliceExt { |
9 | #[doc (alias = "gst_util_dump_mem" )] |
10 | fn dump(&self) -> Dump; |
11 | #[doc (alias = "gst_util_dump_mem" )] |
12 | fn dump_range(&self, range: impl RangeBounds<usize>) -> Dump; |
13 | } |
14 | |
15 | impl ByteSliceExt for &[u8] { |
16 | fn dump(&self) -> Dump { |
17 | self.dump_range(..) |
18 | } |
19 | |
20 | fn dump_range(&self, range: impl RangeBounds<usize>) -> Dump { |
21 | Dump { |
22 | data: self, |
23 | start: range.start_bound().cloned(), |
24 | end: range.end_bound().cloned(), |
25 | } |
26 | } |
27 | } |
28 | |
29 | impl ByteSliceExt for &mut [u8] { |
30 | fn dump(&self) -> Dump { |
31 | self.dump_range(..) |
32 | } |
33 | |
34 | fn dump_range(&self, range: impl RangeBounds<usize>) -> Dump { |
35 | Dump { |
36 | data: self, |
37 | start: range.start_bound().cloned(), |
38 | end: range.end_bound().cloned(), |
39 | } |
40 | } |
41 | } |
42 | |
43 | pub struct Dump<'a> { |
44 | pub(crate) data: &'a [u8], |
45 | pub(crate) start: Bound<usize>, |
46 | pub(crate) end: Bound<usize>, |
47 | } |
48 | |
49 | impl Dump<'_> { |
50 | fn fmt(&self, f: &mut fmt::Formatter, debug: bool) -> fmt::Result { |
51 | use std::fmt::Write; |
52 | |
53 | let data = self.data; |
54 | let len = data.len(); |
55 | |
56 | // Kind of re-implementation of slice indexing to allow handling out of range values better |
57 | // with specific output strings |
58 | let mut start_idx = match self.start { |
59 | Bound::Included(idx) if idx >= len => { |
60 | write!(f, "<start out of range>" )?; |
61 | return Ok(()); |
62 | } |
63 | Bound::Excluded(idx) if idx.checked_add(1).map_or(true, |idx| idx >= len) => { |
64 | write!(f, "<start out of range>" )?; |
65 | return Ok(()); |
66 | } |
67 | Bound::Included(idx) => idx, |
68 | Bound::Excluded(idx) => idx + 1, |
69 | Bound::Unbounded => 0, |
70 | }; |
71 | |
72 | let end_idx = match self.end { |
73 | Bound::Included(idx) if idx.checked_add(1).map_or(true, |idx| idx > len) => { |
74 | write!(f, "<end out of range>" )?; |
75 | return Ok(()); |
76 | } |
77 | Bound::Excluded(idx) if idx > len => { |
78 | write!(f, "<end out of range>" )?; |
79 | return Ok(()); |
80 | } |
81 | Bound::Included(idx) => idx + 1, |
82 | Bound::Excluded(idx) => idx, |
83 | Bound::Unbounded => len, |
84 | }; |
85 | |
86 | if start_idx >= end_idx { |
87 | write!(f, "<empty range>" )?; |
88 | return Ok(()); |
89 | } |
90 | |
91 | let data = &data[start_idx..end_idx]; |
92 | |
93 | if debug { |
94 | for line in data.chunks(16) { |
95 | match end_idx { |
96 | 0x00_00..=0xff_ff => write!(f, " {:04x}: " , start_idx)?, |
97 | 0x01_00_00..=0xff_ff_ff => write!(f, " {:06x}: " , start_idx)?, |
98 | 0x01_00_00_00..=0xff_ff_ff_ff => write!(f, " {:08x}: " , start_idx)?, |
99 | _ => write!(f, " {:016x}: " , start_idx)?, |
100 | } |
101 | |
102 | for (i, v) in line.iter().enumerate() { |
103 | if i > 0 { |
104 | write!(f, " {:02x}" , v)?; |
105 | } else { |
106 | write!(f, " {:02x}" , v)?; |
107 | } |
108 | } |
109 | |
110 | for _ in line.len()..16 { |
111 | write!(f, " " )?; |
112 | } |
113 | write!(f, " " )?; |
114 | |
115 | for v in line { |
116 | if v.is_ascii() && !v.is_ascii_control() { |
117 | f.write_char((*v).into())?; |
118 | } else { |
119 | f.write_char('.' )?; |
120 | } |
121 | } |
122 | |
123 | start_idx = start_idx.saturating_add(16); |
124 | if start_idx < end_idx { |
125 | writeln!(f)?; |
126 | } |
127 | } |
128 | |
129 | Ok(()) |
130 | } else { |
131 | for line in data.chunks(16) { |
132 | for (i, v) in line.iter().enumerate() { |
133 | if i > 0 { |
134 | write!(f, " {:02x}" , v)?; |
135 | } else { |
136 | write!(f, " {:02x}" , v)?; |
137 | } |
138 | } |
139 | |
140 | start_idx = start_idx.saturating_add(16); |
141 | if start_idx < end_idx { |
142 | writeln!(f)?; |
143 | } |
144 | } |
145 | |
146 | Ok(()) |
147 | } |
148 | } |
149 | } |
150 | |
151 | impl fmt::Display for Dump<'_> { |
152 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
153 | self.fmt(f, debug:false) |
154 | } |
155 | } |
156 | |
157 | impl fmt::Debug for Dump<'_> { |
158 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
159 | self.fmt(f, debug:true) |
160 | } |
161 | } |
162 | |