gstreamer/
slice.rs

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