1 //! Utilities for formatting, printing, and user communication.
2
3 use crate::Error;
4 use crate::Result;
5
6 /// Converts buffers to and from hexadecimal numbers.
7 pub mod hex {
8 use std::io;
9
10 use crate::Result;
11
12 /// Encodes the given buffer as hexadecimal number.
encode<B: AsRef<[u8]>>(buffer: B) -> String13 pub fn encode<B: AsRef<[u8]>>(buffer: B) -> String {
14 super::to_hex(buffer.as_ref(), false)
15 }
16
17 /// Encodes the given buffer as hexadecimal number with spaces.
encode_pretty<B: AsRef<[u8]>>(buffer: B) -> String18 pub fn encode_pretty<B: AsRef<[u8]>>(buffer: B) -> String {
19 super::to_hex(buffer.as_ref(), true)
20 }
21
22 /// Decodes the given hexadecimal number.
decode<H: AsRef<str>>(hex: H) -> Result<Vec<u8>>23 pub fn decode<H: AsRef<str>>(hex: H) -> Result<Vec<u8>> {
24 super::from_hex(hex.as_ref(), false)
25 }
26
27 /// Decodes the given hexadecimal number, ignoring whitespace.
decode_pretty<H: AsRef<str>>(hex: H) -> Result<Vec<u8>>28 pub fn decode_pretty<H: AsRef<str>>(hex: H) -> Result<Vec<u8>> {
29 super::from_hex(hex.as_ref(), true)
30 }
31
32 /// Dumps binary data, like `hd(1)`.
dump<W: io::Write, B: AsRef<[u8]>>(sink: W, data: B) -> io::Result<()>33 pub fn dump<W: io::Write, B: AsRef<[u8]>>(sink: W, data: B)
34 -> io::Result<()> {
35 Dumper::new(sink, "").write_ascii(data)
36 }
37
38 /// Writes annotated hex dumps, like hd(1).
39 ///
40 /// # Example
41 ///
42 /// ```rust
43 /// use sequoia_openpgp::fmt::hex;
44 ///
45 /// let mut dumper = hex::Dumper::new(Vec::new(), "");
46 /// dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap();
47 /// dumper.write(&[0x04], "version").unwrap();
48 /// dumper.write(&[0x00], "type").unwrap();
49 ///
50 /// let buf = dumper.into_inner();
51 /// assert_eq!(
52 /// ::std::str::from_utf8(&buf[..]).unwrap(),
53 /// "00000000 89 01 33 frame\n\
54 /// 00000003 04 version\n\
55 /// 00000004 00 type\n\
56 /// ");
57 /// ```
58 pub struct Dumper<W: io::Write> {
59 inner: W,
60 indent: String,
61 offset: usize,
62 }
63
64 impl<W: io::Write> Dumper<W> {
65 /// Creates a new dumper.
66 ///
67 /// The dump is written to `inner`. Every line is indented with
68 /// `indent`.
new<I: AsRef<str>>(inner: W, indent: I) -> Self69 pub fn new<I: AsRef<str>>(inner: W, indent: I) -> Self {
70 Dumper {
71 inner,
72 indent: indent.as_ref().into(),
73 offset: 0,
74 }
75 }
76
77 /// Returns the inner writer.
into_inner(self) -> W78 pub fn into_inner(self) -> W {
79 self.inner
80 }
81
82 /// Writes a chunk of data.
83 ///
84 /// The `msg` is printed at the end of the first line.
write<B, M>(&mut self, buf: B, msg: M) -> io::Result<()> where B: AsRef<[u8]>, M: AsRef<str>,85 pub fn write<B, M>(&mut self, buf: B, msg: M) -> io::Result<()>
86 where B: AsRef<[u8]>,
87 M: AsRef<str>,
88 {
89 let mut first = true;
90 self.write_labeled(buf.as_ref(), move |_, _| {
91 if first {
92 first = false;
93 Some(msg.as_ref().into())
94 } else {
95 None
96 }
97 })
98 }
99
100 /// Writes a chunk of data with ASCII-representation.
101 ///
102 /// This produces output similar to `hd(1)`.
write_ascii<B>(&mut self, buf: B) -> io::Result<()> where B: AsRef<[u8]>,103 pub fn write_ascii<B>(&mut self, buf: B) -> io::Result<()>
104 where B: AsRef<[u8]>,
105 {
106 self.write_labeled(buf, |offset, data| {
107 let mut l = String::new();
108 for _ in 0..offset {
109 l.push(' ');
110 }
111 for &c in data {
112 l.push(if c < 32 {
113 '.'
114 } else if c < 128 {
115 c.into()
116 } else {
117 '.'
118 })
119 }
120 Some(l)
121 })
122 }
123
124 /// Writes a chunk of data.
125 ///
126 /// For each line, the given function is called to compute a
127 /// label that printed at the end of the first line. The
128 /// functions first argument is the offset in the current line
129 /// (0..16), the second the slice of the displayed data.
write_labeled<B, L>(&mut self, buf: B, mut labeler: L) -> io::Result<()> where B: AsRef<[u8]>, L: FnMut(usize, &[u8]) -> Option<String>,130 pub fn write_labeled<B, L>(&mut self, buf: B, mut labeler: L)
131 -> io::Result<()>
132 where B: AsRef<[u8]>,
133 L: FnMut(usize, &[u8]) -> Option<String>,
134 {
135 let buf = buf.as_ref();
136 let mut first_label_offset = self.offset % 16;
137
138 write!(self.inner, "{}{:08x} ", self.indent, self.offset)?;
139 for i in 0 .. self.offset % 16 {
140 if i != 7 {
141 write!(self.inner, " ")?;
142 } else {
143 write!(self.inner, " ")?;
144 }
145 }
146
147 let mut offset_printed = true;
148 let mut data_start = 0;
149 for (i, c) in buf.iter().enumerate() {
150 if ! offset_printed {
151 write!(self.inner,
152 "\n{}{:08x} ", self.indent, self.offset)?;
153 offset_printed = true;
154 }
155
156 write!(self.inner, " {:02x}", c)?;
157 self.offset += 1;
158 match self.offset % 16 {
159 0 => {
160 if let Some(msg) = labeler(
161 first_label_offset, &buf[data_start..i + 1])
162 {
163 write!(self.inner, " {}", msg)?;
164 // Only the first label is offset.
165 first_label_offset = 0;
166 }
167 data_start = i + 1;
168 offset_printed = false;
169 },
170 8 => write!(self.inner, " ")?,
171 _ => (),
172 }
173 }
174
175 if let Some(msg) = labeler(
176 first_label_offset, &buf[data_start..])
177 {
178 for i in self.offset % 16 .. 16 {
179 if i != 7 {
180 write!(self.inner, " ")?;
181 } else {
182 write!(self.inner, " ")?;
183 }
184 }
185
186 write!(self.inner, " {}", msg)?;
187 }
188 writeln!(self.inner)?;
189 Ok(())
190 }
191 }
192 }
193
194 /// A helpful debugging function.
195 #[allow(dead_code)]
to_hex(s: &[u8], pretty: bool) -> String196 pub(crate) fn to_hex(s: &[u8], pretty: bool) -> String {
197 use std::fmt::Write;
198
199 let mut result = String::new();
200 for (i, b) in s.iter().enumerate() {
201 // Add spaces every four digits to make the output more
202 // readable.
203 if pretty && i > 0 && i % 2 == 0 {
204 write!(&mut result, " ").unwrap();
205 }
206 write!(&mut result, "{:02X}", b).unwrap();
207 }
208 result
209 }
210
211 /// A helpful function for converting a hexadecimal string to binary.
212 /// This function skips whitespace if `pretty` is set.
from_hex(hex: &str, pretty: bool) -> Result<Vec<u8>>213 pub(crate) fn from_hex(hex: &str, pretty: bool) -> Result<Vec<u8>> {
214 const BAD: u8 = 255u8;
215 const X: u8 = 'x' as u8;
216
217 let mut nibbles = hex.chars().filter_map(|x| {
218 match x {
219 '0' => Some(0u8),
220 '1' => Some(1u8),
221 '2' => Some(2u8),
222 '3' => Some(3u8),
223 '4' => Some(4u8),
224 '5' => Some(5u8),
225 '6' => Some(6u8),
226 '7' => Some(7u8),
227 '8' => Some(8u8),
228 '9' => Some(9u8),
229 'a' | 'A' => Some(10u8),
230 'b' | 'B' => Some(11u8),
231 'c' | 'C' => Some(12u8),
232 'd' | 'D' => Some(13u8),
233 'e' | 'E' => Some(14u8),
234 'f' | 'F' => Some(15u8),
235 'x' | 'X' if pretty => Some(X),
236 _ if pretty && x.is_whitespace() => None,
237 _ => Some(BAD),
238 }
239 }).collect::<Vec<u8>>();
240
241 if pretty && nibbles.len() >= 2 && nibbles[0] == 0 && nibbles[1] == X {
242 // Drop '0x' prefix.
243 nibbles.remove(0);
244 nibbles.remove(0);
245 }
246
247 if nibbles.iter().any(|&b| b == BAD || b == X) {
248 // Not a hex character.
249 return
250 Err(Error::InvalidArgument("Invalid characters".into()).into());
251 }
252
253 // We need an even number of nibbles.
254 if nibbles.len() % 2 != 0 {
255 return
256 Err(Error::InvalidArgument("Odd number of nibbles".into()).into());
257 }
258
259 let bytes = nibbles.chunks(2).map(|nibbles| {
260 (nibbles[0] << 4) | nibbles[1]
261 }).collect::<Vec<u8>>();
262
263 Ok(bytes)
264 }
265
266 /// Formats the given time using ISO 8601.
267 ///
268 /// This is a no-dependency, best-effort mechanism. If the given time
269 /// is not representable using unsigned UNIX time, we return the debug
270 /// formatting.
time(t: &std::time::SystemTime) -> String271 pub(crate) fn time(t: &std::time::SystemTime) -> String {
272 extern "C" {
273 fn strftime(
274 s: *mut libc::c_char,
275 max: libc::size_t,
276 format: *const libc::c_char,
277 tm: *const libc::tm,
278 ) -> usize;
279 }
280
281 let t = match t.duration_since(std::time::UNIX_EPOCH) {
282 Ok(t) => t.as_secs() as libc::time_t,
283 Err(_) => return format!("{:?}", t),
284 };
285 let fmt = b"%Y-%m-%dT%H:%M:%SZ\x00";
286 assert_eq!(b"2020-03-26T10:08:10Z\x00".len(), 21);
287 let mut s = [0u8; 21];
288
289 unsafe {
290 let mut tm: libc::tm = std::mem::zeroed();
291
292 #[cfg(unix)]
293 libc::gmtime_r(&t, &mut tm);
294 #[cfg(windows)]
295 libc::gmtime_s(&mut tm, &t);
296
297 strftime(s.as_mut_ptr() as *mut libc::c_char,
298 s.len(),
299 fmt.as_ptr() as *const libc::c_char,
300 &tm);
301 }
302
303 std::ffi::CStr::from_bytes_with_nul(&s)
304 .expect("strftime nul terminates string")
305 .to_string_lossy().into()
306 }
307
308 #[cfg(test)]
309 mod test {
310 #[test]
from_hex()311 fn from_hex() {
312 use super::from_hex as fh;
313 assert_eq!(fh("", false).ok(), Some(vec![]));
314 assert_eq!(fh("0", false).ok(), None);
315 assert_eq!(fh("00", false).ok(), Some(vec![0x00]));
316 assert_eq!(fh("09", false).ok(), Some(vec![0x09]));
317 assert_eq!(fh("0f", false).ok(), Some(vec![0x0f]));
318 assert_eq!(fh("99", false).ok(), Some(vec![0x99]));
319 assert_eq!(fh("ff", false).ok(), Some(vec![0xff]));
320 assert_eq!(fh("000", false).ok(), None);
321 assert_eq!(fh("0000", false).ok(), Some(vec![0x00, 0x00]));
322 assert_eq!(fh("0009", false).ok(), Some(vec![0x00, 0x09]));
323 assert_eq!(fh("000f", false).ok(), Some(vec![0x00, 0x0f]));
324 assert_eq!(fh("0099", false).ok(), Some(vec![0x00, 0x99]));
325 assert_eq!(fh("00ff", false).ok(), Some(vec![0x00, 0xff]));
326 assert_eq!(fh("\t\n\x0c\r ", false).ok(), None);
327 assert_eq!(fh("a", false).ok(), None);
328 assert_eq!(fh("0x", false).ok(), None);
329 assert_eq!(fh("0x0", false).ok(), None);
330 assert_eq!(fh("0x00", false).ok(), None);
331 }
332
333 #[test]
from_pretty_hex()334 fn from_pretty_hex() {
335 use super::from_hex as fh;
336 assert_eq!(fh(" ", true).ok(), Some(vec![]));
337 assert_eq!(fh(" 0", true).ok(), None);
338 assert_eq!(fh(" 00", true).ok(), Some(vec![0x00]));
339 assert_eq!(fh(" 09", true).ok(), Some(vec![0x09]));
340 assert_eq!(fh(" 0f", true).ok(), Some(vec![0x0f]));
341 assert_eq!(fh(" 99", true).ok(), Some(vec![0x99]));
342 assert_eq!(fh(" ff", true).ok(), Some(vec![0xff]));
343 assert_eq!(fh(" 00 0", true).ok(), None);
344 assert_eq!(fh(" 00 00", true).ok(), Some(vec![0x00, 0x00]));
345 assert_eq!(fh(" 00 09", true).ok(), Some(vec![0x00, 0x09]));
346 assert_eq!(fh(" 00 0f", true).ok(), Some(vec![0x00, 0x0f]));
347 assert_eq!(fh(" 00 99", true).ok(), Some(vec![0x00, 0x99]));
348 assert_eq!(fh(" 00 ff", true).ok(), Some(vec![0x00, 0xff]));
349 assert_eq!(fh("\t\n\x0c\r ", true).ok(), Some(vec![]));
350 // Fancy Unicode spaces are ok too:
351 assert_eq!(fh(" 23", true).ok(), Some(vec![0x23]));
352 assert_eq!(fh("a", true).ok(), None);
353 assert_eq!(fh(" 0x", true).ok(), Some(vec![]));
354 assert_eq!(fh(" 0x0", true).ok(), None);
355 assert_eq!(fh(" 0x00", true).ok(), Some(vec![0x00]));
356 }
357
358 quickcheck! {
359 fn hex_roundtrip(data: Vec<u8>) -> bool {
360 let hex = super::to_hex(&data, false);
361 data == super::from_hex(&hex, false).unwrap()
362 }
363 }
364
365 quickcheck! {
366 fn pretty_hex_roundtrip(data: Vec<u8>) -> bool {
367 let hex = super::to_hex(&data, true);
368 data == super::from_hex(&hex, true).unwrap()
369 }
370 }
371
372 #[test]
hex_dumper()373 fn hex_dumper() {
374 use super::hex::Dumper;
375
376 let mut dumper = Dumper::new(Vec::new(), "III");
377 dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap();
378 let buf = dumper.into_inner();
379 assert_eq!(
380 ::std::str::from_utf8(&buf[..]).unwrap(),
381 "III00000000 \
382 89 01 33 \
383 frame\n");
384
385 let mut dumper = Dumper::new(Vec::new(), "III");
386 dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame")
387 .unwrap();
388 let buf = dumper.into_inner();
389 assert_eq!(
390 ::std::str::from_utf8(&buf[..]).unwrap(),
391 "III00000000 \
392 89 01 33 89 01 33 89 01 \
393 frame\n");
394
395 let mut dumper = Dumper::new(Vec::new(), "III");
396 dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
397 0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame")
398 .unwrap();
399 let buf = dumper.into_inner();
400 assert_eq!(
401 ::std::str::from_utf8(&buf[..]).unwrap(),
402 "III00000000 \
403 89 01 33 89 01 33 89 01 89 01 33 89 01 33 89 01 \
404 frame\n");
405
406 let mut dumper = Dumper::new(Vec::new(), "III");
407 dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
408 0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
409 0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
410 0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame")
411 .unwrap();
412 let buf = dumper.into_inner();
413 assert_eq!(
414 ::std::str::from_utf8(&buf[..]).unwrap(),
415 "III00000000 \
416 89 01 33 89 01 33 89 01 89 01 33 89 01 33 89 01 \
417 frame\n\
418 III00000010 \
419 89 01 33 89 01 33 89 01 89 01 33 89 01 33 89 01\n");
420
421 let mut dumper = Dumper::new(Vec::new(), "");
422 dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap();
423 dumper.write(&[0x04], "version").unwrap();
424 dumper.write(&[0x00], "type").unwrap();
425 let buf = dumper.into_inner();
426 assert_eq!(
427 ::std::str::from_utf8(&buf[..]).unwrap(),
428 "00000000 89 01 33 \
429 frame\n\
430 00000003 04 \
431 version\n\
432 00000004 00 \
433 type\n\
434 ");
435 }
436
437 #[test]
time()438 fn time() {
439 use super::time;
440 use crate::types::Timestamp;
441 let t = |epoch| -> std::time::SystemTime {
442 Timestamp::from(epoch).into()
443 };
444 assert_eq!(&time(&t(1585217290)), "2020-03-26T10:08:10Z");
445 }
446 }
447