kex/
lib.rs

1//! `kex` - library for streamed hex dumping.
2//! Fully static for performance purpose 
3
4use std::io::*;
5
6pub mod config;
7pub use config::*;
8
9pub mod format;
10pub use format::*;
11
12mod streamer;
13use streamer::*;
14
15const OUTPUT_LOST_MESSAGE: &str = "Somewhere we lost the output";
16
17/// The topmost struct for data output
18pub struct Printer<
19    O: Write,
20    A: AddressFormatting + Clone,
21    B: ByteFormatting + Clone,
22    C: CharFormatting + Clone,
23> {
24    /// Where to print data
25    out: Option<O>,
26
27    streamer: Streamer<A, B, C>,
28
29    is_finished: bool,
30}
31
32impl<
33        O: Write,
34        A: AddressFormatting + Clone,
35        B: ByteFormatting + Clone,
36        C: CharFormatting + Clone,
37    > Printer<O, A, B, C>
38{
39    /// Customized constructor.
40    ///
41    /// All constructors of the [`Printer`] moves given output. To give it back use `finish(mut self)` function
42    ///
43    /// `out` - place to ouput string.
44    ///
45    /// `start_address` - start address to print.
46    ///
47    /// `config` - formatting configuration.
48    ///
49    /// `Printer` does no assumptions on `start_address` where to start reading data,
50    /// it just recieving data chunks in `push(...)` function, then increments the `start_address`
51    pub fn new(out: O, start_address: usize, config: Config<A, B, C>) -> Printer<O, A, B, C> {
52        Printer {
53            out: Some(out),
54            streamer: Streamer::new(
55                config.addr,
56                config.byte,
57                config.text,
58                start_address,
59                config.dedup_enabled,
60            ),
61            is_finished: false,
62        }
63    }
64
65    /// Finalize manually. Prints last unfinished line with paddings and turns back given output
66    pub fn finish(mut self) -> O {
67        _ = self.print_last_line();
68        self.is_finished = true;
69        self.out.take().unwrap()
70    }
71}
72
73impl<
74        O: Write,
75        A: AddressFormatting + Clone,
76        B: ByteFormatting + Clone,
77        C: CharFormatting + Clone,
78    > Printer<O, A, B, C>
79{
80    /// Accepts bytes chunk. Immediately prints `first` and `second` columns to `out`,
81    /// `third` will printed after `second` column is completely filled, or after finalization.
82    pub fn push(&mut self, bytes: &[u8]) -> Result<usize> {
83        let mut out = self.out.take().expect(OUTPUT_LOST_MESSAGE);
84
85        self.streamer.push(bytes, &mut out)?;
86
87        self.out = Some(out);
88
89        Ok(bytes.len())
90    }
91
92    fn print_last_line(&mut self) -> Result<()> {
93        if self.is_finished {
94            return Ok(());
95        }
96
97        let mut out = self.out.take().expect(OUTPUT_LOST_MESSAGE);
98
99        let result = self.streamer.write_tail(&mut out);
100
101        self.out = Some(out);
102
103        result
104    }
105}
106
107impl<
108        O: Write,
109        A: AddressFormatting + Clone + Default,
110        B: ByteFormatting + Clone + Default,
111        C: CharFormatting + Clone + Default,
112    > Printer<O, A, B, C>
113{
114    pub fn default_with(out: O, start_address: usize) -> Printer<O, A, B, C> {
115        Self::new(out, start_address, Config::<A, B, C>::default())
116    }
117}
118
119impl<O: Write> Printer<O, AddressFormatter, ByteFormatter, CharFormatter> {
120    pub fn default_fmt_with(
121        out: O,
122        start_address: usize,
123    ) -> Printer<O, AddressFormatter, ByteFormatter, CharFormatter> {
124        Self::new(
125            out,
126            start_address,
127            Config::<AddressFormatter, ByteFormatter, CharFormatter>::default(),
128        )
129    }
130}
131
132impl<
133        O: Write,
134        A: AddressFormatting + Clone,
135        B: ByteFormatting + Clone,
136        C: CharFormatting + Clone,
137    > Write for Printer<O, A, B, C>
138{
139    fn write(&mut self, buf: &[u8]) -> Result<usize> {
140        self.push(buf)
141    }
142
143    /// Does nothing. Always returns `Ok(())`
144    fn flush(&mut self) -> Result<()> {
145        Ok(())
146    }
147}
148
149impl<
150        O: Write,
151        A: AddressFormatting + Clone,
152        B: ByteFormatting + Clone,
153        C: CharFormatting + Clone,
154    > Drop for Printer<O, A, B, C>
155{
156    fn drop(&mut self) {
157        _ = self.print_last_line();
158    }
159}
160
161#[cfg(test)]
162mod test {
163    use super::*;
164
165    #[test]
166    fn simple() {
167        let result = vec![];
168        let mut printer = Printer::default_fmt_with(result, 0);
169
170        let bytes1 = &[222u8, 173, 190, 239];
171        let bytes2 = &[0xfeu8, 0xed, 0xfa];
172        let title = b"Simple printing";
173
174        for _ in 0..10 {
175            _ = printer.push(bytes1);
176        }
177
178        _ = printer.push(title);
179
180        for _ in 0..11 {
181            _ = printer.push(bytes2);
182        }
183
184        let result = printer.finish();
185        let result_str = String::from_utf8(result).expect("Invalid characters in result");
186
187        let expected = "00000000 deadbeef deadbeef deadbeef deadbeef |................|
188*
18900000020 deadbeef deadbeef 53696d70 6c652070 |........Simple p|
19000000030 72696e74 696e67fe edfafeed fafeedfa |rinting.........|
19100000040 feedfafe edfafeed fafeedfa feedfafe |................|
19200000050 edfafeed fafeedfa ........ ........ |........        |
19300000058 \n";
194
195        assert_eq!(result_str, expected);
196    }
197
198    #[test]
199    fn stable_reading() {
200        println!("Testing reading stability");
201        stable_reading_with("testable/lorem_ipsum");
202        stable_reading_with("testable/duplications");
203    }
204    
205    fn stable_reading_with(path: &str) {
206        println!("Path: {path}");
207
208        let patterns = vec![
209            vec![1],
210            vec![1, 2, 1],
211            vec![5],
212            vec![4],
213            vec![4, 7],
214            vec![4, 1],
215            vec![18, 1, 16, 7, 4, 5, 3],
216            vec![2000],
217            vec![444],
218        ];
219        let test_data =
220            std::fs::read(path).expect("Could not opent testable data");
221    
222        let mut last_result: Option<String> = None;
223    
224        for pat in patterns {
225            let result = string_with(&test_data, pat);
226            if let Some(last_result) = last_result {
227                assert_eq!(result, last_result);
228            }
229    
230            last_result = Some(result);
231        }
232    }
233
234    fn string_with(bytes: &[u8], read_len_pattern: Vec<usize>) -> String {
235        use std::cmp::min;
236
237        let result = vec![];
238        let mut printer = Printer::default_fmt_with(result, 0);
239
240        let mut tmp = bytes;
241
242        let mut pat_idx = 0;
243        while tmp.len() != 0 {
244            let to_read = min(read_len_pattern[pat_idx], tmp.len());
245
246            printer
247                .write(&tmp[..to_read])
248                .expect("Writing to printer error");
249
250            tmp = &tmp[to_read..];
251
252            pat_idx += 1;
253            pat_idx %= read_len_pattern.len();
254        }
255
256        let result = printer.finish();
257        String::from_utf8(result).expect("Invalid characters in result")
258    }
259
260    #[test]
261    fn duplications() {
262        let result = string_with_file("testable/duplications");
263        let expected = "00000000 61626364 65666768 696a6b6c 6d6e6f70 |abcdefghijklmnop|
26400000010 61626364 65666768 69316b6c 6d6e6f70 |abcdefghi1klmnop|
26500000020 61626364 65666768 696a6b6c 6d6e6f70 |abcdefghijklmnop|
266*
26700000040 61626364 65666768 696a6b6c 6d6e6f67 |abcdefghijklmnog|
26800000050 61626364 65666768 6935366c 6d6e6f70 |abcdefghi56lmnop|
26900000060 61626364 65666768 696a6b6c 6d6e6f70 |abcdefghijklmnop|
270*
271000000e0 \n";
272
273        assert_eq!(result, expected);
274    }
275
276    fn string_with_file(path: &str) -> String {
277        let test_data =
278            std::fs::read(path).expect("Could not opent testable data");
279        string_with(&test_data, vec![100])
280    }
281}