Compare commits
3 Commits
b36902b047
...
71e2ed9dcf
Author | SHA1 | Date | |
---|---|---|---|
71e2ed9dcf | |||
492879e483 | |||
02c232f6cb |
@ -1,5 +1,5 @@
|
|||||||
use super::shared::{self, Endian};
|
use super::shared::{self, Endian};
|
||||||
use byteorder::{BigEndian, LittleEndian, NativeEndian, ReadBytesExt};
|
use byteorder::ReadBytesExt;
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use thiserror::Error as TeError;
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
@ -14,14 +14,20 @@ 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("fail to cast value into integral value")]
|
#[error(
|
||||||
|
"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")]
|
||||||
|
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 0x4000000")]
|
||||||
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),
|
||||||
#[error("wrong block tail value {0:#X}. it must be equal to head given value {1:#X}")]
|
#[error("wrong block tail value {0:#X}. it must be equal to head given value {1:#X}")]
|
||||||
WrongBlockTail(u32, u32),
|
WrongBlockTail(u32, u32),
|
||||||
|
#[error("expected magic words \"$&%#\" is not found at block tail")]
|
||||||
|
NoMagicWords,
|
||||||
}
|
}
|
||||||
type Result<T> = std::result::Result<T, Error>;
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
@ -50,14 +56,217 @@ pub struct HSpiceData {}
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct HspiceBlock {
|
struct HspiceBlock {
|
||||||
block_offset: u64,
|
block_offset: u64,
|
||||||
block_len: usize,
|
block_len: u32,
|
||||||
|
}
|
||||||
|
impl HspiceBlock {
|
||||||
|
pub fn new(block_offset: u64, block_len: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
block_offset,
|
||||||
|
block_len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn compute_offset(&self, relative_offset: u32) -> u64 {
|
||||||
|
if relative_offset >= self.block_len {
|
||||||
|
panic!("relative offset is out of range")
|
||||||
|
} else {
|
||||||
|
self.block_offset + u64::from(relative_offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct HspiceBlocks {
|
struct HspiceBlocks {
|
||||||
sim_source: SimSource,
|
sim_source: SimSource,
|
||||||
blocks: Vec<HspiceBlock>,
|
blocks: Vec<HspiceBlock>,
|
||||||
header_blk_cnt: usize,
|
header_blk_cnt: usize,
|
||||||
data_blk_cnt: usize,
|
}
|
||||||
|
impl HspiceBlocks {
|
||||||
|
pub fn new(sim_source: SimSource, blocks: Vec<HspiceBlock>, header_blk_cnt: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
sim_source,
|
||||||
|
blocks,
|
||||||
|
header_blk_cnt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> HspiceBlocks {
|
||||||
|
pub fn to_reader<T>(&'a self, reader: T) -> HspiceBlocksReader<'a, T>
|
||||||
|
where
|
||||||
|
T: Read + Seek,
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
where
|
||||||
|
T: Read + Seek,
|
||||||
|
{
|
||||||
|
reader: T,
|
||||||
|
blocks: &'a [HspiceBlock],
|
||||||
|
|
||||||
|
/// The full size of this reader
|
||||||
|
total_len: u64,
|
||||||
|
/// The table stored the offset relative to this reader.
|
||||||
|
/// It is convenient for Seek implement.
|
||||||
|
offset_table: Vec<u64>,
|
||||||
|
|
||||||
|
/// The cursor of block index.
|
||||||
|
block_cursor: usize,
|
||||||
|
/// The cursor inside block.
|
||||||
|
cursor: u32,
|
||||||
|
}
|
||||||
|
impl<'a, T> HspiceBlocksReader<'a, T>
|
||||||
|
where
|
||||||
|
T: Read + Seek,
|
||||||
|
{
|
||||||
|
pub fn new(reader: T, blocks: &'a [HspiceBlock]) -> Self {
|
||||||
|
// Build total length and offset table.
|
||||||
|
let mut total_len: u64 = 0;
|
||||||
|
let mut offset_table: Vec<u64> = Vec::with_capacity(blocks.len());
|
||||||
|
for block in blocks {
|
||||||
|
offset_table.push(total_len);
|
||||||
|
total_len += u64::from(block.block_len);
|
||||||
|
}
|
||||||
|
// NOTE: This tail is essential for Seek impl.
|
||||||
|
// otherwise Seek impl will panic when SeekFrom::Current.
|
||||||
|
offset_table.push(total_len);
|
||||||
|
|
||||||
|
HspiceBlocksReader {
|
||||||
|
reader,
|
||||||
|
blocks,
|
||||||
|
total_len,
|
||||||
|
offset_table,
|
||||||
|
block_cursor: 0,
|
||||||
|
cursor: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.reader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T> Read for HspiceBlocksReader<'a, T>
|
||||||
|
where
|
||||||
|
T: Read + Seek,
|
||||||
|
{
|
||||||
|
fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
use std::io::{Error, ErrorKind, Result};
|
||||||
|
|
||||||
|
let old_block_cursor = self.block_cursor;
|
||||||
|
let old_cursor = self.cursor;
|
||||||
|
let mut total_read_count: usize = 0;
|
||||||
|
|
||||||
|
let rv: Result<()> = loop {
|
||||||
|
// Check whether there is space to be filled in buffer.
|
||||||
|
if buf.is_empty() {
|
||||||
|
break Ok(());
|
||||||
|
}
|
||||||
|
// Check whether we reach the tail of block chain.
|
||||||
|
if self.block_cursor >= self.blocks.len() {
|
||||||
|
break Err(Error::new(
|
||||||
|
ErrorKind::UnexpectedEof,
|
||||||
|
"reach the tail of blocks",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// Check whether we need to switch to next block.
|
||||||
|
if self.cursor >= self.blocks[self.block_cursor].block_len {
|
||||||
|
self.block_cursor += 1;
|
||||||
|
self.cursor = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide the read count
|
||||||
|
let remain = self.blocks[self.block_cursor].block_len - self.cursor;
|
||||||
|
let read_count = std::cmp::min(usize::try_from(remain).unwrap(), buf.len());
|
||||||
|
// Read data
|
||||||
|
let offset = self.blocks[self.block_cursor].compute_offset(self.cursor);
|
||||||
|
if let Err(e) = self.reader.seek(SeekFrom::Start(offset)) {
|
||||||
|
break Err(e);
|
||||||
|
}
|
||||||
|
if let Err(e) = self.read_exact(&mut buf[..read_count]) {
|
||||||
|
break Err(e);
|
||||||
|
}
|
||||||
|
// Shift buffer and cursor.
|
||||||
|
buf = &mut buf[read_count..];
|
||||||
|
self.cursor += u32::try_from(read_count).unwrap();
|
||||||
|
// Add count into total counter
|
||||||
|
total_read_count += read_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If fail to read, we restore its original states.
|
||||||
|
if let Err(e) = rv {
|
||||||
|
self.block_cursor = old_block_cursor;
|
||||||
|
self.cursor = old_cursor;
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
|
Ok(total_read_count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T> Seek for HspiceBlocksReader<'a, T>
|
||||||
|
where
|
||||||
|
T: Read + Seek,
|
||||||
|
{
|
||||||
|
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||||
|
use std::io::{Error, ErrorKind};
|
||||||
|
|
||||||
|
let err_instance = Error::new(ErrorKind::UnexpectedEof, "offset is out of range");
|
||||||
|
let reader_cursor = match pos {
|
||||||
|
SeekFrom::Start(v) => {
|
||||||
|
if v > self.total_len {
|
||||||
|
return Err(err_instance);
|
||||||
|
} else {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SeekFrom::End(v) => {
|
||||||
|
if v > 0 {
|
||||||
|
return Err(err_instance);
|
||||||
|
} else {
|
||||||
|
match self.total_len.checked_add_signed(v) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Err(err_instance),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SeekFrom::Current(v) => {
|
||||||
|
let current = self.offset_table[self.block_cursor] + u64::from(self.cursor);
|
||||||
|
match current.checked_add_signed(v) {
|
||||||
|
Some(v) => {
|
||||||
|
if v > self.total_len {
|
||||||
|
return Err(err_instance);
|
||||||
|
} else {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return Err(err_instance),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let block_cursor = match self.offset_table.binary_search(&reader_cursor) {
|
||||||
|
Ok(v) => v,
|
||||||
|
// This unwrap should be safe.
|
||||||
|
// Because there is no possible that every items are greater than given value,
|
||||||
|
// due to that the first item of table must be zero.
|
||||||
|
Err(v) => v.checked_sub(1).unwrap(),
|
||||||
|
};
|
||||||
|
let cursor = reader_cursor - self.offset_table[block_cursor];
|
||||||
|
|
||||||
|
self.block_cursor = block_cursor;
|
||||||
|
self.cursor = u32::try_from(cursor).unwrap();
|
||||||
|
Ok(reader_cursor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Guess whether given reader is a legal HSPICE signal file.
|
/// Guess whether given reader is a legal HSPICE signal file.
|
||||||
@ -70,12 +279,12 @@ where
|
|||||||
T: ReadBytesExt + Seek,
|
T: ReadBytesExt + Seek,
|
||||||
{
|
{
|
||||||
// Back to the head of file.
|
// Back to the head of file.
|
||||||
if let Err(_) = reader.seek(SeekFrom::Start(0)) {
|
if let Err(_) = reader.rewind() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether first u32 is 4 (in little or big endian)
|
// Check whether first u32 is 4 (in little or big endian)
|
||||||
match reader.read_u32::<NativeEndian>() {
|
match shared::read_int_value(Endian::NativeEndian, reader) {
|
||||||
Ok(val) => match val {
|
Ok(val) => match val {
|
||||||
LITTLE_ENDIAN_INT | BIG_ENDIAN_INT => true,
|
LITTLE_ENDIAN_INT | BIG_ENDIAN_INT => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
@ -101,10 +310,10 @@ where
|
|||||||
T: ReadBytesExt + Seek,
|
T: ReadBytesExt + Seek,
|
||||||
{
|
{
|
||||||
// Back to the file head
|
// Back to the file head
|
||||||
reader.seek(SeekFrom::Start(0))?;
|
reader.rewind()?;
|
||||||
|
|
||||||
// Fetch the first u32 as little endian to decide the sim source
|
// Fetch the first u32 as little endian to decide the sim source
|
||||||
let endian_u32 = reader.read_u32::<LittleEndian>()?;
|
let endian_u32 = shared::read_int_value(Endian::LittleEndian, reader)?;
|
||||||
let sim_source = match endian_u32 {
|
let sim_source = match endian_u32 {
|
||||||
LITTLE_ENDIAN_INT => SimSource::HspiceWin,
|
LITTLE_ENDIAN_INT => SimSource::HspiceWin,
|
||||||
BIG_ENDIAN_INT => SimSource::Hspice,
|
BIG_ENDIAN_INT => SimSource::Hspice,
|
||||||
@ -114,7 +323,7 @@ where
|
|||||||
|
|
||||||
// Iterate each block
|
// Iterate each block
|
||||||
let mut blocks: Vec<HspiceBlock> = Vec::new();
|
let mut blocks: Vec<HspiceBlock> = Vec::new();
|
||||||
reader.seek(SeekFrom::Start(0))?;
|
reader.rewind()?;
|
||||||
loop {
|
loop {
|
||||||
// Read block header.
|
// Read block header.
|
||||||
// If there is no more data, break this loop.
|
// If there is no more data, break this loop.
|
||||||
@ -135,35 +344,52 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the offset and length of block body
|
// Get the offset and length of block body
|
||||||
let raw_block_len = block_header[3];
|
let block_offset = reader.stream_position()?;
|
||||||
let block_offset = reader.seek(SeekFrom::Current(0))?;
|
let block_len = block_header[3];
|
||||||
let block_len = usize::try_from(raw_block_len).unwrap();
|
|
||||||
|
|
||||||
// Skip block body to check block tail
|
// Skip block body to check block tail
|
||||||
reader.seek(SeekFrom::Current(i64::try_from(raw_block_len).unwrap()))?;
|
reader.seek(SeekFrom::Current(i64::try_from(block_len).unwrap()))?;
|
||||||
let block_tail = shared::read_int_value(endian, reader)?;
|
let block_tail = shared::read_int_value(endian, reader)?;
|
||||||
if block_tail != raw_block_len {
|
if block_tail != block_len {
|
||||||
return Err(Error::WrongBlockTail(block_tail, raw_block_len));
|
return Err(Error::WrongBlockTail(block_tail, block_len));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push result
|
// Push result
|
||||||
blocks.push(HspiceBlock {
|
blocks.push(HspiceBlock::new(block_offset, block_len));
|
||||||
block_offset,
|
|
||||||
block_len,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cursor now at the beginning of next block.
|
// Cursor now at the beginning of next block.
|
||||||
// We can continue analyze next blocks safely.
|
// We can continue analyze next blocks safely.
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// Analyze header block count
|
// 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.
|
// Build result and return.
|
||||||
Ok(HspiceBlocks {
|
Ok(HspiceBlocks::new(sim_source, blocks, header_blk_cnt))
|
||||||
sim_source,
|
|
||||||
blocks,
|
|
||||||
header_blk_cnt: 0,
|
|
||||||
data_blk_cnt: 0,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SIGNAL_NAME_LEN: usize = 8;
|
||||||
|
const MAGIC_WORDS: &[u8; 4] = b"$&%#";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user