1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{
4 fmt,
5 ops::{Bound, RangeBounds},
6};
7
8pub 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
15impl 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
29impl 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
43pub struct Dump<'a> {
44 pub(crate) data: &'a [u8],
45 pub(crate) start: Bound<usize>,
46 pub(crate) end: Bound<usize>,
47}
48
49impl 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
151impl fmt::Display for Dump<'_> {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 self.fmt(f, debug:false)
154 }
155}
156
157impl fmt::Debug for Dump<'_> {
158 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159 self.fmt(f, debug:true)
160 }
161}
162