1use std::io::{self, Read, Write};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum AddressRadix {
6 Octal,
7 Decimal,
8 Hex,
9 None,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum OutputFormat {
15 NamedChar,
17 PrintableChar,
19 SignedDec(usize),
21 Float(usize),
23 Octal(usize),
25 UnsignedDec(usize),
27 Hex(usize),
29}
30
31#[derive(Debug, Clone)]
33pub struct OdConfig {
34 pub address_radix: AddressRadix,
35 pub formats: Vec<OutputFormat>,
36 pub skip_bytes: u64,
37 pub read_bytes: Option<u64>,
38 pub width: usize,
39 pub show_duplicates: bool,
40}
41
42impl Default for OdConfig {
43 fn default() -> Self {
44 Self {
45 address_radix: AddressRadix::Octal,
46 formats: vec![OutputFormat::Octal(2)],
47 skip_bytes: 0,
48 read_bytes: None,
49 width: 16,
50 show_duplicates: false,
51 }
52 }
53}
54
55const NAMED_CHARS: [&str; 128] = [
58 "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", " bs", " ht", " nl", " vt", " ff",
59 " cr", " so", " si", "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", "can", " em",
60 "sub", "esc", " fs", " gs", " rs", " us", " sp", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
61 "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<",
62 "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
63 "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b",
64 "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
65 "v", "w", "x", "y", "z", "{", "|", "}", "~", "del",
66];
67
68fn format_address(offset: u64, radix: AddressRadix) -> String {
70 match radix {
71 AddressRadix::Octal => format!("{:07o}", offset),
72 AddressRadix::Decimal => format!("{:07}", offset),
73 AddressRadix::Hex => format!("{:06x}", offset),
74 AddressRadix::None => String::new(),
75 }
76}
77
78fn field_width(fmt: OutputFormat) -> usize {
81 match fmt {
82 OutputFormat::NamedChar => 4, OutputFormat::PrintableChar => 4, OutputFormat::Octal(1) => 4, OutputFormat::Octal(2) => 7, OutputFormat::Octal(4) => 12, OutputFormat::Octal(8) => 23, OutputFormat::Hex(1) => 3, OutputFormat::Hex(2) => 5, OutputFormat::Hex(4) => 9, OutputFormat::Hex(8) => 17, OutputFormat::UnsignedDec(1) => 4, OutputFormat::UnsignedDec(2) => 6, OutputFormat::UnsignedDec(4) => 11, OutputFormat::UnsignedDec(8) => 21, OutputFormat::SignedDec(1) => 5, OutputFormat::SignedDec(2) => 7, OutputFormat::SignedDec(4) => 12, OutputFormat::SignedDec(8) => 21, OutputFormat::Float(4) => 16, OutputFormat::Float(8) => 25, _ => 4,
103 }
104}
105
106fn element_size(fmt: OutputFormat) -> usize {
108 match fmt {
109 OutputFormat::NamedChar | OutputFormat::PrintableChar => 1,
110 OutputFormat::SignedDec(s)
111 | OutputFormat::Float(s)
112 | OutputFormat::Octal(s)
113 | OutputFormat::UnsignedDec(s)
114 | OutputFormat::Hex(s) => s,
115 }
116}
117
118fn format_value(bytes: &[u8], fmt: OutputFormat, width: usize) -> String {
120 match fmt {
121 OutputFormat::NamedChar => {
122 let b = bytes[0];
123 if b < 128 {
124 format!("{:>w$}", NAMED_CHARS[b as usize], w = width)
125 } else {
126 format!("{:>w$}", format!("{:03o}", b), w = width)
127 }
128 }
129 OutputFormat::PrintableChar => {
130 let b = bytes[0];
131 let s = match b {
132 0x00 => "\\0".to_string(),
133 0x07 => "\\a".to_string(),
134 0x08 => "\\b".to_string(),
135 0x09 => "\\t".to_string(),
136 0x0a => "\\n".to_string(),
137 0x0b => "\\v".to_string(),
138 0x0c => "\\f".to_string(),
139 0x0d => "\\r".to_string(),
140 0x20..=0x7e => format!("{}", b as char),
141 _ => format!("{:03o}", b),
142 };
143 format!("{:>w$}", s, w = width)
144 }
145 OutputFormat::Octal(size) => match size {
146 1 => format!("{:>w$}", format!("{:03o}", bytes[0]), w = width),
147 2 => {
148 let v = u16::from_le_bytes(bytes[..2].try_into().unwrap());
149 format!("{:>w$}", format!("{:06o}", v), w = width)
150 }
151 4 => {
152 let v = u32::from_le_bytes(bytes[..4].try_into().unwrap());
153 format!("{:>w$}", format!("{:011o}", v), w = width)
154 }
155 8 => {
156 let v = u64::from_le_bytes(bytes[..8].try_into().unwrap());
157 format!("{:>w$}", format!("{:022o}", v), w = width)
158 }
159 _ => String::new(),
160 },
161 OutputFormat::Hex(size) => match size {
162 1 => format!("{:>w$}", format!("{:02x}", bytes[0]), w = width),
163 2 => {
164 let v = u16::from_le_bytes(bytes[..2].try_into().unwrap());
165 format!("{:>w$}", format!("{:04x}", v), w = width)
166 }
167 4 => {
168 let v = u32::from_le_bytes(bytes[..4].try_into().unwrap());
169 format!("{:>w$}", format!("{:08x}", v), w = width)
170 }
171 8 => {
172 let v = u64::from_le_bytes(bytes[..8].try_into().unwrap());
173 format!("{:>w$}", format!("{:016x}", v), w = width)
174 }
175 _ => String::new(),
176 },
177 OutputFormat::UnsignedDec(size) => match size {
178 1 => format!("{:>w$}", bytes[0], w = width),
179 2 => {
180 let v = u16::from_le_bytes(bytes[..2].try_into().unwrap());
181 format!("{:>w$}", v, w = width)
182 }
183 4 => {
184 let v = u32::from_le_bytes(bytes[..4].try_into().unwrap());
185 format!("{:>w$}", v, w = width)
186 }
187 8 => {
188 let v = u64::from_le_bytes(bytes[..8].try_into().unwrap());
189 format!("{:>w$}", v, w = width)
190 }
191 _ => String::new(),
192 },
193 OutputFormat::SignedDec(size) => match size {
194 1 => format!("{:>w$}", bytes[0] as i8, w = width),
195 2 => {
196 let v = i16::from_le_bytes(bytes[..2].try_into().unwrap());
197 format!("{:>w$}", v, w = width)
198 }
199 4 => {
200 let v = i32::from_le_bytes(bytes[..4].try_into().unwrap());
201 format!("{:>w$}", v, w = width)
202 }
203 8 => {
204 let v = i64::from_le_bytes(bytes[..8].try_into().unwrap());
205 format!("{:>w$}", v, w = width)
206 }
207 _ => String::new(),
208 },
209 OutputFormat::Float(size) => match size {
210 4 => {
211 let v = f32::from_le_bytes(bytes[..4].try_into().unwrap());
212 format!("{:>w$}", format_float_f32(v), w = width)
213 }
214 8 => {
215 let v = f64::from_le_bytes(bytes[..8].try_into().unwrap());
216 format!("{:>w$}", format_float_f64(v), w = width)
217 }
218 _ => String::new(),
219 },
220 }
221}
222
223fn snprintf_g(v: f64, precision: usize) -> String {
226 let precision = precision.min(50);
227 #[cfg(unix)]
228 {
229 let mut buf = [0u8; 64];
230 let fmt = std::ffi::CString::new(format!("%.{}g", precision)).unwrap();
231 let len = unsafe {
232 libc::snprintf(
233 buf.as_mut_ptr() as *mut libc::c_char,
234 buf.len(),
235 fmt.as_ptr(),
236 v,
237 )
238 };
239 if len > 0 && (len as usize) < buf.len() {
240 return String::from_utf8_lossy(&buf[..len as usize]).into_owned();
241 }
242 }
243 let s = format!("{:.prec$e}", v, prec = precision.saturating_sub(1));
245 if let Some(e_pos) = s.find('e') {
247 let exp: i32 = s[e_pos + 1..].parse().unwrap_or(0);
248 if exp >= -(precision as i32) && exp < precision as i32 {
249 let fixed = format!(
251 "{:.prec$}",
252 v,
253 prec = (precision as i32 - 1 - exp).max(0) as usize
254 );
255 if fixed.contains('.') {
257 let trimmed = fixed.trim_end_matches('0').trim_end_matches('.');
258 return trimmed.to_string();
259 }
260 return fixed;
261 }
262 }
263 format!("{:.*e}", precision.saturating_sub(1), v)
264}
265
266fn format_float_f32(v: f32) -> String {
268 for prec in 6usize..=9 {
271 let s = snprintf_g(v as f64, prec);
272 if let Ok(reparsed) = s.trim().parse::<f32>() {
273 if reparsed == v {
274 return s;
275 }
276 }
277 }
278 snprintf_g(v as f64, 9)
279}
280
281fn format_float_f64(v: f64) -> String {
283 snprintf_g(v, 17)
284}
285
286fn format_line(
288 chunk: &[u8],
289 fmt: OutputFormat,
290 line_width: usize,
291 is_first_format: bool,
292 radix: AddressRadix,
293 offset: u64,
294) -> String {
295 let mut line = String::new();
296
297 if is_first_format {
299 line.push_str(&format_address(offset, radix));
300 } else if radix != AddressRadix::None {
301 let addr_width = match radix {
303 AddressRadix::Octal => 7,
304 AddressRadix::Decimal => 7,
305 AddressRadix::Hex => 6,
306 AddressRadix::None => 0,
307 };
308 for _ in 0..addr_width {
309 line.push(' ');
310 }
311 }
312
313 let elem_sz = element_size(fmt);
314 let fw = field_width(fmt);
315
316 let num_elems = line_width / elem_sz;
318
319 let actual_full = chunk.len() / elem_sz;
321 let remainder = chunk.len() % elem_sz;
322
323 for i in 0..num_elems {
324 if i < actual_full {
325 let start = i * elem_sz;
326 let end = start + elem_sz;
327 line.push_str(&format_value(&chunk[start..end], fmt, fw));
328 } else if i == actual_full && remainder > 0 {
329 let start = i * elem_sz;
331 let mut padded = vec![0u8; elem_sz];
332 padded[..remainder].copy_from_slice(&chunk[start..]);
333 line.push_str(&format_value(&padded, fmt, fw));
334 }
335 }
336
337 line
338}
339
340pub fn parse_format_type(s: &str) -> Result<OutputFormat, String> {
342 if s.is_empty() {
343 return Err("empty format string".to_string());
344 }
345
346 let mut chars = s.chars();
347 let type_char = chars.next().unwrap();
348 let size_str: String = chars.collect();
349
350 match type_char {
351 'a' => Ok(OutputFormat::NamedChar),
352 'c' => Ok(OutputFormat::PrintableChar),
353 'd' => {
354 let size = if size_str.is_empty() {
355 4
356 } else {
357 parse_size_spec(&size_str, "d")?
358 };
359 Ok(OutputFormat::SignedDec(size))
360 }
361 'f' => {
362 let size = if size_str.is_empty() {
363 4
364 } else {
365 parse_float_size(&size_str)?
366 };
367 Ok(OutputFormat::Float(size))
368 }
369 'o' => {
370 let size = if size_str.is_empty() {
371 2
372 } else {
373 parse_size_spec(&size_str, "o")?
374 };
375 Ok(OutputFormat::Octal(size))
376 }
377 'u' => {
378 let size = if size_str.is_empty() {
379 4
380 } else {
381 parse_size_spec(&size_str, "u")?
382 };
383 Ok(OutputFormat::UnsignedDec(size))
384 }
385 'x' => {
386 let size = if size_str.is_empty() {
387 2
388 } else {
389 parse_size_spec(&size_str, "x")?
390 };
391 Ok(OutputFormat::Hex(size))
392 }
393 _ => Err(format!("invalid type string '{}'", s)),
394 }
395}
396
397fn parse_size_spec(s: &str, type_name: &str) -> Result<usize, String> {
398 match s {
400 "C" => Ok(1),
401 "S" => Ok(2),
402 "I" => Ok(4),
403 "L" => Ok(8),
404 _ => {
405 let n: usize = s
406 .parse()
407 .map_err(|_| format!("invalid type string '{}{}': invalid size", type_name, s))?;
408 match n {
409 1 | 2 | 4 | 8 => Ok(n),
410 _ => Err(format!(
411 "invalid type string '{}{}': invalid size",
412 type_name, s
413 )),
414 }
415 }
416 }
417}
418
419fn parse_float_size(s: &str) -> Result<usize, String> {
420 match s {
421 "F" | "4" => Ok(4),
422 "D" | "8" => Ok(8),
423 "L" | "16" => Err("16-byte float not supported".to_string()),
424 _ => {
425 let n: usize = s
426 .parse()
427 .map_err(|_| format!("invalid float size '{}'", s))?;
428 match n {
429 4 | 8 => Ok(n),
430 _ => Err(format!("invalid float size '{}'", s)),
431 }
432 }
433 }
434}
435
436pub fn od_process<R: Read, W: Write>(
438 mut input: R,
439 output: &mut W,
440 config: &OdConfig,
441) -> io::Result<()> {
442 if config.skip_bytes > 0 {
444 let mut to_skip = config.skip_bytes;
445 let mut skip_buf = [0u8; 8192];
446 while to_skip > 0 {
447 let chunk_size = std::cmp::min(to_skip, skip_buf.len() as u64) as usize;
448 let n = input.read(&mut skip_buf[..chunk_size])?;
449 if n == 0 {
450 break;
451 }
452 to_skip -= n as u64;
453 }
454 }
455
456 let data = match config.read_bytes {
458 Some(limit) => {
459 let mut buf = Vec::new();
460 let mut limited = input.take(limit);
461 limited.read_to_end(&mut buf)?;
462 buf
463 }
464 None => {
465 let mut buf = Vec::new();
466 input.read_to_end(&mut buf)?;
467 buf
468 }
469 };
470
471 let width = config.width;
472 let mut offset = config.skip_bytes;
473 let mut prev_chunk: Option<Vec<u8>> = None;
474 let mut star_printed = false;
475
476 let mut pos = 0;
477 while pos < data.len() {
478 let end = std::cmp::min(pos + width, data.len());
479 let chunk = &data[pos..end];
480
481 if !config.show_duplicates && chunk.len() == width {
483 if let Some(ref prev) = prev_chunk {
484 if prev.as_slice() == chunk {
485 if !star_printed {
486 writeln!(output, "*")?;
487 star_printed = true;
488 }
489 pos += width;
490 offset += width as u64;
491 continue;
492 }
493 }
494 }
495
496 star_printed = false;
497
498 for (i, fmt) in config.formats.iter().enumerate() {
499 let line = format_line(chunk, *fmt, width, i == 0, config.address_radix, offset);
500 writeln!(output, "{}", line)?;
501 }
502
503 prev_chunk = Some(chunk.to_vec());
504 pos += width;
505 offset += width as u64;
506 }
507
508 if config.address_radix != AddressRadix::None {
510 let final_offset = config.skip_bytes + data.len() as u64;
512 writeln!(
513 output,
514 "{}",
515 format_address(final_offset, config.address_radix)
516 )?;
517 }
518
519 Ok(())
520}