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