prepare digest reading

This commit is contained in:
2025-06-23 21:32:34 +08:00
parent 71e2ed9dcf
commit d6317502a3
4 changed files with 127 additions and 143 deletions

View File

@ -1,6 +1,7 @@
use super::shared::{self, Endian}; use super::shared::{self, Endian};
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
use std::io::{Read, Seek, SeekFrom}; use std::io::{Read, Seek, SeekFrom};
use std::str::FromStr;
use thiserror::Error as TeError; use thiserror::Error as TeError;
// Reference: // Reference:
@ -14,13 +15,11 @@ use thiserror::Error as TeError;
pub enum Error { pub enum Error {
#[error("IO error {0}")] #[error("IO error {0}")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error( #[error("fail to cast value into integral value. maybe some values out of range")]
"fail to cast value into integral value. this usually caused by some value out of range"
)]
CastToInt(#[from] std::num::TryFromIntError), CastToInt(#[from] std::num::TryFromIntError),
#[error("something was overflow")] #[error("something was overflow")]
Overflow, Overflow,
#[error("wrong endian integer {0:#X} in block head. it must be one of 0x4 or 0x4000000")] #[error("wrong endian integer {0:#X} in block head. it must be one of 0x4 or 0x04000000")]
WrongEndianInt(u32), WrongEndianInt(u32),
#[error("wrong block head value {0:#X}. it must be 0x4")] #[error("wrong block head value {0:#X}. it must be 0x4")]
WrongBlockHead(u32), WrongBlockHead(u32),
@ -31,10 +30,6 @@ pub enum Error {
} }
type Result<T> = std::result::Result<T, Error>; type Result<T> = std::result::Result<T, Error>;
const ENDIAN_INT: u32 = 0x4;
const LITTLE_ENDIAN_INT: u32 = 0x4;
const BIG_ENDIAN_INT: u32 = 0x4000000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SimSource { enum SimSource {
/// For HSPICE, read as big endian. /// For HSPICE, read as big endian.
@ -51,14 +46,16 @@ impl SimSource {
} }
} }
pub struct HSpiceData {} const ENDIAN_INT: u32 = 0x4;
const LITTLE_ENDIAN_INT: u32 = 0x4;
const BIG_ENDIAN_INT: u32 = 0x04000000;
#[derive(Debug)] #[derive(Debug)]
struct HspiceBlock { struct HSpiceBlock {
block_offset: u64, block_offset: u64,
block_len: u32, block_len: u32,
} }
impl HspiceBlock { impl HSpiceBlock {
pub fn new(block_offset: u64, block_len: u32) -> Self { pub fn new(block_offset: u64, block_len: u32) -> Self {
Self { Self {
block_offset, block_offset,
@ -74,46 +71,86 @@ impl HspiceBlock {
} }
} }
#[derive(Debug)] #[derive(Debug)]
struct HspiceBlocks { struct HSpiceBlocks {
sim_source: SimSource, sim_source: SimSource,
blocks: Vec<HspiceBlock>, blocks: Vec<HSpiceBlock>,
header_blk_cnt: usize,
} }
impl HspiceBlocks { impl HSpiceBlocks {
pub fn new(sim_source: SimSource, blocks: Vec<HspiceBlock>, header_blk_cnt: usize) -> Self { pub fn new<T>(reader: &mut T) -> Result<Self>
Self { where
sim_source, T: ReadBytesExt + Seek,
blocks, {
header_blk_cnt, // Back to the file head
reader.rewind()?;
// Fetch the first u32 as little endian to decide the sim source
let endian_u32 = shared::read_int_value(Endian::LittleEndian, reader)?;
let sim_source = match endian_u32 {
LITTLE_ENDIAN_INT => SimSource::HspiceWin,
BIG_ENDIAN_INT => SimSource::Hspice,
other => return Err(Error::WrongEndianInt(other)),
};
let endian = sim_source.to_endian();
// Iterate each block
let mut blocks: Vec<HSpiceBlock> = Vec::new();
reader.rewind()?;
loop {
// Read block header.
// If there is no more data, break this loop.
let mut block_header = [0u32; 4];
if let Err(e) = shared::read_int_values(endian, &mut block_header, reader) {
match e.kind() {
std::io::ErrorKind::UnexpectedEof => break,
_ => return Err(e.into()),
}
}
// Check endian u32
if block_header[0] != ENDIAN_INT {
return Err(Error::WrongBlockHead(block_header[0]));
}
if block_header[2] != ENDIAN_INT {
return Err(Error::WrongBlockHead(block_header[2]));
}
// Get the offset and length of block body
let block_offset = reader.stream_position()?;
let block_len = block_header[3];
// Skip block body to check block tail
reader.seek(SeekFrom::Current(i64::try_from(block_len).unwrap()))?;
let block_tail = shared::read_int_value(endian, reader)?;
if block_tail != block_len {
return Err(Error::WrongBlockTail(block_tail, block_len));
}
// Push result
blocks.push(HSpiceBlock::new(block_offset, block_len));
// Cursor now at the beginning of next block.
// We can continue analyze next blocks safely.
} }
// Build result and return.
Ok(Self { sim_source, blocks })
} }
} }
impl<'a> HspiceBlocks { impl<'a> HSpiceBlocks {
pub fn to_reader<T>(&'a self, reader: T) -> HspiceBlocksReader<'a, T> pub fn to_reader<T>(&'a self, reader: T) -> HSpiceBlocksReader<'a, T>
where where
T: Read + Seek, T: Read + Seek,
{ {
HspiceBlocksReader::new(reader, &self.blocks) HSpiceBlocksReader::new(reader, &self.blocks)
}
pub fn to_header_reader<T>(&'a self, reader: T) -> HspiceBlocksReader<'a, T>
where
T: Read + Seek,
{
HspiceBlocksReader::new(reader, &self.blocks[..self.header_blk_cnt])
}
pub fn to_data_reader<T>(&'a self, reader: T) -> HspiceBlocksReader<'a, T>
where
T: Read + Seek,
{
HspiceBlocksReader::new(reader, &self.blocks[self.header_blk_cnt..])
} }
} }
struct HspiceBlocksReader<'a, T>
struct HSpiceBlocksReader<'a, T>
where where
T: Read + Seek, T: Read + Seek,
{ {
reader: T, reader: T,
blocks: &'a [HspiceBlock], blocks: &'a [HSpiceBlock],
/// The full size of this reader /// The full size of this reader
total_len: u64, total_len: u64,
@ -126,11 +163,11 @@ where
/// The cursor inside block. /// The cursor inside block.
cursor: u32, cursor: u32,
} }
impl<'a, T> HspiceBlocksReader<'a, T> impl<'a, T> HSpiceBlocksReader<'a, T>
where where
T: Read + Seek, T: Read + Seek,
{ {
pub fn new(reader: T, blocks: &'a [HspiceBlock]) -> Self { pub fn new(reader: T, blocks: &'a [HSpiceBlock]) -> Self {
// Build total length and offset table. // Build total length and offset table.
let mut total_len: u64 = 0; let mut total_len: u64 = 0;
let mut offset_table: Vec<u64> = Vec::with_capacity(blocks.len()); let mut offset_table: Vec<u64> = Vec::with_capacity(blocks.len());
@ -142,7 +179,7 @@ where
// otherwise Seek impl will panic when SeekFrom::Current. // otherwise Seek impl will panic when SeekFrom::Current.
offset_table.push(total_len); offset_table.push(total_len);
HspiceBlocksReader { HSpiceBlocksReader {
reader, reader,
blocks, blocks,
total_len, total_len,
@ -155,7 +192,7 @@ where
self.reader self.reader
} }
} }
impl<'a, T> Read for HspiceBlocksReader<'a, T> impl<'a, T> Read for HSpiceBlocksReader<'a, T>
where where
T: Read + Seek, T: Read + Seek,
{ {
@ -213,7 +250,7 @@ where
} }
} }
} }
impl<'a, T> Seek for HspiceBlocksReader<'a, T> impl<'a, T> Seek for HSpiceBlocksReader<'a, T>
where where
T: Read + Seek, T: Read + Seek,
{ {
@ -269,6 +306,46 @@ where
} }
} }
const SIGNAL_NAME_LEN: usize = 8;
const SIGNAL_NAME_MAGIC_TAIL: &[u8; 4] = b"$&%#";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HSpiceVersion {
V9007,
V9601,
V2001,
}
#[derive(Debug, TeError)]
#[error("can not parse given string into valid HSpice version")]
pub struct ParseHSpiceVersionError {}
impl FromStr for HSpiceVersion {
type Err = ParseHSpiceVersionError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"9007" => Ok(Self::V9007),
"9601" => Ok(Self::V9601),
"2001" => Ok(Self::V2001),
_ => Err(ParseHSpiceVersionError {}),
}
}
}
#[derive(Debug)]
struct HSpiceDigest {
version: HSpiceVersion,
file_name: String,
timestamp: String,
copyright: String,
}
#[derive(Debug)]
pub struct HSpiceContent {
digest: HSpiceDigest,
}
pub type HSpiceData = HSpiceContent;
/// Guess whether given reader is a legal HSPICE signal file. /// Guess whether given reader is a legal HSPICE signal file.
/// ///
/// Please note that this is a vauge check. /// Please note that this is a vauge check.
@ -294,102 +371,9 @@ where
} }
/// Load given reader as HSPICE signal file. /// Load given reader as HSPICE signal file.
pub fn load_signal<T>(reader: &mut T) -> Result<HSpiceData> pub fn load_signal<T>(reader: T) -> Result<HSpiceData>
where where
T: ReadBytesExt + Seek, T: ReadBytesExt + Seek,
{ {
// Fetch blocks list first Err(Error::Overflow)
let blocks = analyze_hspice_block(reader)?;
println!("{:#?}", blocks);
Ok(HSpiceData {})
} }
fn analyze_hspice_block<T>(reader: &mut T) -> Result<HspiceBlocks>
where
T: ReadBytesExt + Seek,
{
// Back to the file head
reader.rewind()?;
// Fetch the first u32 as little endian to decide the sim source
let endian_u32 = shared::read_int_value(Endian::LittleEndian, reader)?;
let sim_source = match endian_u32 {
LITTLE_ENDIAN_INT => SimSource::HspiceWin,
BIG_ENDIAN_INT => SimSource::Hspice,
other => return Err(Error::WrongEndianInt(other)),
};
let endian = sim_source.to_endian();
// Iterate each block
let mut blocks: Vec<HspiceBlock> = Vec::new();
reader.rewind()?;
loop {
// Read block header.
// If there is no more data, break this loop.
let mut block_header = [0u32; 4];
if let Err(e) = shared::read_int_values(endian, &mut block_header, reader) {
match e.kind() {
std::io::ErrorKind::UnexpectedEof => break,
_ => return Err(e.into()),
}
}
// Check endian u32
if block_header[0] != ENDIAN_INT {
return Err(Error::WrongBlockHead(block_header[0]));
}
if block_header[2] != ENDIAN_INT {
return Err(Error::WrongBlockHead(block_header[2]));
}
// Get the offset and length of block body
let block_offset = reader.stream_position()?;
let block_len = block_header[3];
// Skip block body to check block tail
reader.seek(SeekFrom::Current(i64::try_from(block_len).unwrap()))?;
let block_tail = shared::read_int_value(endian, reader)?;
if block_tail != block_len {
return Err(Error::WrongBlockTail(block_tail, block_len));
}
// Push result
blocks.push(HspiceBlock::new(block_offset, block_len));
// Cursor now at the beginning of next block.
// We can continue analyze next blocks safely.
}
// Analyze header block count
let mut header_blk_cnt: Option<usize> = None;
for (i, block) in blocks.iter().enumerate() {
let signal_name_len = u32::try_from(SIGNAL_NAME_LEN).unwrap();
let offset = if block.block_len < signal_name_len {
continue;
} else {
block.compute_offset(block.block_len - signal_name_len)
};
reader.seek(SeekFrom::Start(offset))?;
let mut magic_word = [0u8; MAGIC_WORDS.len()];
reader.read_exact(&mut magic_word)?;
if &magic_word == MAGIC_WORDS {
match i.checked_add(1) {
Some(v) => header_blk_cnt = Some(v),
None => return Err(Error::Overflow),
}
break;
}
}
let header_blk_cnt = match header_blk_cnt {
Some(v) => v,
None => return Err(Error::NoMagicWords),
};
// Build result and return.
Ok(HspiceBlocks::new(sim_source, blocks, header_blk_cnt))
}
const SIGNAL_NAME_LEN: usize = 8;
const MAGIC_WORDS: &[u8; 4] = b"$&%#";

View File

@ -71,13 +71,13 @@ pub enum SpiceData {
NgSpice(ngspice::NgSpiceData), NgSpice(ngspice::NgSpiceData),
} }
pub fn load_signal<T>(reader: &mut T) -> Result<SpiceData> pub fn load_signal<T>(mut reader: T) -> Result<SpiceData>
where where
T: ReadBytesExt + Seek, T: ReadBytesExt + Seek,
{ {
if ngspice::guess_signal(reader) { if ngspice::guess_signal(&mut reader) {
Ok(SpiceData::NgSpice(ngspice::load_signal(reader)?)) Ok(SpiceData::NgSpice(ngspice::load_signal(reader)?))
} else if hspice::guess_signal(reader) { } else if hspice::guess_signal(&mut reader) {
Ok(SpiceData::HSpice(hspice::load_signal(reader)?)) Ok(SpiceData::HSpice(hspice::load_signal(reader)?))
} else { } else {
Err(Error::InvalidFmt) Err(Error::InvalidFmt)

View File

@ -25,7 +25,7 @@ where
} }
/// Load given reader as HSPICE signal file. /// Load given reader as HSPICE signal file.
pub fn load_signal<T>(reader: &mut T) -> Result<NgSpiceData> pub fn load_signal<T>(reader: T) -> Result<NgSpiceData>
where where
T: ReadBytesExt + Seek, T: ReadBytesExt + Seek,
{ {

View File

@ -14,11 +14,11 @@ struct LoadSigArgs {
fn main() { fn main() {
let args = LoadSigArgs::parse(); let args = LoadSigArgs::parse();
let mut reader = std::fs::File::open(&args.input).unwrap_or_else(|e| { let reader = std::fs::File::open(&args.input).unwrap_or_else(|e| {
eprintln!("{}", e); eprintln!("{}", e);
std::process::exit(1); std::process::exit(1);
}); });
let _ = loadsig::load_signal(&mut reader).unwrap_or_else(|e| { let _ = loadsig::load_signal(reader).unwrap_or_else(|e| {
eprintln!("{}", e); eprintln!("{}", e);
std::process::exit(2); std::process::exit(2);
}); });