Skip to main content

fst_reader/
io.rs

1// Copyright 2023 The Regents of the University of California
2// Copyright 2024-2026 Cornell University
3// released under BSD 3-Clause License
4// author: Kevin Laeufer <laeufer@cornell.edu>
5// Contains basic read and write operations for FST files.
6
7use crate::FstSignalValue;
8use crate::types::*;
9use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
10use std::cmp::Ordering;
11#[cfg(test)]
12use std::io::Write;
13use std::io::{Read, Seek, SeekFrom};
14use std::num::NonZeroU32;
15use thiserror::Error;
16
17#[derive(Debug, Error)]
18pub enum ReaderError {
19    #[error(
20        "failed to read a null terminated string because it exceeds the expected size of {0} bytes.\n{1}"
21    )]
22    CStringTooLong(usize, String),
23    #[error("failed to parse an enum table string: {0}\n{1}")]
24    EnumTableString(String, String),
25    #[error("failed to read leb128 integer, more than the expected {0} bits")]
26    Leb128(u32),
27    #[error("failed to parse an integer")]
28    ParseInt(#[from] std::num::ParseIntError),
29    #[error("failed to decompress with lz4")]
30    Lz4Decompress(#[from] lz4_flex::block::DecompressError),
31    #[error("failed to decompress with zlib")]
32    ZLibDecompress(#[from] miniz_oxide::inflate::DecompressError),
33    #[error("failed to parse a gzip header: {0}")]
34    GZipHeader(String),
35    #[error("failed to decompress gzip stream: {0}")]
36    GZipBody(String),
37    #[error("failed to decode string")]
38    Utf8(#[from] std::str::Utf8Error),
39    #[error("failed to decode string")]
40    Utf8String(#[from] std::string::FromUtf8Error),
41    #[error("I/O operation failed")]
42    Io(#[from] std::io::Error),
43    #[error("The FST file is still being compressed into its final GZIP wrapper.")]
44    NotFinishedCompressing(),
45    #[error("Unexpected block type")]
46    BlockType(#[from] TryFromPrimitiveError<BlockType>),
47    #[error("Unexpected file type")]
48    FileType(#[from] TryFromPrimitiveError<FileType>),
49    #[error("Unexpected vhdl variable type")]
50    FstVhdlVarType(#[from] TryFromPrimitiveError<FstVhdlVarType>),
51    #[error("Unexpected vhdl data type")]
52    FstVhdlDataType(#[from] TryFromPrimitiveError<FstVhdlDataType>),
53    #[error("Unexpected variable type")]
54    FstVarType(#[from] TryFromPrimitiveError<FstVarType>),
55    #[error("Unexpected scope type")]
56    FstScopeType(#[from] TryFromPrimitiveError<FstScopeType>),
57    #[error("Unexpected variable direction")]
58    FstVarDirection(#[from] TryFromPrimitiveError<FstVarDirection>),
59    #[error("Unexpected attribute type")]
60    AttributeType(#[from] TryFromPrimitiveError<AttributeType>),
61    #[error("Unexpected misc attribute type")]
62    MiscType(#[from] TryFromPrimitiveError<MiscType>),
63    #[error("Unexpected array type")]
64    ArrayType(#[from] TryFromPrimitiveError<FstArrayType>),
65    #[error("Unexpected sv enum type")]
66    EnumType(#[from] TryFromPrimitiveError<FstEnumType>),
67    #[error("Unexpected pack type")]
68    PackType(#[from] TryFromPrimitiveError<FstPackType>),
69    #[error("The FST file is incomplete: geometry block is missing.")]
70    MissingGeometry(),
71    #[error("The FST file is incomplete: hierarchy block is missing.")]
72    MissingHierarchy(),
73}
74
75pub type ReadResult<T> = Result<T, ReaderError>;
76
77#[cfg(test)]
78pub type WriteResult<T> = Result<T, ReaderError>;
79
80//////////////// Primitives
81
82#[inline]
83pub(crate) fn read_variant_u32(input: &mut impl Read) -> ReadResult<(u32, u32)> {
84    let mut byte = [0u8; 1];
85    let mut res = 0u32;
86    // 32bit / 7bit = ~4.6
87    for ii in 0..5u32 {
88        input.read_exact(&mut byte)?;
89        let value = (byte[0] as u32) & 0x7f;
90        res |= value << (7 * ii);
91        if (byte[0] & 0x80) == 0 {
92            return Ok((res, ii + 1));
93        }
94    }
95    Err(ReaderError::Leb128(32))
96}
97
98#[inline]
99pub(crate) fn read_variant_i64(input: &mut impl Read) -> ReadResult<i64> {
100    let mut byte = [0u8; 1];
101    let mut res = 0u64;
102    // 64bit / 7bit = ~9.1
103    for ii in 0..10 {
104        input.read_exact(&mut byte)?;
105        let value = (byte[0] & 0x7f) as u64;
106        let shift_by = 7 * ii;
107        res |= value << shift_by;
108        if (byte[0] & 0x80) == 0 {
109            // sign extend
110            let sign_bit_set = (byte[0] & 0x40) != 0;
111            if (shift_by + 7) < u64::BITS && sign_bit_set {
112                res |= u64::MAX << (shift_by + 7);
113            }
114            return Ok(res as i64);
115        }
116    }
117    Err(ReaderError::Leb128(64))
118}
119
120#[inline]
121pub(crate) fn read_variant_u64(input: &mut impl Read) -> ReadResult<(u64, usize)> {
122    let mut byte = [0u8; 1];
123    let mut res = 0u64;
124    for ii in 0..10 {
125        // 64bit / 7bit = ~9.1
126        input.read_exact(&mut byte)?;
127        let value = (byte[0] as u64) & 0x7f;
128        res |= value << (7 * ii);
129        if (byte[0] & 0x80) == 0 {
130            return Ok((res, ii + 1));
131        }
132    }
133    Err(ReaderError::Leb128(64))
134}
135
136#[cfg(test)]
137#[inline]
138pub(crate) fn write_variant_u64(output: &mut impl Write, mut value: u64) -> WriteResult<usize> {
139    // often, the value is small
140    if value <= 0x7f {
141        let byte = [value as u8; 1];
142        output.write_all(&byte)?;
143        return Ok(1);
144    }
145
146    let mut bytes = Vec::with_capacity(10);
147    while value != 0 {
148        let next_value = value >> 7;
149        let mask: u8 = if next_value == 0 { 0 } else { 0x80 };
150        bytes.push((value & 0x7f) as u8 | mask);
151        value = next_value;
152    }
153    assert!(bytes.len() <= 10);
154    output.write_all(&bytes)?;
155    Ok(bytes.len())
156}
157
158#[cfg(test)]
159#[inline]
160pub(crate) fn write_variant_i64(output: &mut impl Write, mut value: i64) -> WriteResult<usize> {
161    // often, the value is small
162    if value <= 63 && value >= -64 {
163        let byte = [value as u8 & 0x7f; 1];
164        output.write_all(&byte)?;
165        return Ok(1);
166    }
167
168    // calculate the number of bits we need to represent
169    let bits = if value >= 0 {
170        64 - value.leading_zeros() + 1
171    } else {
172        64 - value.leading_ones() + 1
173    };
174    let num_bytes = bits.div_ceil(7) as usize;
175
176    let mut bytes = Vec::with_capacity(num_bytes);
177    for ii in 0..num_bytes {
178        let mark = if ii == num_bytes - 1 { 0 } else { 0x80 };
179        bytes.push((value & 0x7f) as u8 | mark);
180        value >>= 7;
181    }
182    output.write_all(&bytes)?;
183    Ok(bytes.len())
184}
185
186#[cfg(test)]
187#[inline]
188pub(crate) fn write_variant_u32(output: &mut impl Write, value: u32) -> WriteResult<usize> {
189    write_variant_u64(output, value as u64)
190}
191
192#[inline]
193pub(crate) fn read_u64(input: &mut impl Read) -> ReadResult<u64> {
194    let mut buf = [0u8; 8];
195    input.read_exact(&mut buf)?;
196    Ok(u64::from_be_bytes(buf))
197}
198
199#[cfg(test)]
200#[inline]
201pub(crate) fn write_u64(output: &mut impl Write, value: u64) -> WriteResult<()> {
202    let buf = value.to_be_bytes();
203    output.write_all(&buf)?;
204    Ok(())
205}
206
207#[inline]
208pub(crate) fn read_u8(input: &mut impl Read) -> ReadResult<u8> {
209    let mut buf = [0u8; 1];
210    input.read_exact(&mut buf)?;
211    Ok(buf[0])
212}
213
214#[cfg(test)]
215fn write_u8(output: &mut impl Write, value: u8) -> WriteResult<()> {
216    let buf = value.to_be_bytes();
217    output.write_all(&buf)?;
218    Ok(())
219}
220
221#[inline]
222pub(crate) fn read_i8(input: &mut impl Read) -> ReadResult<i8> {
223    let mut buf = [0u8; 1];
224    input.read_exact(&mut buf)?;
225    Ok(i8::from_be_bytes(buf))
226}
227
228#[cfg(test)]
229#[inline]
230fn write_i8(output: &mut impl Write, value: i8) -> WriteResult<()> {
231    let buf = value.to_be_bytes();
232    output.write_all(&buf)?;
233    Ok(())
234}
235
236pub(crate) fn read_c_str(input: &mut impl Read, max_len: usize) -> ReadResult<String> {
237    let mut bytes: Vec<u8> = Vec::with_capacity(32);
238    for _ in 0..max_len {
239        let byte = read_u8(input)?;
240        if byte == 0 {
241            return Ok(String::from_utf8(bytes)?);
242        } else {
243            bytes.push(byte);
244        }
245    }
246    Err(ReaderError::CStringTooLong(
247        max_len,
248        String::from_utf8_lossy(&bytes).to_string(),
249    ))
250}
251
252#[cfg(test)]
253fn write_c_str(output: &mut impl Write, value: &str) -> WriteResult<()> {
254    let bytes = value.as_bytes();
255    output.write_all(bytes)?;
256    write_u8(output, 0)?;
257    Ok(())
258}
259
260#[inline] // inline to specialize on length
261pub(crate) fn read_c_str_fixed_length(input: &mut impl Read, len: usize) -> ReadResult<String> {
262    let mut bytes = read_bytes(input, len)?;
263    let zero_index = bytes.iter().position(|b| *b == 0u8).unwrap_or(len - 1);
264    let str_len = zero_index;
265    bytes.truncate(str_len);
266    Ok(String::from_utf8(bytes)?)
267}
268
269#[cfg(test)]
270#[cfg(test)]
271#[inline]
272fn write_c_str_fixed_length(
273    output: &mut impl Write,
274    value: &str,
275    max_len: usize,
276) -> WriteResult<()> {
277    let bytes = value.as_bytes();
278    if bytes.len() >= max_len {
279        todo!("Return error.")
280    }
281    output.write_all(bytes)?;
282    let zeros = vec![0u8; max_len - bytes.len()];
283    output.write_all(&zeros)?;
284    Ok(())
285}
286
287const RCV_STR: [u8; 8] = [b'x', b'z', b'h', b'u', b'w', b'l', b'-', b'?'];
288#[inline]
289pub(crate) fn one_bit_signal_value_to_char(vli: u32) -> u8 {
290    if (vli & 1) == 0 {
291        (((vli >> 1) & 1) as u8) | b'0'
292    } else {
293        RCV_STR[((vli >> 1) & 7) as usize]
294    }
295}
296
297/// Decodes a digital (1/0) signal. This is indicated by bit0 in vli being cleared.
298#[inline]
299pub(crate) fn multi_bit_digital_signal_to_chars(bytes: &[u8], len: usize, output: &mut Vec<u8>) {
300    output.resize(len, 0);
301    for (ii, out) in output.iter_mut().enumerate() {
302        let byte_id = ii / 8;
303        let bit_id = 7 - (ii & 7);
304        let bit = (bytes[byte_id] >> bit_id) & 1;
305        *out = bit | b'0';
306    }
307}
308
309pub(crate) fn read_one_bit_signal_time_delta(bytes: &[u8], offset: u32) -> ReadResult<usize> {
310    let mut slice = &bytes[(offset as usize)..];
311    let (vli, _) = read_variant_u32(&mut slice)?;
312    let shift_count = 2u32 << (vli & 1);
313    Ok((vli >> shift_count) as usize)
314}
315
316pub(crate) fn read_multi_bit_signal_time_delta(bytes: &[u8], offset: u32) -> ReadResult<usize> {
317    let mut slice = &bytes[(offset as usize)..];
318    let (vli, _) = read_variant_u32(&mut slice)?;
319    Ok((vli >> 1) as usize)
320}
321
322/// Reads ZLib compressed bytes.
323pub(crate) fn read_zlib_compressed_bytes(
324    input: &mut (impl Read + Seek),
325    uncompressed_length: u64,
326    compressed_length: u64,
327    allow_uncompressed: bool,
328) -> ReadResult<Vec<u8>> {
329    let bytes = if uncompressed_length == compressed_length && allow_uncompressed {
330        read_bytes(input, compressed_length as usize)?
331    } else {
332        let start = input.stream_position()?;
333
334        // read first byte to check which compression is used.
335        let first_byte = read_u8(input)?;
336        input.seek(SeekFrom::Start(start))?;
337        // for zlib compression, the first byte should be 0x78
338        let is_zlib = first_byte == 0x78;
339        debug_assert!(is_zlib, "expected a zlib compressed block!");
340
341        let compressed = read_bytes(input, compressed_length as usize)?;
342
343        miniz_oxide::inflate::decompress_to_vec_zlib_with_limit(
344            compressed.as_slice(),
345            uncompressed_length as usize,
346        )?
347    };
348    assert_eq!(bytes.len(), uncompressed_length as usize);
349    Ok(bytes)
350}
351
352/// ZLib compresses bytes. If allow_uncompressed is true, we overwrite the compressed with the
353/// uncompressed bytes if it turns out that the compressed bytes are longer.
354#[cfg(test)]
355pub(crate) fn write_compressed_bytes(
356    output: &mut (impl Write + Seek),
357    bytes: &[u8],
358    compression_level: u8,
359    allow_uncompressed: bool,
360) -> WriteResult<usize> {
361    let compressed = miniz_oxide::deflate::compress_to_vec_zlib(bytes, compression_level);
362    if !allow_uncompressed || compressed.len() < bytes.len() {
363        output.write_all(compressed.as_slice())?;
364        Ok(compressed.len())
365    } else {
366        // it turns out that the compression was futile!
367        output.write_all(bytes)?;
368        Ok(bytes.len())
369    }
370}
371
372#[inline]
373pub(crate) fn read_bytes(input: &mut impl Read, len: usize) -> ReadResult<Vec<u8>> {
374    let mut buf: Vec<u8> = Vec::with_capacity(len);
375    input.take(len as u64).read_to_end(&mut buf)?;
376    Ok(buf)
377}
378
379pub(crate) fn read_block_tpe(input: &mut impl Read) -> ReadResult<BlockType> {
380    Ok(BlockType::try_from(read_u8(input)?)?)
381}
382
383pub(crate) fn determine_f64_endian(
384    input: &mut impl Read,
385    needle: f64,
386) -> ReadResult<FloatingPointEndian> {
387    let bytes = read_bytes(input, 8)?;
388    let mut byte_reader: &[u8] = &bytes;
389    let le = read_f64(&mut byte_reader, FloatingPointEndian::Little)?;
390    if le == needle {
391        return Ok(FloatingPointEndian::Little);
392    }
393    byte_reader = &bytes;
394    let be = read_f64(&mut byte_reader, FloatingPointEndian::Big)?;
395    if be == needle {
396        Ok(FloatingPointEndian::Big)
397    } else {
398        todo!("should not get here")
399    }
400}
401
402#[inline]
403pub(crate) fn read_f64(input: &mut impl Read, endian: FloatingPointEndian) -> ReadResult<f64> {
404    let mut buf = [0u8; 8];
405    input.read_exact(&mut buf)?;
406    match endian {
407        FloatingPointEndian::Little => Ok(f64::from_le_bytes(buf)),
408        FloatingPointEndian::Big => Ok(f64::from_be_bytes(buf)),
409    }
410}
411
412#[cfg(test)]
413#[inline]
414fn write_f64(output: &mut impl Write, value: f64) -> WriteResult<()> {
415    // for f64, we have the option to use either LE or BE, we just need to be consistent
416    let buf = value.to_le_bytes();
417    output.write_all(&buf)?;
418    Ok(())
419}
420
421fn read_lz4_compressed_bytes(
422    input: &mut impl Read,
423    uncompressed_length: usize,
424    compressed_length: usize,
425) -> ReadResult<Vec<u8>> {
426    let compressed = read_bytes(input, compressed_length)?;
427    let bytes = lz4_flex::decompress(&compressed, uncompressed_length)?;
428    Ok(bytes)
429}
430
431//////////////// Header
432
433const HEADER_LENGTH: u64 = 329;
434const HEADER_VERSION_MAX_LEN: usize = 128;
435const HEADER_DATE_MAX_LEN: usize = 119;
436pub(crate) fn read_header(input: &mut impl Read) -> ReadResult<(Header, FloatingPointEndian)> {
437    let section_length = read_u64(input)?;
438    assert_eq!(section_length, HEADER_LENGTH);
439    let start_time = read_u64(input)?;
440    let end_time = read_u64(input)?;
441    let float_endian = determine_f64_endian(input, DOUBLE_ENDIAN_TEST)?;
442    let memory_used_by_writer = read_u64(input)?;
443    let scope_count = read_u64(input)?;
444    let var_count = read_u64(input)?;
445    let max_var_id_code = read_u64(input)?;
446    let vc_section_count = read_u64(input)?;
447    let timescale_exponent = read_i8(input)?;
448    let version = read_c_str_fixed_length(input, HEADER_VERSION_MAX_LEN)?;
449    // this size was reduced compared to what is documented in block_format.txt
450    let date = read_c_str_fixed_length(input, HEADER_DATE_MAX_LEN)?;
451    let file_type = FileType::try_from(read_u8(input)?)?;
452    let time_zero = read_u64(input)?;
453
454    let header = Header {
455        start_time,
456        end_time,
457        memory_used_by_writer,
458        scope_count,
459        var_count,
460        max_var_id_code,
461        vc_section_count,
462        timescale_exponent,
463        version,
464        date,
465        file_type,
466        time_zero,
467    };
468    Ok((header, float_endian))
469}
470
471#[cfg(test)]
472pub(crate) fn write_header(output: &mut impl Write, header: &Header) -> WriteResult<()> {
473    write_u64(output, HEADER_LENGTH)?;
474    write_u64(output, header.start_time)?;
475    write_u64(output, header.end_time)?;
476    write_f64(output, DOUBLE_ENDIAN_TEST)?;
477    write_u64(output, header.memory_used_by_writer)?;
478    write_u64(output, header.scope_count)?;
479    write_u64(output, header.var_count)?;
480    write_u64(output, header.max_var_id_code)?;
481    write_u64(output, header.vc_section_count)?;
482    write_i8(output, header.timescale_exponent)?;
483    write_c_str_fixed_length(output, &header.version, HEADER_VERSION_MAX_LEN)?;
484    write_c_str_fixed_length(output, &header.date, HEADER_DATE_MAX_LEN)?;
485    write_u8(output, header.file_type as u8)?;
486    write_u64(output, header.time_zero)?;
487    Ok(())
488}
489
490//////////////// Geometry
491
492pub(crate) fn read_geometry(input: &mut (impl Read + Seek)) -> ReadResult<Vec<SignalInfo>> {
493    let section_length = read_u64(input)?;
494    let uncompressed_length = read_u64(input)?;
495    let max_handle = read_u64(input)?;
496    let compressed_length = section_length - 3 * 8;
497
498    let bytes = read_zlib_compressed_bytes(input, uncompressed_length, compressed_length, true)?;
499
500    let mut signals: Vec<SignalInfo> = Vec::with_capacity(max_handle as usize);
501    let mut byte_reader: &[u8] = &bytes;
502
503    for _ii in 0..max_handle {
504        let (value, _) = read_variant_u32(&mut byte_reader)?;
505        signals.push(SignalInfo::from_file_format(value));
506    }
507    Ok(signals)
508}
509
510#[cfg(test)]
511pub(crate) fn write_geometry(
512    output: &mut (impl Write + Seek),
513    signals: &Vec<SignalInfo>,
514    compression: u8,
515) -> WriteResult<()> {
516    // remember start to fix the section length afterwards
517    let start = output.stream_position()?;
518    write_u64(output, 0)?; // dummy section length
519
520    // write uncompressed signal info
521    let mut bytes: Vec<u8> = Vec::with_capacity(signals.len() * 2);
522    for signal in signals {
523        write_variant_u64(&mut bytes, signal.to_file_format() as u64)?;
524    }
525    let uncompressed_length = bytes.len() as u64;
526    write_u64(output, uncompressed_length)?;
527    let max_handle = signals.len() as u64;
528    write_u64(output, max_handle)?;
529
530    // compress signals
531    let compressed_len = write_compressed_bytes(output, &bytes, compression, true)? as u64;
532
533    // fix section length
534    let section_length = compressed_len + 3 * 8;
535    let end = output.stream_position()?;
536    output.seek(SeekFrom::Start(start))?;
537    write_u64(output, section_length)?;
538    output.seek(SeekFrom::Start(end))?;
539
540    Ok(())
541}
542
543//////////////// Blackout
544
545pub(crate) fn read_blackout(input: &mut (impl Read + Seek)) -> ReadResult<Vec<BlackoutData>> {
546    // remember start for later sanity check
547    let start = input.stream_position()?;
548    let section_length = read_u64(input)?;
549    let (num_blackouts, _) = read_variant_u32(input)?;
550    let mut blackouts = Vec::with_capacity(num_blackouts as usize);
551    let mut current_blackout = 0u64;
552    for _ in 0..num_blackouts {
553        let activity = read_u8(input)? != 0;
554        let (delta, _) = read_variant_u64(input)?;
555        current_blackout += delta;
556        let bo = BlackoutData {
557            time: current_blackout,
558            contains_activity: activity,
559        };
560        blackouts.push(bo);
561    }
562    let end = input.stream_position()?;
563    assert_eq!(start + section_length, end);
564    Ok(blackouts)
565}
566
567#[cfg(test)]
568pub(crate) fn write_blackout(
569    output: &mut (impl Write + Seek),
570    blackouts: &[BlackoutData],
571) -> WriteResult<()> {
572    // remember start to fix the section length afterwards
573    let start = output.stream_position()?;
574    write_u64(output, 0)?; // dummy section length
575
576    let num_blackouts = blackouts.len() as u32;
577    write_variant_u32(output, num_blackouts)?;
578
579    let mut last_blackout = 0u64;
580    for blackout in blackouts {
581        let activity_byte = if blackout.contains_activity { 1 } else { 0 };
582        write_u8(output, activity_byte)?;
583        let delta = blackout.time - last_blackout;
584        last_blackout = blackout.time;
585        write_variant_u64(output, delta)?;
586    }
587
588    // fix section length
589    let end = output.stream_position()?;
590    output.seek(SeekFrom::Start(start))?;
591    write_u64(output, end - start)?;
592    output.seek(SeekFrom::Start(end))?;
593
594    Ok(())
595}
596
597//////////////// Hierarchy
598#[cfg(test)]
599const HIERARCHY_GZIP_COMPRESSION_LEVEL: u8 = 4;
600
601/// uncompresses zlib compressed bytes with a gzip header
602fn read_gzip_compressed_bytes(
603    input: &mut impl Read,
604    uncompressed_len: usize,
605    compressed_len: usize,
606) -> ReadResult<Vec<u8>> {
607    read_gzip_header(input)?;
608    // we do not care about other header bytes
609    let data = read_bytes(input, compressed_len - 10)?;
610    let uncompressed =
611        miniz_oxide::inflate::decompress_to_vec_with_limit(data.as_slice(), uncompressed_len)?;
612    debug_assert_eq!(uncompressed.len(), uncompressed_len);
613    Ok(uncompressed)
614}
615
616pub(crate) fn read_gzip_header(input: &mut impl Read) -> ReadResult<()> {
617    let header = read_bytes(input, 10)?;
618    let correct_magic = header[0] == 0x1f && header[1] == 0x8b;
619    if !correct_magic {
620        return Err(ReaderError::GZipHeader(format!(
621            "expected magic bytes (0x1f, 0x8b) got {header:x?}"
622        )));
623    }
624    let is_deflate_compressed = header[2] == 8;
625    if !is_deflate_compressed {
626        return Err(ReaderError::GZipHeader(format!(
627            "expected deflate compression (8) got {:x?}",
628            header[2]
629        )));
630    }
631    let flag = header[3];
632    if flag != 0 {
633        return Err(ReaderError::GZipHeader(format!(
634            "TODO currently extra flags are not supported {flag}"
635        )));
636    }
637    Ok(())
638}
639
640pub(crate) fn read_hierarchy_bytes(
641    input: &mut (impl Read + Seek),
642    compression: HierarchyCompression,
643) -> ReadResult<Vec<u8>> {
644    Ok(match compression {
645        HierarchyCompression::Uncompressed => {
646            let mut buf: Vec<u8> = Vec::new();
647            input.read_to_end(&mut buf)?;
648            buf
649        }
650        _ => {
651            let section_length = read_u64(input)? as usize;
652            let uncompressed_length = read_u64(input)? as usize;
653            let compressed_length = section_length - 2 * 8;
654            let bytes = match compression {
655                HierarchyCompression::Uncompressed => unreachable!(),
656                HierarchyCompression::ZLib => {
657                    read_gzip_compressed_bytes(input, uncompressed_length, compressed_length)?
658                }
659                HierarchyCompression::Lz4 => {
660                    read_lz4_compressed_bytes(input, uncompressed_length, compressed_length)?
661                }
662                HierarchyCompression::Lz4Duo => {
663                    // the length after the _first_ decompression
664                    let (len, skiplen) = read_variant_u64(input)?;
665                    let lvl1_len = len as usize;
666                    let lvl1 =
667                        read_lz4_compressed_bytes(input, lvl1_len, compressed_length - skiplen)?;
668                    let mut lvl1_reader = lvl1.as_slice();
669                    read_lz4_compressed_bytes(&mut lvl1_reader, uncompressed_length, lvl1_len)?
670                }
671            };
672            assert_eq!(bytes.len(), uncompressed_length);
673            bytes
674        }
675    })
676}
677
678#[cfg(test)]
679const GZIP_HEADER: [u8; 10] = [
680    0x1f, 0x8b, // magic bytes
681    8,    // using deflate
682    0,    // no flags
683    0, 0, 0, 0,   // timestamp = 0
684    0,   // compression level (does not really matter)
685    255, // OS set to 255 by default
686];
687
688/// writes zlib compressed bytes with a gzip header
689#[cfg(test)]
690pub(crate) fn write_gzip_compressed_bytes(
691    output: &mut impl Write,
692    bytes: &[u8],
693    compression_level: u8,
694) -> ReadResult<()> {
695    output.write_all(GZIP_HEADER.as_slice())?;
696    let compressed = miniz_oxide::deflate::compress_to_vec(bytes, compression_level);
697    output.write_all(compressed.as_slice())?;
698    Ok(())
699}
700
701#[cfg(test)]
702pub(crate) fn write_hierarchy_bytes(
703    output: &mut (impl Write + Seek),
704    compression: HierarchyCompression,
705    bytes: &[u8],
706) -> WriteResult<()> {
707    match compression {
708        HierarchyCompression::Uncompressed => {
709            output.write_all(bytes)?;
710        }
711        _ => {
712            // remember start to fix the section length afterwards
713            let start = output.stream_position()?;
714            write_u64(output, 0)?; // dummy section length
715            let uncompressed_length = bytes.len() as u64;
716            write_u64(output, uncompressed_length)?;
717
718            match compression {
719                HierarchyCompression::Uncompressed => unreachable!(),
720                HierarchyCompression::ZLib => {
721                    write_gzip_compressed_bytes(output, bytes, HIERARCHY_GZIP_COMPRESSION_LEVEL)?;
722                }
723                HierarchyCompression::Lz4 => {
724                    let compressed = lz4_flex::compress(bytes);
725                    output.write_all(&compressed)?;
726                }
727                HierarchyCompression::Lz4Duo => {
728                    let compressed_lvl1 = lz4_flex::compress(bytes);
729                    let lvl1_len = compressed_lvl1.len() as u64;
730                    write_variant_u64(output, lvl1_len)?;
731                    let compressed_lvl2 = lz4_flex::compress(&compressed_lvl1);
732                    output.write_all(&compressed_lvl2)?;
733                }
734            };
735
736            // fix section length
737            let end = output.stream_position()?;
738            output.seek(SeekFrom::Start(start))?;
739            write_u64(output, end - start)?;
740            output.seek(SeekFrom::Start(end))?;
741        }
742    };
743
744    Ok(())
745}
746
747fn enum_table_from_string(value: String, handle: u64) -> ReadResult<FstHierarchyEntry> {
748    let parts: Vec<&str> = value.split(' ').collect();
749    if parts.len() < 2 {
750        return Err(ReaderError::EnumTableString(
751            "not enough spaces".to_string(),
752            value,
753        ));
754    }
755    let name = parts[0].to_string();
756    let element_count = parts[1].parse::<usize>()?;
757    let expected_part_len = element_count * 2;
758    if parts.len() - 2 != expected_part_len {
759        return Err(ReaderError::EnumTableString(
760            format!(
761                "expected {} parts got {}",
762                expected_part_len,
763                parts.len() - 2
764            ),
765            value,
766        ));
767    }
768    let mut mapping = Vec::with_capacity(element_count);
769    for ii in 0..element_count {
770        let name = parts[2 + ii].to_string();
771        let value = parts[2 + element_count + ii].to_string();
772        mapping.push((value, name));
773    }
774    // TODO: deal with correct de-escaping
775    Ok(FstHierarchyEntry::EnumTable {
776        name,
777        handle,
778        mapping,
779    })
780}
781
782#[cfg(test)]
783fn enum_table_to_string(name: &str, mapping: &[(String, String)]) -> String {
784    let mut out = String::with_capacity(name.len() + mapping.len() * 32 + 32);
785    out.push_str(name);
786    out.push(' ');
787    out.push_str(&format!("{}", mapping.len()));
788    for (_value, name) in mapping {
789        out.push(' ');
790        out.push_str(name);
791    }
792    for (value, _name) in mapping {
793        out.push(' ');
794        out.push_str(value);
795    }
796    out
797}
798
799const FST_SUP_VAR_DATA_TYPE_BITS: u32 = 10;
800const FST_SUP_VAR_DATA_TYPE_MASK: u64 = (1 << FST_SUP_VAR_DATA_TYPE_BITS) - 1;
801
802fn parse_misc_attribute(
803    name: String,
804    tpe: MiscType,
805    arg: u64,
806    arg2: Option<u64>,
807) -> ReadResult<FstHierarchyEntry> {
808    let res = match tpe {
809        MiscType::Comment => FstHierarchyEntry::Comment { string: name },
810        MiscType::EnvVar => todo!("EnvVar Attribute"), // fstWriterSetEnvVar()
811        MiscType::SupVar => {
812            // This attribute supplies VHDL specific information and is used by GHDL
813            let var_type = (arg >> FST_SUP_VAR_DATA_TYPE_BITS) as u8;
814            let data_type = (arg & FST_SUP_VAR_DATA_TYPE_MASK) as u8;
815            FstHierarchyEntry::VhdlVarInfo {
816                type_name: name,
817                var_type: FstVhdlVarType::try_from_primitive(var_type)?,
818                data_type: FstVhdlDataType::try_from_primitive(data_type)?,
819            }
820        }
821        MiscType::PathName => FstHierarchyEntry::PathName { name, id: arg },
822        MiscType::SourceStem => FstHierarchyEntry::SourceStem {
823            is_instantiation: false,
824            path_id: arg2.unwrap(),
825            line: arg,
826        },
827        MiscType::SourceInstantiationStem => FstHierarchyEntry::SourceStem {
828            is_instantiation: true,
829            path_id: arg2.unwrap(),
830            line: arg,
831        },
832        MiscType::ValueList => todo!("ValueList Attribute"), // fstWriterSetValueList()
833        MiscType::EnumTable => {
834            if name.is_empty() {
835                FstHierarchyEntry::EnumTableRef { handle: arg }
836            } else {
837                enum_table_from_string(name, arg)?
838            }
839        }
840        MiscType::Unknown => todo!("unknown Attribute"),
841    };
842    Ok(res)
843}
844
845fn read_hierarchy_attribute_arg2_encoded_as_name(input: &mut impl Read) -> ReadResult<u64> {
846    let (value, _) = read_variant_u64(input)?;
847    let end_byte = read_u8(input)?;
848    assert_eq!(end_byte, 0, "expected to be zero terminated!");
849    Ok(value)
850}
851
852const HIERARCHY_TPE_VCD_SCOPE: u8 = 254;
853const HIERARCHY_TPE_VCD_UP_SCOPE: u8 = 255;
854const HIERARCHY_TPE_VCD_ATTRIBUTE_BEGIN: u8 = 252;
855const HIERARCHY_TPE_VCD_ATTRIBUTE_END: u8 = 253;
856
857pub(crate) fn read_hierarchy_entry(
858    input: &mut impl Read,
859    handle_count: &mut u32,
860) -> ReadResult<Option<FstHierarchyEntry>> {
861    let entry_tpe = match read_u8(input) {
862        Ok(tpe) => tpe,
863        Err(_) => return Ok(None),
864    };
865    let entry = match entry_tpe {
866        HIERARCHY_TPE_VCD_SCOPE => {
867            // VcdScope (ScopeType)
868            let tpe = FstScopeType::try_from_primitive(read_u8(input)?)?;
869            let name = read_c_str(input, HIERARCHY_NAME_MAX_SIZE)?;
870            let component = read_c_str(input, HIERARCHY_NAME_MAX_SIZE)?;
871            FstHierarchyEntry::Scope {
872                tpe,
873                name,
874                component,
875            }
876        }
877        0..=29 => {
878            // VcdEvent ... SvShortReal (VariableType)
879            let tpe = FstVarType::try_from_primitive(entry_tpe)?;
880            let direction = FstVarDirection::try_from_primitive(read_u8(input)?)?;
881            let name = read_c_str(input, HIERARCHY_NAME_MAX_SIZE)?;
882            let (raw_length, _) = read_variant_u32(input)?;
883            let length = if tpe == FstVarType::Port {
884                // remove delimiting spaces and adjust signal size
885                (raw_length - 2) / 3
886            } else {
887                raw_length
888            };
889            let (alias, _) = read_variant_u32(input)?;
890            let (is_alias, handle) = if alias == 0 {
891                *handle_count += 1;
892                (false, FstSignalHandle::new(*handle_count))
893            } else {
894                (true, FstSignalHandle::new(alias))
895            };
896            FstHierarchyEntry::Var {
897                tpe,
898                direction,
899                name,
900                length,
901                handle,
902                is_alias,
903            }
904        }
905        HIERARCHY_TPE_VCD_UP_SCOPE => {
906            // VcdUpScope (ScopeType)
907            FstHierarchyEntry::UpScope
908        }
909        HIERARCHY_TPE_VCD_ATTRIBUTE_BEGIN => {
910            let tpe = AttributeType::try_from_primitive(read_u8(input)?)?;
911            match tpe {
912                AttributeType::Misc => {
913                    let subtype = MiscType::try_from_primitive(read_u8(input)?)?;
914                    let (name, arg2) = match subtype {
915                        MiscType::SourceStem | MiscType::SourceInstantiationStem => {
916                            let arg2 = read_hierarchy_attribute_arg2_encoded_as_name(input)?;
917                            ("".to_string(), Some(arg2))
918                        }
919                        _ => {
920                            let name = read_c_str(input, HIERARCHY_ATTRIBUTE_MAX_SIZE)?;
921                            (name, None)
922                        }
923                    };
924                    let (arg, _) = read_variant_u64(input)?;
925                    parse_misc_attribute(name, subtype, arg, arg2)?
926                }
927                AttributeType::Array => {
928                    let array_type = FstArrayType::try_from_primitive(read_u8(input)?)?;
929                    let name = read_c_str(input, HIERARCHY_NAME_MAX_SIZE)?;
930                    let (left_right, _) = read_variant_u64(input)?;
931                    let mask32 = u32::MAX as u64;
932                    let left = ((left_right >> 32) & mask32) as i32;
933                    let right = (left_right & mask32) as i32;
934                    FstHierarchyEntry::Array {
935                        name,
936                        array_type,
937                        left,
938                        right,
939                    }
940                }
941                AttributeType::Enum => {
942                    let enum_type = FstEnumType::try_from_primitive(read_u8(input)?)?;
943                    let name = read_c_str(input, HIERARCHY_NAME_MAX_SIZE)?;
944                    let (value, _) = read_variant_u64(input)?;
945
946                    FstHierarchyEntry::SVEnum {
947                        name,
948                        enum_type,
949                        value,
950                    }
951                }
952                AttributeType::Pack => {
953                    let pack_type = FstPackType::try_from_primitive(read_u8(input)?)?;
954                    let name = read_c_str(input, HIERARCHY_NAME_MAX_SIZE)?;
955                    let (value, _) = read_variant_u64(input)?;
956
957                    FstHierarchyEntry::Pack {
958                        name,
959                        pack_type,
960                        value,
961                    }
962                }
963            }
964        }
965        HIERARCHY_TPE_VCD_ATTRIBUTE_END => {
966            // GenAttributeEnd (ScopeType)
967            FstHierarchyEntry::AttributeEnd
968        }
969
970        other => todo!("Deal with hierarchy entry of type: {other}"),
971    };
972
973    Ok(Some(entry))
974}
975
976#[cfg(test)]
977fn write_hierarchy_attribute(
978    output: &mut impl Write,
979    tpe: AttributeType,
980    subtype: u8,
981    name: &str,
982    arg: u64,
983    arg2: Option<u64>,
984) -> WriteResult<()> {
985    write_u8(output, HIERARCHY_TPE_VCD_ATTRIBUTE_BEGIN)?;
986    write_u8(output, tpe as u8)?;
987    write_u8(output, subtype)?;
988    let raw_name_bytes = match arg2 {
989        None => {
990            assert!(name.len() <= HIERARCHY_ATTRIBUTE_MAX_SIZE);
991            name.to_string().into_bytes()
992        }
993        Some(value) => {
994            assert!(name.is_empty(), "cannot have a name + an arg2!");
995            let mut buf = vec![0u8; 10];
996            let mut buf_writer: &mut [u8] = buf.as_mut();
997            let len = write_variant_u64(&mut buf_writer, value)?;
998            buf.truncate(len);
999            buf
1000        }
1001    };
1002    output.write_all(&raw_name_bytes)?;
1003    write_u8(output, 0)?; // zero terminate string/variant
1004    write_variant_u64(output, arg)?;
1005    Ok(())
1006}
1007
1008#[cfg(test)]
1009pub(crate) fn write_hierarchy_entry(
1010    output: &mut impl Write,
1011    handle_count: &mut u32,
1012    entry: &FstHierarchyEntry,
1013) -> WriteResult<()> {
1014    match entry {
1015        FstHierarchyEntry::Scope {
1016            tpe,
1017            name,
1018            component,
1019        } => {
1020            write_u8(output, HIERARCHY_TPE_VCD_SCOPE)?;
1021            write_u8(output, *tpe as u8)?;
1022            assert!(name.len() <= HIERARCHY_NAME_MAX_SIZE);
1023            write_c_str(output, name)?;
1024            assert!(component.len() <= HIERARCHY_NAME_MAX_SIZE);
1025            write_c_str(output, component)?;
1026        }
1027        FstHierarchyEntry::UpScope => {
1028            write_u8(output, HIERARCHY_TPE_VCD_UP_SCOPE)?;
1029        }
1030        FstHierarchyEntry::Var {
1031            tpe,
1032            direction,
1033            name,
1034            length,
1035            handle,
1036            is_alias,
1037        } => {
1038            write_u8(output, *tpe as u8)?;
1039            write_u8(output, *direction as u8)?;
1040            assert!(name.len() <= HIERARCHY_NAME_MAX_SIZE);
1041            write_c_str(output, name)?;
1042            let raw_length = if *tpe == FstVarType::Port {
1043                3 * (*length) + 2
1044            } else {
1045                *length
1046            };
1047            write_variant_u32(output, raw_length)?;
1048            if *is_alias {
1049                write_variant_u32(output, handle.get_raw())?;
1050            } else {
1051                // sanity check handle
1052                assert_eq!(handle.get_index(), *handle_count as usize);
1053                *handle_count += 1;
1054                // write no-alias
1055                write_variant_u32(output, 0)?;
1056            }
1057        }
1058        FstHierarchyEntry::PathName { name, id } => write_hierarchy_attribute(
1059            output,
1060            AttributeType::Misc,
1061            MiscType::PathName as u8,
1062            name,
1063            *id,
1064            None,
1065        )?,
1066        FstHierarchyEntry::SourceStem {
1067            is_instantiation,
1068            path_id,
1069            line,
1070        } => {
1071            let subtpe = if *is_instantiation {
1072                MiscType::SourceInstantiationStem
1073            } else {
1074                MiscType::SourceStem
1075            };
1076            write_hierarchy_attribute(
1077                output,
1078                AttributeType::Misc,
1079                subtpe as u8,
1080                "",
1081                *line,
1082                Some(*path_id),
1083            )?
1084        }
1085        FstHierarchyEntry::Comment { string } => write_hierarchy_attribute(
1086            output,
1087            AttributeType::Misc,
1088            MiscType::Comment as u8,
1089            string,
1090            0,
1091            None,
1092        )?,
1093        FstHierarchyEntry::EnumTable {
1094            name,
1095            handle,
1096            mapping,
1097        } => {
1098            let table_str = enum_table_to_string(name, mapping);
1099            write_hierarchy_attribute(
1100                output,
1101                AttributeType::Misc,
1102                MiscType::EnumTable as u8,
1103                &table_str,
1104                *handle,
1105                None,
1106            )?
1107        }
1108        FstHierarchyEntry::EnumTableRef { handle } => write_hierarchy_attribute(
1109            output,
1110            AttributeType::Misc,
1111            MiscType::EnumTable as u8,
1112            "",
1113            *handle,
1114            None,
1115        )?,
1116        FstHierarchyEntry::VhdlVarInfo {
1117            type_name,
1118            var_type,
1119            data_type,
1120        } => {
1121            let arg = ((*var_type as u64) << FST_SUP_VAR_DATA_TYPE_BITS) | (*data_type as u64);
1122            write_hierarchy_attribute(
1123                output,
1124                AttributeType::Misc,
1125                MiscType::SupVar as u8,
1126                type_name,
1127                arg,
1128                None,
1129            )?;
1130        }
1131        FstHierarchyEntry::Array {
1132            name,
1133            array_type,
1134            left,
1135            right,
1136        } => {
1137            let arg = (((*left) as u64) << 32) | ((*right as u64) & 0xFFFFFFFF);
1138            write_hierarchy_attribute(
1139                output,
1140                AttributeType::Array,
1141                *array_type as u8,
1142                name,
1143                arg,
1144                None,
1145            )?;
1146        }
1147        FstHierarchyEntry::SVEnum {
1148            name,
1149            enum_type,
1150            value,
1151        } => {
1152            write_hierarchy_attribute(
1153                output,
1154                AttributeType::Enum,
1155                *enum_type as u8,
1156                name,
1157                *value,
1158                None,
1159            )?;
1160        }
1161        FstHierarchyEntry::Pack {
1162            name,
1163            pack_type,
1164            value,
1165        } => {
1166            write_hierarchy_attribute(
1167                output,
1168                AttributeType::Pack,
1169                *pack_type as u8,
1170                name,
1171                *value,
1172                None,
1173            )?;
1174        }
1175        FstHierarchyEntry::AttributeEnd => {
1176            write_u8(output, HIERARCHY_TPE_VCD_ATTRIBUTE_END)?;
1177        }
1178    }
1179
1180    Ok(())
1181}
1182
1183//////////////// Vale Change Data
1184
1185pub(crate) fn read_packed_signal_value_bytes(
1186    input: &mut (impl Read + Seek),
1187    len: u32,
1188    tpe: ValueChangePackType,
1189) -> ReadResult<Vec<u8>> {
1190    let (value, skiplen) = read_variant_u32(input)?;
1191    if value != 0 {
1192        let uncompressed_length = value as u64;
1193        let uncompressed: Vec<u8> = match tpe {
1194            ValueChangePackType::Lz4 => {
1195                let compressed_length = (len - skiplen) as u64;
1196                read_lz4_compressed_bytes(
1197                    input,
1198                    uncompressed_length as usize,
1199                    compressed_length as usize,
1200                )?
1201            }
1202            ValueChangePackType::FastLz => {
1203                let compressed_length = (len - skiplen) as u64;
1204                crate::fastlz::decompress(
1205                    input,
1206                    compressed_length as usize,
1207                    uncompressed_length as usize,
1208                )?
1209            }
1210            ValueChangePackType::Zlib => {
1211                let compressed_length = len as u64;
1212                // Important: for signals, we do not skip decompression,
1213                // even if the compressed and uncompressed length are the same
1214                read_zlib_compressed_bytes(input, uncompressed_length, compressed_length, false)?
1215            }
1216        };
1217        Ok(uncompressed)
1218    } else {
1219        let dest_length = len - skiplen;
1220        let bytes = read_bytes(input, dest_length as usize)?;
1221        Ok(bytes)
1222    }
1223}
1224
1225pub(crate) fn read_time_table(
1226    input: &mut (impl Read + Seek),
1227    section_start: u64,
1228    section_length: u64,
1229) -> ReadResult<(u64, Vec<u64>)> {
1230    // the time block meta data is in the last 24 bytes at the end of the section
1231    input.seek(SeekFrom::Start(section_start + section_length - 3 * 8))?;
1232    let uncompressed_length = read_u64(input)?;
1233    let compressed_length = read_u64(input)?;
1234    let number_of_items = read_u64(input)?;
1235    assert!(compressed_length <= section_length);
1236
1237    // now that we know how long the block actually is, we can go back to it
1238    input.seek(SeekFrom::Current(-(3 * 8) - (compressed_length as i64)))?;
1239    let bytes = read_zlib_compressed_bytes(input, uncompressed_length, compressed_length, true)?;
1240    let mut byte_reader: &[u8] = &bytes;
1241    let mut time_table: Vec<u64> = Vec::with_capacity(number_of_items as usize);
1242    let mut time_val: u64 = 0; // running time counter
1243
1244    for _ in 0..number_of_items {
1245        let (value, _) = read_variant_u64(&mut byte_reader)?;
1246        time_val += value;
1247        time_table.push(time_val);
1248    }
1249
1250    let time_section_length = compressed_length + 3 * 8;
1251    Ok((time_section_length, time_table))
1252}
1253
1254#[cfg(test)]
1255pub(crate) fn write_time_table(
1256    output: &mut (impl Write + Seek),
1257    compression: Option<u8>,
1258    table: &[u64],
1259) -> WriteResult<()> {
1260    // delta compress
1261    let num_entries = table.len();
1262    let table = delta_compress_time_table(table)?;
1263    // write data
1264    let (uncompressed_len, compressed_len) = match compression {
1265        Some(comp) => {
1266            let compressed = miniz_oxide::deflate::compress_to_vec_zlib(table.as_slice(), comp);
1267            // is compression worth it?
1268            if compressed.len() < table.len() {
1269                output.write_all(compressed.as_slice())?;
1270                (table.len(), compressed.len())
1271            } else {
1272                // it is more space efficient to stick with the uncompressed version
1273                output.write_all(table.as_slice())?;
1274                (table.len(), table.len())
1275            }
1276        }
1277        None => {
1278            output.write_all(table.as_slice())?;
1279            (table.len(), table.len())
1280        }
1281    };
1282    write_u64(output, uncompressed_len as u64)?;
1283    write_u64(output, compressed_len as u64)?;
1284    write_u64(output, num_entries as u64)?;
1285
1286    Ok(())
1287}
1288
1289#[cfg(test)]
1290#[inline]
1291fn delta_compress_time_table(table: &[u64]) -> WriteResult<Vec<u8>> {
1292    let mut output = vec![];
1293    let mut prev_time = 0u64;
1294    for time in table {
1295        let delta = *time - prev_time;
1296        prev_time = *time;
1297        write_variant_u64(&mut output, delta)?;
1298    }
1299    Ok(output)
1300}
1301#[allow(clippy::too_many_arguments)]
1302#[inline]
1303pub(crate) fn read_frame(
1304    input: &mut (impl Read + Seek),
1305    section_start: u64,
1306    section_length: u64,
1307    signals: &[SignalInfo],
1308    signal_filter: &BitMask,
1309    float_endian: FloatingPointEndian,
1310    start_time: u64,
1311    callback: &mut impl FnMut(u64, FstSignalHandle, FstSignalValue),
1312) -> ReadResult<()> {
1313    // we skip the section header (section_length, start_time, end_time, ???)
1314    input.seek(SeekFrom::Start(section_start + 4 * 8))?;
1315    let (uncompressed_length, _) = read_variant_u64(input)?;
1316    let (compressed_length, _) = read_variant_u64(input)?;
1317    let (max_handle, _) = read_variant_u64(input)?;
1318    assert!(compressed_length <= section_length);
1319    let bytes_vec =
1320        read_zlib_compressed_bytes(input, uncompressed_length, compressed_length, true)?;
1321    let mut bytes = std::io::Cursor::new(bytes_vec);
1322
1323    assert_eq!(signals.len(), max_handle as usize);
1324    for (idx, signal) in signals.iter().enumerate() {
1325        let signal_length = signal.len();
1326        if signal_filter.is_set(idx) {
1327            let handle = FstSignalHandle::from_index(idx);
1328            match signal_length {
1329                0 => {} // ignore since variable-length records have no initial value
1330                len => {
1331                    if !signal.is_real() {
1332                        let value = read_bytes(&mut bytes, len as usize)?;
1333                        callback(start_time, handle, FstSignalValue::String(&value));
1334                    } else {
1335                        let value = read_f64(&mut bytes, float_endian)?;
1336                        callback(start_time, handle, FstSignalValue::Real(value));
1337                    }
1338                }
1339            }
1340        } else {
1341            // skip
1342            bytes.seek(SeekFrom::Current(signal_length as i64))?;
1343        }
1344    }
1345    Ok(())
1346}
1347
1348#[inline]
1349pub(crate) fn skip_frame(input: &mut (impl Read + Seek), section_start: u64) -> ReadResult<()> {
1350    // we skip the section header (section_length, start_time, end_time, ???)
1351    input.seek(SeekFrom::Start(section_start + 4 * 8))?;
1352    let (_uncompressed_length, _) = read_variant_u64(input)?;
1353    let (compressed_length, _) = read_variant_u64(input)?;
1354    let (_max_handle, _) = read_variant_u64(input)?;
1355    input.seek(SeekFrom::Current(compressed_length as i64))?;
1356    Ok(())
1357}
1358
1359/// Table of signal offsets inside a data block.
1360#[derive(Debug)]
1361pub(crate) struct OffsetTable(Vec<SignalDataLoc>);
1362
1363impl From<Vec<SignalDataLoc>> for OffsetTable {
1364    fn from(value: Vec<SignalDataLoc>) -> Self {
1365        Self(value)
1366    }
1367}
1368
1369impl OffsetTable {
1370    pub(crate) fn iter(&self) -> OffsetTableIter<'_> {
1371        OffsetTableIter {
1372            table: self,
1373            signal_idx: 0,
1374        }
1375    }
1376
1377    #[allow(dead_code)]
1378    pub(crate) fn len(&self) -> usize {
1379        self.0.len()
1380    }
1381
1382    fn get_entry(&self, signal_idx: usize) -> Option<OffsetEntry> {
1383        match &self.0[signal_idx] {
1384            SignalDataLoc::None => None,
1385            // aliases should always directly point to an offset,
1386            // so we should not have to recurse!
1387            SignalDataLoc::Alias(alias_idx) => match &self.0[*alias_idx as usize] {
1388                SignalDataLoc::Offset(offset, len) => Some(OffsetEntry {
1389                    signal_idx,
1390                    offset: offset.get() as u64,
1391                    len: len.get(),
1392                }),
1393                _ => unreachable!("aliases should always directly point to an offset"),
1394            },
1395            SignalDataLoc::Offset(offset, len) => Some(OffsetEntry {
1396                signal_idx,
1397                offset: offset.get() as u64,
1398                len: len.get(),
1399            }),
1400        }
1401    }
1402}
1403
1404pub(crate) struct OffsetTableIter<'a> {
1405    table: &'a OffsetTable,
1406    signal_idx: usize,
1407}
1408
1409#[derive(Debug)]
1410pub(crate) struct OffsetEntry {
1411    pub(crate) signal_idx: usize,
1412    pub(crate) offset: u64,
1413    pub(crate) len: u32,
1414}
1415impl Iterator for OffsetTableIter<'_> {
1416    type Item = OffsetEntry;
1417
1418    fn next(&mut self) -> Option<Self::Item> {
1419        // get the first entry which is not None
1420        while self.signal_idx < self.table.0.len()
1421            && matches!(self.table.0[self.signal_idx], SignalDataLoc::None)
1422        {
1423            self.signal_idx += 1
1424        }
1425
1426        // did we reach the end?
1427        if self.signal_idx >= self.table.0.len() {
1428            return None;
1429        }
1430
1431        // read out result
1432        let res = self.table.get_entry(self.signal_idx);
1433        debug_assert!(res.is_some());
1434
1435        // increment id for next call
1436        self.signal_idx += 1;
1437
1438        // return result
1439        res
1440    }
1441}
1442
1443fn read_value_change_alias2(
1444    mut chain_bytes: &[u8],
1445    max_handle: u64,
1446    last_table_entry: u32,
1447) -> ReadResult<OffsetTable> {
1448    let mut table = vec![SignalDataLoc::None; max_handle as usize];
1449    let mut idx = 0_usize;
1450    let mut offset: Option<NonZeroU32> = None;
1451    let mut prev_alias = 0u32;
1452    let mut prev_offset_idx = 0usize;
1453    while !chain_bytes.is_empty() {
1454        let kind = chain_bytes[0];
1455        if (kind & 1) == 1 {
1456            let shval = read_variant_i64(&mut chain_bytes)? >> 1;
1457            match shval.cmp(&0) {
1458                Ordering::Greater => {
1459                    // a new incremental offset
1460                    let new_offset = NonZeroU32::new(
1461                        (offset.map(|o| o.get()).unwrap_or_default() as i64 + shval) as u32,
1462                    )
1463                    .unwrap();
1464                    // if there was a previous entry, we need to update the length
1465                    if let Some(prev_offset) = offset {
1466                        let len = NonZeroU32::new(new_offset.get() - prev_offset.get()).unwrap();
1467                        table[prev_offset_idx] = SignalDataLoc::Offset(prev_offset, len);
1468                    }
1469                    offset = Some(new_offset);
1470                    prev_offset_idx = idx;
1471                    // increase index, value will be replaced as soon as we know the length
1472                    idx += 1;
1473                }
1474                Ordering::Less => {
1475                    // new signal alias
1476                    prev_alias = (-shval - 1) as u32;
1477                    table[idx] = SignalDataLoc::Alias(prev_alias);
1478                    idx += 1;
1479                }
1480                Ordering::Equal => {
1481                    // same signal alias as previous signal
1482                    table[idx] = SignalDataLoc::Alias(prev_alias);
1483                    idx += 1;
1484                }
1485            }
1486        } else {
1487            // a block of signals that do not have any data
1488            let (value, _) = read_variant_u32(&mut chain_bytes)?;
1489            let zeros = value >> 1;
1490            idx += zeros as usize;
1491        }
1492    }
1493
1494    // if there was a previous entry, we need to update the length
1495    if let Some(prev_offset) = offset {
1496        let len = NonZeroU32::new(last_table_entry - prev_offset.get()).unwrap();
1497        table[prev_offset_idx] = SignalDataLoc::Offset(prev_offset, len);
1498    }
1499
1500    debug_assert_eq!(max_handle as usize, idx);
1501
1502    Ok(table.into())
1503}
1504
1505fn read_value_change_alias(
1506    mut chain_bytes: &[u8],
1507    max_handle: u64,
1508    last_table_entry: u32,
1509) -> ReadResult<OffsetTable> {
1510    let mut table = Vec::with_capacity(max_handle as usize);
1511    let mut prev_offset_idx = 0usize;
1512    let mut offset: Option<NonZeroU32> = None;
1513    while !chain_bytes.is_empty() {
1514        let (raw_val, _) = read_variant_u32(&mut chain_bytes)?;
1515        let idx = table.len();
1516        if raw_val == 0 {
1517            let (raw_alias, _) = read_variant_u32(&mut chain_bytes)?;
1518            let alias = ((raw_alias as i64) - 1) as u32;
1519            table.push(SignalDataLoc::Alias(alias));
1520        } else if (raw_val & 1) == 1 {
1521            // a new incremental offset
1522            let new_offset =
1523                NonZeroU32::new(offset.map(|o| o.get()).unwrap_or_default() + (raw_val >> 1))
1524                    .unwrap();
1525            // if there was a previous entry, we need to update the length
1526            if let Some(prev_offset) = offset {
1527                let len = NonZeroU32::new(new_offset.get() - prev_offset.get()).unwrap();
1528                table[prev_offset_idx] = SignalDataLoc::Offset(prev_offset, len);
1529            }
1530            offset = Some(new_offset);
1531            prev_offset_idx = idx;
1532            // push a placeholder which will be replaced as soon as we know the length
1533            table.push(SignalDataLoc::None);
1534        } else {
1535            // a block of signals that do not have any data
1536            let zeros = raw_val >> 1;
1537            for _ in 0..zeros {
1538                table.push(SignalDataLoc::None);
1539            }
1540        }
1541    }
1542
1543    // if there was a previous entry, we need to update the length
1544    if let Some(prev_offset) = offset {
1545        let len = NonZeroU32::new(last_table_entry - prev_offset.get()).unwrap();
1546        table[prev_offset_idx] = SignalDataLoc::Offset(prev_offset, len);
1547    }
1548
1549    Ok(table.into())
1550}
1551
1552/// Indicates the location of the signal data for the current block.
1553#[derive(Debug, Copy, Clone)]
1554enum SignalDataLoc {
1555    /// The signal has no value changes in the current block.
1556    None,
1557    /// The signal has the same offset as another signal.
1558    Alias(u32),
1559    /// The signal has a new offset.
1560    Offset(NonZeroU32, NonZeroU32),
1561}
1562
1563pub(crate) fn read_signal_locs(
1564    input: &mut (impl Read + Seek),
1565    chain_len_offset: u64,
1566    section_kind: DataSectionKind,
1567    max_handle: u64,
1568    start: u64,
1569) -> ReadResult<OffsetTable> {
1570    input.seek(SeekFrom::Start(chain_len_offset))?;
1571    let chain_compressed_length = read_u64(input)?;
1572
1573    // the chain starts _chain_length_ bytes before the chain length
1574    let chain_start = chain_len_offset - chain_compressed_length;
1575    input.seek(SeekFrom::Start(chain_start))?;
1576    let chain_bytes = read_bytes(input, chain_compressed_length as usize)?;
1577
1578    let last_table_entry = (chain_start - start) as u32; // indx_pos - vc_start
1579    if section_kind == DataSectionKind::DynamicAlias2 {
1580        read_value_change_alias2(&chain_bytes, max_handle, last_table_entry)
1581    } else {
1582        read_value_change_alias(&chain_bytes, max_handle, last_table_entry)
1583    }
1584}
1585
1586#[cfg(test)]
1587mod tests {
1588    use super::*;
1589    use proptest::prelude::*;
1590
1591    #[test]
1592    fn data_struct_sizes() {
1593        assert_eq!(
1594            std::mem::size_of::<SignalDataLoc>(),
1595            std::mem::size_of::<u64>() + std::mem::size_of::<u32>()
1596        );
1597    }
1598
1599    #[test]
1600    fn test_read_variant_i64() {
1601        // a positive value from a real fst file (solution from gtkwave)
1602        let in1 = [0x13];
1603        assert_eq!(read_variant_i64(&mut in1.as_slice()).unwrap(), 19);
1604        // a negative value from a real fst file (solution from gtkwave)
1605        let in0 = [0x7b];
1606        assert_eq!(read_variant_i64(&mut in0.as_slice()).unwrap(), -5);
1607    }
1608
1609    #[test]
1610    fn regression_test_read_write_variant_i64() {
1611        do_test_read_write_variant_i64(-36028797018963969);
1612        do_test_read_write_variant_i64(-4611686018427387905);
1613    }
1614
1615    fn do_test_read_write_variant_i64(value: i64) {
1616        let mut buf = std::io::Cursor::new(vec![0u8; 24]);
1617        write_variant_i64(&mut buf, value).unwrap();
1618        buf.seek(SeekFrom::Start(0)).unwrap();
1619        let read_value = read_variant_i64(&mut buf).unwrap();
1620        assert_eq!(read_value, value);
1621    }
1622
1623    proptest! {
1624         #[test]
1625        fn test_read_write_variant_u64(value: u64) {
1626            let mut buf = std::io::Cursor::new(vec![0u8; 24]);
1627            write_variant_u64(&mut buf, value).unwrap();
1628            buf.seek(SeekFrom::Start(0)).unwrap();
1629            let (read_value, _) = read_variant_u64(&mut buf).unwrap();
1630            assert_eq!(read_value, value);
1631        }
1632
1633         #[test]
1634        fn test_read_write_variant_i64(value: i64) {
1635            do_test_read_write_variant_i64(value);
1636        }
1637    }
1638
1639    #[test]
1640    fn test_read_c_str_fixed_length() {
1641        let input = [b'h', b'i', 0u8, b'x'];
1642        assert_eq!(
1643            read_c_str_fixed_length(&mut input.as_slice(), 4).unwrap(),
1644            "hi"
1645        );
1646        let input2 = [b'h', b'i', b'i', 0u8, b'x'];
1647        assert_eq!(
1648            read_c_str_fixed_length(&mut input2.as_slice(), 5).unwrap(),
1649            "hii"
1650        );
1651    }
1652
1653    /// makes sure that there are no zero bytes inside the string and that the max length is obeyed
1654    fn is_valid_c_str(value: &str, max_len: usize) -> bool {
1655        let string_bytes: &[u8] = value.as_bytes();
1656        let len_constraint = string_bytes.len() < max_len;
1657        let non_zero_constraint = !string_bytes.contains(&0u8);
1658        len_constraint && non_zero_constraint
1659    }
1660
1661    fn is_valid_alphanumeric_c_str(value: &str, max_len: usize) -> bool {
1662        let alphanumeric_constraint = value.chars().all(|c| c.is_alphanumeric());
1663        is_valid_c_str(value, max_len) && alphanumeric_constraint
1664    }
1665
1666    proptest! {
1667        #[test]
1668        fn test_write_c_str_fixed_length(string: String, max_len in 1 .. 400usize) {
1669            prop_assume!(is_valid_c_str(&string, max_len));
1670            let mut buf = std::io::Cursor::new(vec![0u8; max_len]);
1671            write_c_str_fixed_length(&mut buf, &string, max_len).unwrap();
1672            buf.seek(SeekFrom::Start(0)).unwrap();
1673            assert_eq!(
1674                read_c_str_fixed_length(&mut buf, max_len).unwrap(),
1675                string
1676            );
1677        }
1678    }
1679
1680    proptest! {
1681        #[test]
1682        fn test_write_c_str(string: String, max_len in 1 .. 400usize) {
1683            prop_assume!(is_valid_c_str(&string, max_len));
1684            let mut buf = std::io::Cursor::new(vec![0u8; max_len]);
1685            write_c_str(&mut buf, &string).unwrap();
1686            buf.seek(SeekFrom::Start(0)).unwrap();
1687            assert_eq!(
1688                read_c_str(&mut buf, max_len).unwrap(),
1689                string
1690            );
1691        }
1692    }
1693
1694    proptest! {
1695        #[test]
1696        fn test_read_write_header(header: Header) {
1697            // return early if the header strings are too long
1698            prop_assume!(header.version.len() <= HEADER_VERSION_MAX_LEN);
1699            prop_assume!(header.date.len() <= HEADER_DATE_MAX_LEN );
1700
1701            let mut buf = [0u8; 512];
1702            write_header(&mut buf.as_mut(), &header).unwrap();
1703            let (actual_header, endian) = read_header(&mut buf.as_slice()).unwrap();
1704            assert_eq!(endian, FloatingPointEndian::Little);
1705            assert_eq!(actual_header, header);
1706        }
1707    }
1708
1709    proptest! {
1710        #[test]
1711        fn test_compress_bytes(bytes: Vec<u8>, allow_uncompressed: bool) {
1712            let mut buf = std::io::Cursor::new(vec![0u8; bytes.len() * 2]);
1713            let compressed_len = write_compressed_bytes(&mut buf, &bytes, 3, allow_uncompressed).unwrap();
1714            if allow_uncompressed {
1715                assert!(compressed_len <= bytes.len());
1716            }
1717            buf.seek(SeekFrom::Start(0)).unwrap();
1718            let uncompressed = read_zlib_compressed_bytes(&mut buf, bytes.len() as u64, compressed_len as u64, allow_uncompressed).unwrap();
1719            assert_eq!(uncompressed, bytes);
1720        }
1721    }
1722
1723    proptest! {
1724        #[test]
1725        fn test_read_write_blackout(mut blackouts: Vec<BlackoutData>) {
1726            // blackout times must be in increasing order => sort
1727            blackouts.sort_by(|a, b| a.time.cmp(&b.time));
1728
1729            // actual test
1730            let max_len = blackouts.len() * 5 + 3 * 8;
1731            let mut buf = std::io::Cursor::new(vec![0u8; max_len]);
1732            write_blackout(&mut buf, &blackouts).unwrap();
1733            buf.seek(SeekFrom::Start(0)).unwrap();
1734            let actual = read_blackout(&mut buf).unwrap();
1735            assert_eq!(actual.len(), blackouts.len());
1736            assert_eq!(actual, blackouts);
1737        }
1738    }
1739
1740    proptest! {
1741        #[test]
1742        fn test_read_write_geometry(signals: Vec<SignalInfo>) {
1743            let max_len = signals.len() * 4 + 3 * 8;
1744            let mut buf = std::io::Cursor::new(vec![0u8; max_len]);
1745            write_geometry(&mut buf, &signals, 3).unwrap();
1746            buf.seek(SeekFrom::Start(0)).unwrap();
1747            let actual = read_geometry(&mut buf).unwrap();
1748            assert_eq!(actual.len(), signals.len());
1749            assert_eq!(actual, signals);
1750        }
1751    }
1752
1753    /// ensures that no string contains zero bytes or is longer than max_len
1754    fn hierarchy_entry_with_valid_c_strings(entry: &FstHierarchyEntry) -> bool {
1755        match entry {
1756            FstHierarchyEntry::Scope {
1757                name, component, ..
1758            } => {
1759                is_valid_c_str(name, HIERARCHY_NAME_MAX_SIZE)
1760                    && is_valid_c_str(component, HIERARCHY_NAME_MAX_SIZE)
1761            }
1762            FstHierarchyEntry::UpScope => true,
1763            FstHierarchyEntry::Var { name, .. } => is_valid_c_str(name, HIERARCHY_NAME_MAX_SIZE),
1764            FstHierarchyEntry::PathName { name, .. } => {
1765                is_valid_c_str(name, HIERARCHY_ATTRIBUTE_MAX_SIZE)
1766            }
1767            FstHierarchyEntry::SourceStem { .. } => true,
1768            FstHierarchyEntry::Comment { string } => {
1769                is_valid_c_str(string, HIERARCHY_ATTRIBUTE_MAX_SIZE)
1770            }
1771            FstHierarchyEntry::EnumTable { name, mapping, .. } => {
1772                is_valid_alphanumeric_c_str(name, HIERARCHY_ATTRIBUTE_MAX_SIZE)
1773                    && mapping.iter().all(|(k, v)| {
1774                        is_valid_alphanumeric_c_str(k, HIERARCHY_ATTRIBUTE_MAX_SIZE)
1775                            && is_valid_alphanumeric_c_str(v, HIERARCHY_ATTRIBUTE_MAX_SIZE)
1776                    })
1777            }
1778            FstHierarchyEntry::EnumTableRef { .. } => true,
1779            FstHierarchyEntry::VhdlVarInfo { type_name, .. } => {
1780                is_valid_c_str(type_name, HIERARCHY_NAME_MAX_SIZE)
1781            }
1782            FstHierarchyEntry::Array { name, .. } => is_valid_c_str(name, HIERARCHY_NAME_MAX_SIZE),
1783            FstHierarchyEntry::SVEnum { name, .. } => is_valid_c_str(name, HIERARCHY_NAME_MAX_SIZE),
1784            FstHierarchyEntry::Pack { name, .. } => is_valid_c_str(name, HIERARCHY_NAME_MAX_SIZE),
1785            FstHierarchyEntry::AttributeEnd => true,
1786        }
1787    }
1788
1789    /// ensures that the mapping strings are non-empty and do not contain spaces
1790    fn hierarchy_entry_with_valid_mapping(entry: &FstHierarchyEntry) -> bool {
1791        match entry {
1792            FstHierarchyEntry::EnumTable { mapping, .. } => mapping
1793                .iter()
1794                .all(|(k, v)| is_valid_mapping_str(k) && is_valid_mapping_str(v)),
1795            _ => true,
1796        }
1797    }
1798    fn is_valid_mapping_str(value: &str) -> bool {
1799        !value.is_empty() && !value.contains(' ')
1800    }
1801
1802    /// ensures that ports are not too wide
1803    fn hierarchy_entry_with_valid_port_width(entry: &FstHierarchyEntry) -> bool {
1804        if let FstHierarchyEntry::Var {
1805            tpe: FstVarType::Port,
1806            length,
1807            ..
1808        } = entry
1809        {
1810            *length < (u32::MAX / 3) - 2
1811        } else {
1812            true
1813        }
1814    }
1815
1816    fn read_write_hierarchy_entry(entry: FstHierarchyEntry) {
1817        // the handle count is only important if we are writing a non-aliased variable
1818        let base_handle_count: u32 = match &entry {
1819            FstHierarchyEntry::Var {
1820                handle, is_alias, ..
1821            } => {
1822                if *is_alias {
1823                    0
1824                } else {
1825                    handle.get_index() as u32
1826                }
1827            }
1828            _ => 0,
1829        };
1830
1831        let max_len = 1024 * 64;
1832        let mut buf = std::io::Cursor::new(vec![0u8; max_len]);
1833        let mut handle_count = base_handle_count;
1834        write_hierarchy_entry(&mut buf, &mut handle_count, &entry).unwrap();
1835        if base_handle_count > 0 {
1836            assert_eq!(handle_count, base_handle_count + 1);
1837        }
1838        buf.seek(SeekFrom::Start(0)).unwrap();
1839        handle_count = base_handle_count;
1840        let actual = read_hierarchy_entry(&mut buf, &mut handle_count)
1841            .unwrap()
1842            .unwrap();
1843        assert_eq!(actual, entry);
1844    }
1845
1846    #[test]
1847    fn test_read_write_hierarchy_path_name_entry() {
1848        let entry = FstHierarchyEntry::PathName {
1849            id: 1,
1850            name: "".to_string(),
1851        };
1852        read_write_hierarchy_entry(entry);
1853    }
1854
1855    proptest! {
1856        #[test]
1857        fn test_prop_read_write_hierarchy_entry(entry: FstHierarchyEntry) {
1858            prop_assume!(hierarchy_entry_with_valid_c_strings(&entry));
1859            prop_assume!(hierarchy_entry_with_valid_mapping(&entry));
1860            prop_assume!(hierarchy_entry_with_valid_port_width(&entry));
1861            read_write_hierarchy_entry(entry);
1862        }
1863    }
1864
1865    // test with some manually chosen entries
1866    #[test]
1867    fn test_read_write_hierarchy_entry() {
1868        // make sure that we can write and read long attributes
1869        let entry = FstHierarchyEntry::Comment {
1870            string: "TEST ".repeat((8000 + 4) / 5),
1871        };
1872        read_write_hierarchy_entry(entry);
1873    }
1874
1875    fn do_test_read_write_hierarchy_bytes(tpe: HierarchyCompression, bytes: Vec<u8>) {
1876        let max_len = match tpe {
1877            HierarchyCompression::Uncompressed => bytes.len(),
1878            _ => std::cmp::max(64, bytes.len() + 3 * 8),
1879        };
1880        let mut buf = std::io::Cursor::new(vec![0u8; max_len]);
1881        write_hierarchy_bytes(&mut buf, tpe, &bytes).unwrap();
1882        buf.seek(SeekFrom::Start(0)).unwrap();
1883        let actual = read_hierarchy_bytes(&mut buf, tpe).unwrap();
1884        assert_eq!(actual, bytes);
1885    }
1886
1887    #[test]
1888    fn test_read_write_hierarchy_bytes_regression() {
1889        do_test_read_write_hierarchy_bytes(HierarchyCompression::Lz4, vec![]);
1890        do_test_read_write_hierarchy_bytes(HierarchyCompression::ZLib, vec![]);
1891    }
1892
1893    proptest! {
1894        #[test]
1895        fn test_prop_read_write_hierarchy_bytes(tpe: HierarchyCompression, bytes: Vec<u8>) {
1896            do_test_read_write_hierarchy_bytes(tpe, bytes);
1897        }
1898    }
1899
1900    fn read_write_time_table(mut table: Vec<u64>, compressed: bool) {
1901        // the table has to be sorted since we are computing and saving time deltas
1902        table.sort();
1903        let max_len = std::cmp::max(64, table.len() * 8 + 3 * 8);
1904        let mut buf = std::io::Cursor::new(vec![0u8; max_len]);
1905        let comp = if compressed { Some(3) } else { None };
1906        write_time_table(&mut buf, comp, &table).unwrap();
1907        let section_start = 0u64;
1908        let section_length = buf.stream_position().unwrap();
1909        buf.seek(SeekFrom::Start(0)).unwrap();
1910        let (actual_len, actual_table) =
1911            read_time_table(&mut buf, section_start, section_length).unwrap();
1912        assert_eq!(actual_len, section_length);
1913        assert_eq!(actual_table, table);
1914    }
1915
1916    #[test]
1917    fn test_read_write_time_table_uncompressed() {
1918        let table = vec![1, 0];
1919        read_write_time_table(table, false);
1920    }
1921
1922    #[test]
1923    fn test_read_write_time_table_compressed() {
1924        let table = (0..10000).collect();
1925        read_write_time_table(table, true);
1926    }
1927
1928    proptest! {
1929        #[test]
1930        fn test_prop_read_write_time_table(table: Vec<u64>, compressed: bool) {
1931            read_write_time_table(table, compressed);
1932        }
1933    }
1934}