use anyhow::{anyhow, Result};
use clap::Parser;
-use num::Num;
+use hexplay::HexViewBuilder;
+use num::{Float, Num};
+use std::{fmt, num::FpCategory};
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
fn main() -> Result<()> {
let Args { max_cases, files } = Args::parse();
- let error = false;
for file in files {
Dissector::new(file)?;
}
s
}
+fn slice_trim_end(mut s: &[u8], c: u8) -> &[u8] {
+ while s.last() == Some(&c) {
+ s = s.split_last().unwrap().1;
+ }
+ s
+}
+
fn format_name(type_: u32) -> &'static str {
match type_ {
1 => "A",
(x + (y - T::one())) / y * y
}
+struct UntypedValue {
+ raw: [u8; 8],
+ endianness: Endianness
+}
+
impl UntypedValue {
- fn new(
+ fn new(raw: [u8; 8], endianness: Endianness) -> UntypedValue {
+ UntypedValue { raw, endianness }
+ }
+}
+
+impl fmt::Display for UntypedValue {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let numeric: f64 = self.endianness.parse(self.raw);
+ let n_printable = self.raw.iter().take_while(|&&x| x == b' ' || x.is_ascii_graphic()).count();
+ let printable_prefix = std::str::from_utf8(&self.raw[0..n_printable]).unwrap();
+ write!(f, "{numeric}/\"{printable_prefix}\"")
+ }
+}
+
+struct HexFloat<T: Float>(T);
+
+impl<T: Float> fmt::Display for HexFloat<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let sign = if self.0.is_sign_negative() { "-" } else { "" };
+ match self.0.classify() {
+ FpCategory::Nan => return write!(f, "NaN"),
+ FpCategory::Infinite => return write!(f, "{sign}Infinity"),
+ FpCategory::Zero => return write!(f, "{sign}0.0"),
+ _ => (),
+ };
+ let (significand, mut exponent, _) = self.0.integer_decode();
+ let mut hex_sig = format!("{:x}", significand);
+ while hex_sig.ends_with('0') {
+ hex_sig.pop();
+ exponent += 4;
+ }
+ match hex_sig.len() {
+ 0 => write!(f, "{sign}0.0"),
+ 1 => write!(f, "{sign}0x{hex_sig}.0p{exponent}"),
+ len => write!(f, "{sign}0x{}.{}p{}",
+ hex_sig.chars().nth(0).unwrap(),
+ &hex_sig[1..],
+ exponent + 4 * (len as i16 - 1))
+ }
+ }
+}
+
+#[cfg(test)]
+mod hex_float_tests {
+ use crate::HexFloat;
+ use num::Float;
+
+ #[test]
+ fn test() {
+ assert_eq!(format!("{}", HexFloat(1.0)), "0x1.0p0");
+ assert_eq!(format!("{}", HexFloat(123.0)), "0x1.ecp6");
+ assert_eq!(format!("{}", HexFloat(1.0 / 16.0)), "0x1.0p-4");
+ assert_eq!(format!("{}", HexFloat(f64::infinity())), "Infinity");
+ assert_eq!(format!("{}", HexFloat(f64::neg_infinity())), "-Infinity");
+ assert_eq!(format!("{}", HexFloat(f64::nan())), "NaN");
+ assert_eq!(format!("{}", HexFloat(0.0)), "0.0");
+ assert_eq!(format!("{}", HexFloat(f64::neg_zero())), "-0.0");
+ }
}
impl Dissector {
let creation_date: [u8; 9] = read_bytes(&mut d.r)?;
let creation_time: [u8; 8] = read_bytes(&mut d.r)?;
let file_label: [u8; 64] = read_bytes(&mut d.r)?;
- let mut file_label = trim_end(Vec::from(file_label), b' ');
- d.r.seek_relative(3)?;
+ let file_label = trim_end(Vec::from(file_label), b' ');
+ d.skip_bytes(3)?;
println!("File header record:");
println!("{:>17}: {}", "Product name", String::from_utf8_lossy(&eye_catcher));
2 => d.read_variable_record()?,
3 => d.read_value_label_record()?,
4 => Err(anyhow!("Misplaced type 4 record."))?,
+ 6 => d.read_document_record()?,
+ 7 => d.read_extension_record()?,
999 => break,
_ => Err(anyhow!("Unrecognized record type {rec_type}."))?
}
Ok(d)
}
+ fn read_extension_record(&mut self) -> Result<()> {
+ let offset = self.r.stream_position()?;
+ let subtype: u32 = self.read_swap()?;
+ let size: u32 = self.read_swap()?;
+ let count: u32 = self.read_swap()?;
+ println!("{offset:08x}: Record 7, subtype {subtype}, size={size}, count={count}");
+ match subtype {
+ 3 => self.read_machine_integer_info(size, count),
+ 4 => self.read_machine_float_info(size, count),
+ _ => self.read_unknown_extension(subtype, size, count),
+ }
+ }
+
+ fn warn(&mut self, s: String) -> Result<()> {
+ println!("\"{}\" near offset 0x{:08x}: {s}", self.filename, self.r.stream_position()?);
+ Ok(())
+ }
+
+ fn skip_bytes(&mut self, mut n: u64) -> Result<()> {
+ let mut buf = [0; 1024];
+ while n > 0 {
+ let chunk = u64::min(n, buf.len() as u64);
+ self.r.read_exact(&mut buf[0..chunk as usize])?;
+ n -= chunk;
+ }
+ Ok(())
+ }
+
+ fn read_unknown_extension(&mut self, subtype: u32, size: u32, count: u32) -> Result<()> {
+ self.warn(format!("Unrecognized record type 7, subtype {subtype}."))?;
+ if size == 0 || count > 65536 / size {
+ self.skip_bytes(size as u64 * count as u64)?;
+ } else if size != 1 {
+ let mut offset = 0;
+ for _ in 0..count {
+ let vec = read_vec(&mut self.r, size as usize)?;
+ println!("{}", HexViewBuilder::new(&vec).address_offset(offset).finish());
+ offset += size as usize;
+ }
+ }
+ Ok(())
+ }
+
fn read_variable_record(&mut self) -> Result<()> {
self.n_variable_records += 1;
println!("{:08x}: variable record {}", self.r.stream_position()?, self.n_variable_records);
let label = read_vec(&mut self.r, read_len)?;
println!("\t{offset:08x} Variable label: \"{}\"", String::from_utf8_lossy(&label));
- self.r.seek_relative((round_up(len, 4) - len).into())?;
+ self.skip_bytes((round_up(len, 4) - len).into())?;
},
_ => Err(anyhow!("Variable label indicator field is not 0 or 1."))?,
};
let high: f64 = self.read_swap()?;
print!(" {low}...{high}");
}
- for _i in 0..n_individual {
+ for _ in 0..n_individual {
let value: f64 = self.read_swap()?;
print!(" {value}");
}
if missing_value_code < 1 || missing_value_code > 3 {
Err(anyhow!("String missing value indicator field is not 0, 1, 2, or 3."))?;
}
- for _i in 0..missing_value_code {
+ for _ in 0..missing_value_code {
let string: [u8; 8] = read_bytes(&mut self.r)?;
let string: Vec<u8> = trim_end(Vec::from(string), b'\0');
println!(" {}", String::from_utf8_lossy(&string));
fn read_value_label_record(&mut self) -> Result<()> {
println!("{:08x}: value labels record", self.r.stream_position()?);
+ // Read the labels.
let n_labels: u32 = self.read_swap()?;
- for _i in 0..n_labels {
+ for _ in 0..n_labels {
let raw: [u8; 8] = read_bytes(&mut self.r)?;
+ let value = UntypedValue::new(raw, self.fp_format);
let label_len: u8 = self.read_swap()?;
let padded_len = round_up(label_len as usize + 1, 8);
let mut label = read_vec(&mut self.r, padded_len)?;
label.truncate(label_len as usize);
- print
+ let label = String::from_utf8_lossy(&label);
+
+ println!("\t{value}: {label}");
+ }
+
+ // Read the type-4 record with the corresponding variable indexes.
+ let rec_type: u32 = self.read_swap()?;
+ if rec_type != 4 {
+ Err(anyhow!("Variable index record (type 4) does not immediately \
+ follow value label record (type 3) as it should."))?;
+ }
+
+ println!("\t{:08x}: apply to variables", self.r.stream_position()?);
+ let n_vars: u32 = self.read_swap()?;
+ for _ in 0..n_vars {
+ let index: u32 = self.read_swap()?;
+ print!(" {index}");
}
+ println!();
Ok(())
}
+
+ fn read_document_record(&mut self) -> Result<()> {
+ println!("{:08x}: document record", self.r.stream_position()?);
+ let n_lines: u32 = self.read_swap()?;
+ println!("\t{n_lines} lines of documents");
+
+ for i in 0..n_lines {
+ print!("\t{:08x}: ", self.r.stream_position()?);
+ let line: [u8; 64] = read_bytes(&mut self.r)?;
+ let line = trim_end(Vec::from(line), b' ');
+ println!("line {i}: \"{}\"", String::from_utf8_lossy(&line));
+ }
+ Ok(())
+ }
+
+ fn read_machine_integer_info(&mut self, size: u32, count: u32) -> Result<()> {
+ let offset = self.r.stream_position()?;
+ let version_major: u32 = self.read_swap()?;
+ let version_minor: u32 = self.read_swap()?;
+ let version_revision: u32 = self.read_swap()?;
+ let machine_code: u32 = self.read_swap()?;
+ let float_representation: u32 = self.read_swap()?;
+ let compression_code: u32 = self.read_swap()?;
+ let integer_representation: u32 = self.read_swap()?;
+ let character_code: u32 = self.read_swap()?;
+
+ println!("{offset:08x}: machine integer info");
+ if size != 4 || count != 8 {
+ Err(anyhow!("Bad size ({size}) or count ({count}) field on record type 7, subtype 3"))?;
+ }
+ println!("\tVersion: {version_major}.{version_minor}.{version_revision}");
+ println!("\tMachine code: {machine_code}");
+ println!("\tFloating point representation: {float_representation} ({})",
+ match float_representation {
+ 1 => "IEEE 754",
+ 2 => "IBM 370",
+ 3 => "DEC VAX",
+ _ => "unknown"
+ });
+ println!("\tCompression code: {compression_code}");
+ println!("\tEndianness: {integer_representation} ({})",
+ match integer_representation {
+ 1 => "big",
+ 2 => "little",
+ _ => "unknown"
+ });
+ println!("\tCharacter code: {character_code}");
+ Ok(())
+ }
+
+ fn read_machine_float_info(&mut self, size: u32, count: u32) -> Result<()> {
+ let offset = self.r.stream_position()?;
+ let sysmis: f64 = self.read_swap()?;
+ let highest: f64 = self.read_swap()?;
+ let lowest: f64 = self.read_swap()?;
+
+ println!("{offset:08x}: machine float info");
+ if size != 4 || count != 8 {
+ Err(anyhow!("Bad size ({size}) or count ({count}) field on extension 4."))?;
+ }
+
+ println!("\tsysmis: {sysmis} ({})", HexFloat(sysmis));
+ println!("\thighest: {highest} ({})", HexFloat(highest));
+ println!("\tlowest: {lowest} ({})", HexFloat(lowest));
+ Ok(())
+ }
+
+ fn read_variable_sets(&mut self, size: u32, count: u32) -> Result<()> {
+ println!("{:08x}: variable sets", self.r.stream_position()?);
+ let mut text = self.open_text_record(size, count)?;
+ loop {
+ while text.match_byte(b'\n') {
+ continue;
+ }
+ let set = match text.tokenize(b'=') {
+ Some(set) => String::from_utf8_lossy(&set).into_owned(),
+ None => break,
+ };
+
+ // Always present even for an empty set.
+ text.match_byte(b' ');
+
+ match text.tokenize(b'\n') {
+ None => println!("\tset \"{set}\" is empty"),
+ Some(variables) => {
+ println!("\tset \"{set}\" contains \"{}\"", String::from_utf8_lossy(variables).trim_end_matches('\r'));
+ },
+ };
+
+ }
+ Ok(())
+ }
+
+ fn read_extra_product_info(&mut self, size: u32, count: u32) -> Result<()> {
+ print!("{:08x}: extra product info", self.r.stream_position()?);
+ let mut text = self.open_text_record(size, count)?;
+
+ }
+
+ fn open_text_record(&mut self, size: u32, count: u32) -> Result<TextRecord> {
+ let n_bytes = match u32::checked_mul(size, count) {
+ Some(n) => n,
+ None => Err(anyhow!("Extension record too large."))?
+ };
+ Ok(TextRecord::new(read_vec(&mut self.r, n_bytes as usize)?))
+ }
+}
+
+struct TextRecord {
+ buffer: Vec<u8>,
+ pos: usize
+}
+
+impl TextRecord {
+ fn new(buffer: Vec<u8>) -> TextRecord {
+ TextRecord { buffer, pos: 0 }
+ }
+
+ fn tokenize<'a>(&'a mut self, delimiter: u8) -> Option<&'a [u8]> {
+ let mut start = self.pos;
+ while self.pos < self.buffer.len() && self.buffer[self.pos] != delimiter && self.buffer[self.pos] != 0 {
+ self.pos += 1
+ }
+ if start == self.pos {
+ None
+ } else {
+ Some(&self.buffer[start..self.pos])
+ }
+ }
+
+ fn match_byte(&mut self, c: u8) -> bool {
+ if self.pos < self.buffer.len() && self.buffer[self.pos] == c {
+ self.pos += 1;
+ true
+ } else {
+ false
+ }
+ }
}