sas7bdat 0.2.0

Rust library + CLI for decoding SAS7BDAT datasets and streaming them to modern formats.
Documentation
use sas7bdat::parser::{parse_header, read_u16, read_u32, read_u64};
use std::{
    env,
    fs::File,
    io::{Read, Seek, SeekFrom},
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let path = env::args().nth(1).expect("path required");
    let mut file = File::open(&path)?;
    let header = parse_header(&mut file)?;
    let mut page = vec![0u8; header.page_size as usize];

    for page_index in 0..header.page_count {
        let offset = header.data_offset + page_index * u64::from(header.page_size);
        file.seek(SeekFrom::Start(offset))?;
        file.read_exact(&mut page)?;
        let subheader_count = read_u16(
            header.endianness,
            &page[(header.page_header_size as usize) - 4..(header.page_header_size as usize) - 2],
        );
        let mut cursor = header.page_header_size as usize;
        for pointer_index in 0..subheader_count {
            let pointer = &page[cursor..cursor + header.subheader_pointer_size as usize];
            cursor += header.subheader_pointer_size as usize;
            let (offset, length) = if header.uses_u64 {
                let offset_raw = read_u64(header.endianness, &pointer[0..8]);
                let length_raw = read_u64(header.endianness, &pointer[8..16]);
                let Ok(offset) = usize::try_from(offset_raw) else {
                    println!(
                        "page {page_index} pointer {pointer_index}: offset {offset_raw} exceeds usize"
                    );
                    continue;
                };
                let Ok(length) = usize::try_from(length_raw) else {
                    println!(
                        "page {page_index} pointer {pointer_index}: length {length_raw} exceeds usize"
                    );
                    continue;
                };
                (offset, length)
            } else {
                (
                    read_u32(header.endianness, &pointer[0..4]) as usize,
                    read_u32(header.endianness, &pointer[4..8]) as usize,
                )
            };
            let compression = pointer[if header.uses_u64 { 16 } else { 8 }];
            let page_ref = if header.uses_u64 {
                read_u32(header.endianness, &pointer[18..22])
            } else {
                u32::from(read_u16(header.endianness, &pointer[10..12]))
            };
            let extra_flag = pointer[if header.uses_u64 { 17 } else { 9 }];

            if length == 0 {
                continue;
            }
            let end = offset + length;
            if end > page.len() {
                println!(
                    "page {page_index} pointer {pointer_index}: offset={offset} length={length} end={end} > page_len={} compression={} extra_flag={} page_ref={}",
                    page.len(),
                    compression,
                    extra_flag,
                    page_ref
                );
            }
        }
    }

    Ok(())
}