/*
 * Copyright (c) 2009-2023 Tony Bybell.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * SPDX-License-Identifier: MIT
 */

/*
 * possible disables:
 *
 * FST_DYNAMIC_ALIAS_DISABLE : dynamic aliases are not processed
 * FST_DYNAMIC_ALIAS2_DISABLE : new encoding for dynamic aliases is not generated
 * FST_WRITEX_DISABLE : fast write I/O routines are disabled
 *
 * possible enables:
 *
 * FST_DEBUG : not for production use, only enable for development
 * FST_REMOVE_DUPLICATE_VC : glitch removal (has writer performance impact)
 * HAVE_LIBPTHREAD -> FST_WRITER_PARALLEL : enables inclusion of parallel writer code
 *
 */

#ifdef FST_INCLUDE_CONFIG
#include "config.h"
#endif

#include "fstapi.h"
#include "fastlz.h"
#include "lz4.h"
#include <errno.h>

#ifndef HAVE_LIBPTHREAD
#undef FST_WRITER_PARALLEL
#endif

#ifdef FST_WRITER_PARALLEL
#include <pthread.h>
#endif

#if defined __CYGWIN__ || defined __MINGW32__ || defined _MSC_VER
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#ifndef PATH_MAX
#define PATH_MAX (4096)
#endif

#if defined(_MSC_VER)
typedef int64_t fst_off_t;
#else
typedef off_t fst_off_t;
#endif

/* should be more than enough for fstWriterSetSourceStem() */
#define FST_PATH_HASHMASK ((1UL << 16) - 1)
typedef const void *Pcvoid_t;
typedef void *Pvoid_t;
typedef void **PPvoid_t;
void JenkinsFree(void *base_i, uint32_t hashmask);
void **JenkinsIns(void *base_i, const unsigned char *mem, uint32_t length, uint32_t hashmask);

#ifndef FST_WRITEX_DISABLE
#define FST_WRITEX_MAX (64 * 1024)
#else
#define fstWritex(a, b, c) fstFwrite((b), (c), 1, fv)
#endif

/* these defines have a large impact on writer speed when a model has a */
/* huge number of symbols.  as a default, use 128MB and increment when  */
/* every 1M signals are defined.                                        */
#define FST_BREAK_SIZE (1UL << 27)
#define FST_BREAK_ADD_SIZE (1UL << 22)
#define FST_BREAK_SIZE_MAX (1UL << 31)
#define FST_ACTIVATE_HUGE_BREAK (1000000)
#define FST_ACTIVATE_HUGE_INC (1000000)

#define FST_WRITER_STR "fstWriter"
#define FST_ID_NAM_SIZ (512)
#define FST_ID_NAM_ATTR_SIZ (65536 + 4096)
#define FST_DOUBLE_ENDTEST (2.7182818284590452354)
#define FST_HDR_SIM_VERSION_SIZE (128)
#define FST_HDR_DATE_SIZE (119)
#define FST_HDR_FILETYPE_SIZE (1)
#define FST_HDR_TIMEZERO_SIZE (8)
#define FST_GZIO_LEN (32768)
#define FST_HDR_FOURPACK_DUO_SIZE (4 * 1024 * 1024)
#define FST_ZWRAPPER_HDR_SIZE (1 + 8 + 8)

#if defined(__APPLE__) && defined(__MACH__)
#define FST_MACOSX
#include <sys/sysctl.h>
#endif

#if defined(FST_MACOSX) || defined(__MINGW32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || \
    defined(__NetBSD__) || defined(_MSC_VER)
#define FST_UNBUFFERED_IO
#endif

#ifdef __GNUC__
/* Boolean expression more often true than false */
#define FST_LIKELY(x) __builtin_expect(!!(x), 1)
/* Boolean expression more often false than true */
#define FST_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
#define FST_LIKELY(x) (!!(x))
#define FST_UNLIKELY(x) (!!(x))
#endif

#define FST_APIMESS "FSTAPI  | "

/***********************/
/***                 ***/
/*** common function ***/
/***                 ***/
/***********************/

#if defined(__MINGW32__) || defined(_MSC_VER)
#include <io.h>
#ifndef HAVE_FSEEKO
#define ftello _ftelli64
#define fseeko _fseeki64
#endif
#endif

/*
 * the recoded "extra" values...
 * note that FST_RCV_Q is currently unused and is for future expansion.
 * its intended use is as another level of escape such that any arbitrary
 * value can be stored as the value: { time_delta, 8 bits, FST_RCV_Q }.
 * this is currently not implemented so that the branchless decode is:
 * uint32_t shcnt = 2 << (vli & 1); tdelta = vli >> shcnt;
 */
#define FST_RCV_X (1 | (0 << 1))
#define FST_RCV_Z (1 | (1 << 1))
#define FST_RCV_H (1 | (2 << 1))
#define FST_RCV_U (1 | (3 << 1))
#define FST_RCV_W (1 | (4 << 1))
#define FST_RCV_L (1 | (5 << 1))
#define FST_RCV_D (1 | (6 << 1))
#define FST_RCV_Q (1 | (7 << 1))

#define FST_RCV_STR "xzhuwl-?"
/*                   01234567 */

/*
 * report abort messages
 */
static void chk_report_abort(const char *s)
{
    fprintf(stderr, "Triggered %s security check, exiting.\n", s);
    abort();
}

/*
 * prevent old file overwrite when currently being read
 */
static FILE *unlink_fopen(const char *nam, const char *mode)
{
    unlink(nam);
    return (fopen(nam, mode));
}

/*
 * system-specific temp file handling
 */
#ifdef __MINGW32__

static FILE *tmpfile_open(char **nam)
{
    char *fname = NULL;
    TCHAR szTempFileName[MAX_PATH];
    TCHAR lpTempPathBuffer[MAX_PATH];
    DWORD dwRetVal = 0;
    UINT uRetVal = 0;
    FILE *fh = NULL;

    if (nam) /* cppcheck warning fix: nam is always defined, so this is not needed */
    {
        dwRetVal = GetTempPath(MAX_PATH, lpTempPathBuffer);
        if ((dwRetVal > MAX_PATH) || (dwRetVal == 0)) {
            fprintf(stderr,
                    FST_APIMESS "GetTempPath() failed in " __FILE__ " line %d, exiting.\n",
                    __LINE__);
            exit(255);
        } else {
            uRetVal = GetTempFileName(lpTempPathBuffer, TEXT("FSTW"), 0, szTempFileName);
            if (uRetVal == 0) {
                fprintf(stderr,
                        FST_APIMESS "GetTempFileName() failed in " __FILE__ " line %d, exiting.\n",
                        __LINE__);
                exit(255);
            } else {
                fname = strdup(szTempFileName);
            }
        }

        if (fname) {
            *nam = fname;
            fh = unlink_fopen(fname, "w+b");
        }
    }

    return (fh);
}

#else

static FILE *tmpfile_open(char **nam)
{
    FILE *f = tmpfile(); /* replace with mkstemp() + fopen(), etc if this is not good enough */
    if (nam) {
        *nam = NULL;
    }
    return (f);
}

#endif

static void tmpfile_close(FILE **f, char **nam)
{
    if (f) {
        if (*f) {
            fclose(*f);
            *f = NULL;
        }
    }

    if (nam) {
        if (*nam) {
            unlink(*nam);
            free(*nam);
            *nam = NULL;
        }
    }
}

/*****************************************/

/*
 * to remove warn_unused_result compile time messages
 * (in the future there needs to be results checking)
 */
static size_t fstFread(void *buf, size_t siz, size_t cnt, FILE *fp)
{
    return (fread(buf, siz, cnt, fp));
}

static size_t fstFwrite(const void *buf, size_t siz, size_t cnt, FILE *fp)
{
    return (fwrite(buf, siz, cnt, fp));
}

static int fstFtruncate(int fd, fst_off_t length)
{
    return (ftruncate(fd, length));
}

/*
 * realpath compatibility
 */
static char *fstRealpath(const char *path, char *resolved_path)
{
#if defined __USE_BSD || defined __USE_XOPEN_EXTENDED || defined __CYGWIN__ || defined HAVE_REALPATH
#if (defined(__MACH__) && defined(__APPLE__))
    if (!resolved_path) {
        resolved_path =
            (char *)malloc(PATH_MAX + 1); /* fixes bug on Leopard when resolved_path == NULL */
    }
#endif

    return (realpath(path, resolved_path));

#else
#ifdef __MINGW32__
    if (!resolved_path) {
        resolved_path = (char *)malloc(PATH_MAX + 1);
    }
    return (_fullpath(resolved_path, path, PATH_MAX));
#else
    (void)path;
    (void)resolved_path;
    return (NULL);
#endif
#endif
}

/*
 * mmap compatibility
 */
#if defined __MINGW32__ || defined _MSC_VER
#include <limits.h>
#define fstMmap(__addr, __len, __prot, __flags, __fd, __off) fstMmap2((__len), (__fd), (__off))
#define fstMunmap(__addr, __len) UnmapViewOfFile((LPCVOID)__addr)

static void *fstMmap2(size_t __len, int __fd, fst_off_t __off)
{
    DWORD64 len64 = __len; /* Must be 64-bit for shift below */
    HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(__fd),
                                      NULL,
                                      PAGE_READWRITE,
                                      (DWORD)(len64 >> 32),
                                      (DWORD)__len,
                                      NULL);
    if (!handle) {
        return NULL;
    }

    void *ptr = MapViewOfFileEx(handle,
                                FILE_MAP_READ | FILE_MAP_WRITE,
                                0,
                                (DWORD)__off,
                                (SIZE_T)__len,
                                (LPVOID)NULL);
    CloseHandle(handle);
    return ptr;
}
#else
#include <sys/mman.h>
#if defined(__SUNPRO_C)
#define FST_CADDR_T_CAST (caddr_t)
#else
#define FST_CADDR_T_CAST
#endif
#define fstMmap(__addr, __len, __prot, __flags, __fd, __off) \
    (void *)mmap(FST_CADDR_T_CAST(__addr), (__len), (__prot), (__flags), (__fd), (__off))
#define fstMunmap(__addr, __len) \
    { \
        if (__addr) \
            munmap(FST_CADDR_T_CAST(__addr), (__len)); \
    }
#endif

/*
 * regular and variable-length integer access functions
 */
static uint32_t fstGetUint32(unsigned char *mem)
{
    uint32_t u32;
    unsigned char *buf = (unsigned char *)(&u32);

    memcpy(buf, mem, sizeof(uint32_t));

    return (*(uint32_t *)buf);
}

static int fstWriterUint64(FILE *handle, uint64_t v)
{
    unsigned char buf[8];
    int i;

    for (i = 7; i >= 0; i--) {
        buf[i] = v & 0xff;
        v >>= 8;
    }

    fstFwrite(buf, 8, 1, handle);
    return (8);
}

static uint64_t fstReaderUint64(FILE *f)
{
    uint64_t val = 0;
    unsigned char buf[sizeof(uint64_t)];
    unsigned int i;

    fstFread(buf, sizeof(uint64_t), 1, f);
    for (i = 0; i < sizeof(uint64_t); i++) {
        val <<= 8;
        val |= buf[i];
    }

    return (val);
}

static uint32_t fstGetVarint32(unsigned char *mem, int *skiplen)
{
    unsigned char *mem_orig = mem;
    uint32_t rc = 0;
    while (*mem & 0x80) {
        mem++;
    }

    *skiplen = mem - mem_orig + 1;
    for (;;) {
        rc <<= 7;
        rc |= (uint32_t)(*mem & 0x7f);
        if (mem == mem_orig) {
            break;
        }
        mem--;
    }

    return (rc);
}

static uint32_t fstGetVarint32Length(unsigned char *mem)
{
    unsigned char *mem_orig = mem;

    while (*mem & 0x80) {
        mem++;
    }

    return (mem - mem_orig + 1);
}

static uint32_t fstGetVarint32NoSkip(unsigned char *mem)
{
    unsigned char *mem_orig = mem;
    uint32_t rc = 0;
    while (*mem & 0x80) {
        mem++;
    }

    for (;;) {
        rc <<= 7;
        rc |= (uint32_t)(*mem & 0x7f);
        if (mem == mem_orig) {
            break;
        }
        mem--;
    }

    return (rc);
}

static unsigned char *fstCopyVarint32ToLeft(unsigned char *pnt, uint32_t v)
{
    unsigned char *spnt;
    uint32_t nxt = v;
    int cnt = 1;
    int i;

    while ((nxt = nxt >>
                  7)) /* determine len to avoid temp buffer copying to cut down on load-hit-store */
    {
        cnt++;
    }

    pnt -= cnt;
    spnt = pnt;
    cnt--;

    for (i = 0; i < cnt; i++) /* now generate left to right as normal */
    {
        nxt = v >> 7;
        *(spnt++) = ((unsigned char)v) | 0x80;
        v = nxt;
    }
    *spnt = (unsigned char)v;

    return (pnt);
}

static unsigned char *fstCopyVarint64ToRight(unsigned char *pnt, uint64_t v)
{
    uint64_t nxt;

    while ((nxt = v >> 7)) {
        *(pnt++) = ((unsigned char)v) | 0x80;
        v = nxt;
    }
    *(pnt++) = (unsigned char)v;

    return (pnt);
}

static uint64_t fstGetVarint64(unsigned char *mem, int *skiplen)
{
    unsigned char *mem_orig = mem;
    uint64_t rc = 0;
    while (*mem & 0x80) {
        mem++;
    }

    *skiplen = mem - mem_orig + 1;
    for (;;) {
        rc <<= 7;
        rc |= (uint64_t)(*mem & 0x7f);
        if (mem == mem_orig) {
            break;
        }
        mem--;
    }

    return (rc);
}

static uint32_t fstReaderVarint32(FILE *f)
{
    const int chk_len_max = 5; /* TALOS-2023-1783 */
    int chk_len = chk_len_max;
    unsigned char buf[chk_len_max];
    unsigned char *mem = buf;
    uint32_t rc = 0;
    int ch;

    do {
        ch = fgetc(f);
        *(mem++) = ch;
    } while ((ch & 0x80) && (--chk_len));

    if (ch & 0x80)
        chk_report_abort("TALOS-2023-1783");
    mem--;

    for (;;) {
        rc <<= 7;
        rc |= (uint32_t)(*mem & 0x7f);
        if (mem == buf) {
            break;
        }
        mem--;
    }

    return (rc);
}

static uint32_t fstReaderVarint32WithSkip(FILE *f, uint32_t *skiplen)
{
    const int chk_len_max = 5; /* TALOS-2023-1783 */
    int chk_len = chk_len_max;
    unsigned char buf[chk_len_max];
    unsigned char *mem = buf;
    uint32_t rc = 0;
    int ch;

    do {
        ch = fgetc(f);
        *(mem++) = ch;
    } while ((ch & 0x80) && (--chk_len));

    if (ch & 0x80)
        chk_report_abort("TALOS-2023-1783");
    *skiplen = mem - buf;
    mem--;

    for (;;) {
        rc <<= 7;
        rc |= (uint32_t)(*mem & 0x7f);
        if (mem == buf) {
            break;
        }
        mem--;
    }

    return (rc);
}

static uint64_t fstReaderVarint64(FILE *f)
{
    const int chk_len_max = 16; /* TALOS-2023-1783 */
    int chk_len = chk_len_max;
    unsigned char buf[chk_len_max];
    unsigned char *mem = buf;
    uint64_t rc = 0;
    int ch;

    do {
        ch = fgetc(f);
        *(mem++) = ch;
    } while ((ch & 0x80) && (--chk_len));

    if (ch & 0x80)
        chk_report_abort("TALOS-2023-1783");
    mem--;

    for (;;) {
        rc <<= 7;
        rc |= (uint64_t)(*mem & 0x7f);
        if (mem == buf) {
            break;
        }
        mem--;
    }

    return (rc);
}

static int fstWriterVarint(FILE *handle, uint64_t v)
{
    uint64_t nxt;
    unsigned char buf[10]; /* ceil(64/7) = 10 */
    unsigned char *pnt = buf;
    int len;

    while ((nxt = v >> 7)) {
        *(pnt++) = ((unsigned char)v) | 0x80;
        v = nxt;
    }
    *(pnt++) = (unsigned char)v;

    len = pnt - buf;
    fstFwrite(buf, len, 1, handle);
    return (len);
}

/* signed integer read/write routines are currently unused */
static int64_t fstGetSVarint64(unsigned char *mem, int *skiplen)
{
    unsigned char *mem_orig = mem;
    int64_t rc = 0;
    const int64_t one = 1;
    const int siz = sizeof(int64_t) * 8;
    int shift = 0;
    unsigned char byt;

    do {
        byt = *(mem++);
        rc |= ((int64_t)(byt & 0x7f)) << shift;
        shift += 7;

    } while (byt & 0x80);

    if ((shift < siz) && (byt & 0x40)) {
        rc |= -(one << shift); /* sign extend */
    }

    *skiplen = mem - mem_orig;

    return (rc);
}

#ifndef FST_DYNAMIC_ALIAS2_DISABLE
static int fstWriterSVarint(FILE *handle, int64_t v)
{
    unsigned char buf[15]; /* ceil(64/7) = 10 + sign byte padded way up */
    unsigned char byt;
    unsigned char *pnt = buf;
    int more = 1;
    int len;

    do {
        byt = v | 0x80;
        v >>= 7;

        if (((!v) && (!(byt & 0x40))) || ((v == -1) && (byt & 0x40))) {
            more = 0;
            byt &= 0x7f;
        }

        *(pnt++) = byt;
    } while (more);

    len = pnt - buf;
    fstFwrite(buf, len, 1, handle);
    return (len);
}
#endif

/***********************/
/***                 ***/
/*** writer function ***/
/***                 ***/
/***********************/

/*
 * private structs
 */
struct fstBlackoutChain
{
    struct fstBlackoutChain *next;
    uint64_t tim;
    unsigned active : 1;
};

struct fstWriterContext
{
    FILE *handle;
    FILE *hier_handle;
    FILE *geom_handle;
    FILE *valpos_handle;
    FILE *curval_handle;
    FILE *tchn_handle;

    unsigned char *vchg_mem;

    fst_off_t hier_file_len;

    uint32_t *valpos_mem;
    unsigned char *curval_mem;

    unsigned char *outval_mem; /* for two-state / Verilator-style value changes */
    uint32_t outval_alloc_siz;

    char *filename;

    fstHandle maxhandle;
    fstHandle numsigs;
    uint32_t maxvalpos;

    unsigned vc_emitted : 1;
    unsigned is_initial_time : 1;
    unsigned fourpack : 1;
    unsigned fastpack : 1;

    int64_t timezero;
    fst_off_t section_header_truncpos;
    uint32_t tchn_cnt, tchn_idx;
    uint64_t curtime;
    uint64_t firsttime;
    uint32_t vchg_siz;
    uint32_t vchg_alloc_siz;

    uint32_t secnum;
    fst_off_t section_start;

    uint32_t numscopes;
    double nan; /* nan value for uninitialized doubles */

    struct fstBlackoutChain *blackout_head;
    struct fstBlackoutChain *blackout_curr;
    uint32_t num_blackouts;

    uint64_t dump_size_limit;

    unsigned char filetype; /* default is 0, FST_FT_VERILOG */

    unsigned compress_hier : 1;
    unsigned repack_on_close : 1;
    unsigned skip_writing_section_hdr : 1;
    unsigned size_limit_locked : 1;
    unsigned section_header_only : 1;
    unsigned flush_context_pending : 1;
    unsigned parallel_enabled : 1;
    unsigned parallel_was_enabled : 1;

    /* should really be semaphores, but are bytes to cut down on read-modify-write window size */
    unsigned char already_in_flush; /* in case control-c handlers interrupt */
    unsigned char already_in_close; /* in case control-c handlers interrupt */

#ifdef FST_WRITER_PARALLEL
    pthread_mutex_t mutex;
    pthread_t thread;
    pthread_attr_t thread_attr;
    struct fstWriterContext *xc_parent;
#endif
    unsigned in_pthread : 1;

    size_t fst_orig_break_size;
    size_t fst_orig_break_add_size;

    size_t fst_break_size;
    size_t fst_break_add_size;

    size_t fst_huge_break_size;

    fstHandle next_huge_break;

    Pvoid_t path_array;
    uint32_t path_array_count;

    unsigned fseek_failed : 1;

    char *geom_handle_nam;
    char *valpos_handle_nam;
    char *curval_handle_nam;
    char *tchn_handle_nam;

    fstEnumHandle max_enumhandle;
};

static int fstWriterFseeko(struct fstWriterContext *xc, FILE *stream, fst_off_t offset, int whence)
{
    int rc = fseeko(stream, offset, whence);

    if (rc < 0) {
        xc->fseek_failed = 1;
#ifdef FST_DEBUG
        fprintf(stderr, FST_APIMESS "Seek to #%" PRId64 " (whence = %d) failed!\n", offset, whence);
        perror("Why");
#endif
    }

    return (rc);
}

static uint32_t fstWriterUint32WithVarint32(struct fstWriterContext *xc,
                                            uint32_t *u,
                                            uint32_t v,
                                            const void *dbuf,
                                            uint32_t siz)
{
    unsigned char *buf = xc->vchg_mem + xc->vchg_siz;
    unsigned char *pnt = buf;
    uint32_t nxt;
    uint32_t len;

    memcpy(pnt, u, sizeof(uint32_t));
    pnt += 4;

    while ((nxt = v >> 7)) {
        *(pnt++) = ((unsigned char)v) | 0x80;
        v = nxt;
    }
    *(pnt++) = (unsigned char)v;
    memcpy(pnt, dbuf, siz);

    len = pnt - buf + siz;
    return (len);
}

static uint32_t fstWriterUint32WithVarint32AndLength(struct fstWriterContext *xc,
                                                     uint32_t *u,
                                                     uint32_t v,
                                                     const void *dbuf,
                                                     uint32_t siz)
{
    unsigned char *buf = xc->vchg_mem + xc->vchg_siz;
    unsigned char *pnt = buf;
    uint32_t nxt;
    uint32_t len;

    memcpy(pnt, u, sizeof(uint32_t));
    pnt += 4;

    while ((nxt = v >> 7)) {
        *(pnt++) = ((unsigned char)v) | 0x80;
        v = nxt;
    }
    *(pnt++) = (unsigned char)v;

    v = siz;
    while ((nxt = v >> 7)) {
        *(pnt++) = ((unsigned char)v) | 0x80;
        v = nxt;
    }
    *(pnt++) = (unsigned char)v;

    memcpy(pnt, dbuf, siz);

    len = pnt - buf + siz;
    return (len);
}

/*
 * header bytes, write here so defines are set up before anything else
 * that needs to use them
 */
static void fstWriterEmitHdrBytes(struct fstWriterContext *xc)
{
    char vbuf[FST_HDR_SIM_VERSION_SIZE];
    char dbuf[FST_HDR_DATE_SIZE];
    double endtest = FST_DOUBLE_ENDTEST;
    time_t walltime;

#define FST_HDR_OFFS_TAG (0)
    fputc(FST_BL_HDR, xc->handle); /* +0 tag */

#define FST_HDR_OFFS_SECLEN (FST_HDR_OFFS_TAG + 1)
    fstWriterUint64(xc->handle, 329); /* +1 section length */

#define FST_HDR_OFFS_START_TIME (FST_HDR_OFFS_SECLEN + 8)
    fstWriterUint64(xc->handle, 0); /* +9 start time */

#define FST_HDR_OFFS_END_TIME (FST_HDR_OFFS_START_TIME + 8)
    fstWriterUint64(xc->handle, 0); /* +17 end time */

#define FST_HDR_OFFS_ENDIAN_TEST (FST_HDR_OFFS_END_TIME + 8)
    fstFwrite(&endtest, 8, 1, xc->handle); /* +25 endian test for reals */

#define FST_HDR_OFFS_MEM_USED (FST_HDR_OFFS_ENDIAN_TEST + 8)
    fstWriterUint64(xc->handle, xc->fst_break_size); /* +33 memory used by writer */

#define FST_HDR_OFFS_NUM_SCOPES (FST_HDR_OFFS_MEM_USED + 8)
    fstWriterUint64(xc->handle, 0); /* +41 scope creation count */

#define FST_HDR_OFFS_NUM_VARS (FST_HDR_OFFS_NUM_SCOPES + 8)
    fstWriterUint64(xc->handle, 0); /* +49 var creation count */

#define FST_HDR_OFFS_MAXHANDLE (FST_HDR_OFFS_NUM_VARS + 8)
    fstWriterUint64(xc->handle, 0); /* +57 max var idcode */

#define FST_HDR_OFFS_SECTION_CNT (FST_HDR_OFFS_MAXHANDLE + 8)
    fstWriterUint64(xc->handle, 0); /* +65 vc section count */

#define FST_HDR_OFFS_TIMESCALE (FST_HDR_OFFS_SECTION_CNT + 8)
    fputc((-9) & 255, xc->handle); /* +73 timescale 1ns */

#define FST_HDR_OFFS_SIM_VERSION (FST_HDR_OFFS_TIMESCALE + 1)
    memset(vbuf, 0, FST_HDR_SIM_VERSION_SIZE);
    strcpy(vbuf, FST_WRITER_STR);
    fstFwrite(vbuf, FST_HDR_SIM_VERSION_SIZE, 1, xc->handle); /* +74 version */

#define FST_HDR_OFFS_DATE (FST_HDR_OFFS_SIM_VERSION + FST_HDR_SIM_VERSION_SIZE)
    memset(dbuf, 0, FST_HDR_DATE_SIZE);
    time(&walltime);
    strcpy(dbuf, asctime(localtime(&walltime)));
    fstFwrite(dbuf, FST_HDR_DATE_SIZE, 1, xc->handle); /* +202 date */

    /* date size is deliberately overspecified at 119 bytes (originally 128) in order to provide
     * backfill for new args */

#define FST_HDR_OFFS_FILETYPE (FST_HDR_OFFS_DATE + FST_HDR_DATE_SIZE)
    fputc(xc->filetype, xc->handle); /* +321 filetype */

#define FST_HDR_OFFS_TIMEZERO (FST_HDR_OFFS_FILETYPE + FST_HDR_FILETYPE_SIZE)
    fstWriterUint64(xc->handle, xc->timezero); /* +322 timezero */

#define FST_HDR_LENGTH (FST_HDR_OFFS_TIMEZERO + FST_HDR_TIMEZERO_SIZE)
    /* +330 next section starts here */
    fflush(xc->handle);
}

/*
 * mmap functions
 */
static void fstWriterMmapSanity(void *pnt, const char *file, int line, const char *usage)
{
    if (pnt == NULL
#ifdef MAP_FAILED
        || pnt == MAP_FAILED
#endif
    ) {
        fprintf(stderr,
                "fstMmap() assigned to %s failed: errno: %d, file %s, line %d.\n",
                usage,
                errno,
                file,
                line);
#if !defined(__MINGW32__)
        perror("Why");
#else
        LPSTR mbuf = NULL;
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
                          FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL,
                      GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      (LPSTR)&mbuf,
                      0,
                      NULL);
        fprintf(stderr, "%s", mbuf);
        LocalFree(mbuf);
#endif
        pnt = NULL;
    }
}

static void fstWriterCreateMmaps(struct fstWriterContext *xc)
{
    fst_off_t curpos = ftello(xc->handle);

    fflush(xc->hier_handle);

    /* write out intermediate header */
    fstWriterFseeko(xc, xc->handle, FST_HDR_OFFS_START_TIME, SEEK_SET);
    fstWriterUint64(xc->handle, xc->firsttime);
    fstWriterUint64(xc->handle, xc->curtime);
    fstWriterFseeko(xc, xc->handle, FST_HDR_OFFS_NUM_SCOPES, SEEK_SET);
    fstWriterUint64(xc->handle, xc->numscopes);
    fstWriterUint64(xc->handle, xc->numsigs);
    fstWriterUint64(xc->handle, xc->maxhandle);
    fstWriterUint64(xc->handle, xc->secnum);
    fstWriterFseeko(xc, xc->handle, curpos, SEEK_SET);
    fflush(xc->handle);

    /* do mappings */
    if (!xc->valpos_mem) {
        fflush(xc->valpos_handle);
        errno = 0;
        if (xc->maxhandle) {
            fstWriterMmapSanity(xc->valpos_mem =
                                    (uint32_t *)fstMmap(NULL,
                                                        xc->maxhandle * 4 * sizeof(uint32_t),
                                                        PROT_READ | PROT_WRITE,
                                                        MAP_SHARED,
                                                        fileno(xc->valpos_handle),
                                                        0),
                                __FILE__,
                                __LINE__,
                                "xc->valpos_mem");
        }
    }
    if (!xc->curval_mem) {
        fflush(xc->curval_handle);
        errno = 0;
        if (xc->maxvalpos) {
            fstWriterMmapSanity(xc->curval_mem = (unsigned char *)fstMmap(NULL,
                                                                          xc->maxvalpos,
                                                                          PROT_READ | PROT_WRITE,
                                                                          MAP_SHARED,
                                                                          fileno(xc->curval_handle),
                                                                          0),
                                __FILE__,
                                __LINE__,
                                "xc->curval_handle");
        }
    }
}

static void fstDestroyMmaps(struct fstWriterContext *xc, int is_closing)
{
    (void)is_closing;

    fstMunmap(xc->valpos_mem, xc->maxhandle * 4 * sizeof(uint32_t));
    xc->valpos_mem = NULL;

    fstMunmap(xc->curval_mem, xc->maxvalpos);
    xc->curval_mem = NULL;
}

/*
 * set up large and small memory usages
 * crossover point in model is FST_ACTIVATE_HUGE_BREAK number of signals
 */
static void fstDetermineBreakSize(struct fstWriterContext *xc)
{
#if defined(__linux__) || defined(FST_MACOSX)
    int was_set = 0;

#ifdef __linux__
    FILE *f = fopen("/proc/meminfo", "rb");

    if (f) {
        char buf[257];
        char *s;
        while (!feof(f)) {
            buf[0] = 0;
            s = fgets(buf, 256, f);
            if (s && *s) {
                if (!strncmp(s, "MemTotal:", 9)) {
                    size_t v = atol(s + 10);
                    v *= 1024; /* convert to bytes */
                    v /= 8; /* chop down to 1/8 physical memory */
                    if (v > FST_BREAK_SIZE) {
                        if (v > FST_BREAK_SIZE_MAX) {
                            v = FST_BREAK_SIZE_MAX;
                        }

                        xc->fst_huge_break_size = v;
                        was_set = 1;
                        break;
                    }
                }
            }
        }

        fclose(f);
    }

    if (!was_set) {
        xc->fst_huge_break_size = FST_BREAK_SIZE;
    }
#else
    int mib[2];
    int64_t v;
    size_t length;

    mib[0] = CTL_HW;
    mib[1] = HW_MEMSIZE;
    length = sizeof(int64_t);
    if (!sysctl(mib, 2, &v, &length, NULL, 0)) {
        v /= 8;

        if (v > (int64_t)FST_BREAK_SIZE) {
            if (v > (int64_t)FST_BREAK_SIZE_MAX) {
                v = FST_BREAK_SIZE_MAX;
            }

            xc->fst_huge_break_size = v;
            was_set = 1;
        }
    }

    if (!was_set) {
        xc->fst_huge_break_size = FST_BREAK_SIZE;
    }
#endif
#else
    xc->fst_huge_break_size = FST_BREAK_SIZE;
#endif

    xc->fst_break_size = xc->fst_orig_break_size = FST_BREAK_SIZE;
    xc->fst_break_add_size = xc->fst_orig_break_add_size = FST_BREAK_ADD_SIZE;
    xc->next_huge_break = FST_ACTIVATE_HUGE_BREAK;
}

/*
 * file creation and close
 */
fstWriterContext *fstWriterCreate(const char *nam, int use_compressed_hier)
{
    fstWriterContext *xc = (fstWriterContext *)calloc(1, sizeof(fstWriterContext));

    xc->compress_hier = use_compressed_hier;
    fstDetermineBreakSize(xc);

    if ((!nam) || (!(xc->handle = unlink_fopen(nam, "w+b")))) {
        free(xc);
        xc = NULL;
    } else {
        int flen = strlen(nam);
        char *hf = (char *)calloc(1, flen + 6);

        memcpy(hf, nam, flen);
        strcpy(hf + flen, ".hier");
        xc->hier_handle = unlink_fopen(hf, "w+b");

        xc->geom_handle = tmpfile_open(&xc->geom_handle_nam); /* .geom */
        xc->valpos_handle = tmpfile_open(&xc->valpos_handle_nam); /* .offs */
        xc->curval_handle = tmpfile_open(&xc->curval_handle_nam); /* .bits */
        xc->tchn_handle = tmpfile_open(&xc->tchn_handle_nam); /* .tchn */
        xc->vchg_alloc_siz = xc->fst_break_size + xc->fst_break_add_size;
        xc->vchg_mem = (unsigned char *)malloc(xc->vchg_alloc_siz);

        if (xc->hier_handle && xc->geom_handle && xc->valpos_handle && xc->curval_handle &&
            xc->vchg_mem && xc->tchn_handle) {
            xc->filename = strdup(nam);
            xc->is_initial_time = 1;

            fstWriterEmitHdrBytes(xc);
            xc->nan = strtod("NaN", NULL);
#ifdef FST_WRITER_PARALLEL
            pthread_mutex_init(&xc->mutex, NULL);
            pthread_attr_init(&xc->thread_attr);
            pthread_attr_setdetachstate(&xc->thread_attr, PTHREAD_CREATE_DETACHED);
#endif
        } else {
            fclose(xc->handle);
            if (xc->hier_handle) {
                fclose(xc->hier_handle);
                unlink(hf);
            }
            tmpfile_close(&xc->geom_handle, &xc->geom_handle_nam);
            tmpfile_close(&xc->valpos_handle, &xc->valpos_handle_nam);
            tmpfile_close(&xc->curval_handle, &xc->curval_handle_nam);
            tmpfile_close(&xc->tchn_handle, &xc->tchn_handle_nam);
            free(xc->vchg_mem);
            free(xc);
            xc = NULL;
        }

        free(hf);
    }

    return (xc);
}

/*
 * generation and writing out of value change data sections
 */
static void fstWriterEmitSectionHeader(fstWriterContext *xc)
{
    if (xc) {
        unsigned long destlen;
        unsigned char *dmem;
        int rc;

        destlen = xc->maxvalpos;
        dmem = (unsigned char *)malloc(compressBound(destlen));
        rc = compress2(dmem,
                       &destlen,
                       xc->curval_mem,
                       xc->maxvalpos,
                       4); /* was 9...which caused performance drag on traces with many signals */

        fputc(FST_BL_SKIP,
              xc->handle); /* temporarily tag the section, use FST_BL_VCDATA on finalize */
        xc->section_start = ftello(xc->handle);
#ifdef FST_WRITER_PARALLEL
        if (xc->xc_parent)
            xc->xc_parent->section_start = xc->section_start;
#endif
        xc->section_header_only = 1; /* indicates truncate might be needed */
        fstWriterUint64(xc->handle, 0); /* placeholder = section length */
        fstWriterUint64(xc->handle,
                        xc->is_initial_time ? xc->firsttime
                                            : xc->curtime); /* begin time of section */
        fstWriterUint64(xc->handle, xc->curtime); /* end time of section (placeholder) */
        fstWriterUint64(
            xc->handle,
            0); /* placeholder = amount of buffer memory required in reader for full vc traversal */
        fstWriterVarint(xc->handle, xc->maxvalpos); /* maxvalpos = length of uncompressed data */

        if ((rc == Z_OK) && (destlen < xc->maxvalpos)) {
            fstWriterVarint(xc->handle, destlen); /* length of compressed data */
        } else {
            fstWriterVarint(xc->handle,
                            xc->maxvalpos); /* length of (unable to be) compressed data */
        }
        fstWriterVarint(xc->handle, xc->maxhandle); /* max handle associated with this data (in case
                                                       of dynamic facility adds) */

        if ((rc == Z_OK) && (destlen < xc->maxvalpos)) {
            fstFwrite(dmem, destlen, 1, xc->handle);
        } else /* comparison between compressed / decompressed len tells if compressed */
        {
            fstFwrite(xc->curval_mem, xc->maxvalpos, 1, xc->handle);
        }

        free(dmem);
    }
}

/*
 * only to be called directly by fst code...otherwise must
 * be synced up with time changes
 */
#ifdef FST_WRITER_PARALLEL
static void fstWriterFlushContextPrivate2(fstWriterContext *xc)
#else
static void fstWriterFlushContextPrivate(fstWriterContext *xc)
#endif
{
#ifdef FST_DEBUG
    int cnt = 0;
#endif
    unsigned int i;
    unsigned char *vchg_mem;
    FILE *f;
    fst_off_t fpos, indxpos, endpos;
    uint32_t prevpos;
    int zerocnt;
    unsigned char *scratchpad;
    unsigned char *scratchpnt;
    unsigned char *tmem;
    fst_off_t tlen;
    fst_off_t unc_memreq = 0; /* for reader */
    unsigned char *packmem;
    unsigned int packmemlen;
    uint32_t *vm4ip;
#ifdef FST_WRITER_PARALLEL
    struct fstWriterContext *xc2 = xc->xc_parent;
#else
    struct fstWriterContext *xc2 = xc;
#endif

#ifndef FST_DYNAMIC_ALIAS_DISABLE
    Pvoid_t PJHSArray = (Pvoid_t)NULL;
    uint32_t hashmask = xc->maxhandle;
    hashmask |= hashmask >> 1;
    hashmask |= hashmask >> 2;
    hashmask |= hashmask >> 4;
    hashmask |= hashmask >> 8;
    hashmask |= hashmask >> 16;
#endif

    if ((xc->vchg_siz <= 1) || (xc->already_in_flush))
        return;
    xc->already_in_flush = 1; /* should really do this with a semaphore */

    xc->section_header_only = 0;
    scratchpad = (unsigned char *)malloc(xc->vchg_siz);

    vchg_mem = xc->vchg_mem;

    f = xc->handle;
    fstWriterVarint(f, xc->maxhandle); /* emit current number of handles */
    fputc(xc->fourpack ? '4' : (xc->fastpack ? 'F' : 'Z'), f);
    fpos = 1;

    packmemlen = 1024; /* maintain a running "longest" allocation to */
    packmem =
        (unsigned char *)malloc(packmemlen); /* prevent continual malloc...free every loop iter */

    for (i = 0; i < xc->maxhandle; i++) {
        vm4ip = &(xc->valpos_mem[4 * i]);

        if (vm4ip[2]) {
            uint32_t offs = vm4ip[2];
            uint32_t next_offs;
            unsigned int wrlen;

            vm4ip[2] = fpos;

            scratchpnt = scratchpad + xc->vchg_siz; /* build this buffer backwards */
            if (vm4ip[1] <= 1) {
                if (vm4ip[1] == 1) {
                    wrlen = fstGetVarint32Length(vchg_mem + offs +
                                                 4); /* used to advance and determine wrlen */
#ifndef FST_REMOVE_DUPLICATE_VC
                    xc->curval_mem[vm4ip[0]] = vchg_mem[offs + 4 + wrlen]; /* checkpoint variable */
#endif
                    while (offs) {
                        unsigned char val;
                        uint32_t time_delta, rcv;
                        next_offs = fstGetUint32(vchg_mem + offs);
                        offs += 4;

                        time_delta = fstGetVarint32(vchg_mem + offs, (int *)&wrlen);
                        val = vchg_mem[offs + wrlen];
                        offs = next_offs;

                        switch (val) {
                            case '0':
                            case '1':
                                rcv = ((val & 1) << 1) | (time_delta << 2);
                                break; /* pack more delta bits in for 0/1 vchs */

                            case 'x':
                            case 'X':
                                rcv = FST_RCV_X | (time_delta << 4);
                                break;
                            case 'z':
                            case 'Z':
                                rcv = FST_RCV_Z | (time_delta << 4);
                                break;
                            case 'h':
                            case 'H':
                                rcv = FST_RCV_H | (time_delta << 4);
                                break;
                            case 'u':
                            case 'U':
                                rcv = FST_RCV_U | (time_delta << 4);
                                break;
                            case 'w':
                            case 'W':
                                rcv = FST_RCV_W | (time_delta << 4);
                                break;
                            case 'l':
                            case 'L':
                                rcv = FST_RCV_L | (time_delta << 4);
                                break;
                            default:
                                rcv = FST_RCV_D | (time_delta << 4);
                                break;
                        }

                        scratchpnt = fstCopyVarint32ToLeft(scratchpnt, rcv);
                    }
                } else {
                    /* variable length */
                    /* fstGetUint32 (next_offs) + fstGetVarint32 (time_delta) + fstGetVarint32 (len)
                     * + payload */
                    unsigned char *pnt;
                    uint32_t record_len;
                    uint32_t time_delta;

                    while (offs) {
                        next_offs = fstGetUint32(vchg_mem + offs);
                        offs += 4;
                        pnt = vchg_mem + offs;
                        offs = next_offs;
                        time_delta = fstGetVarint32(pnt, (int *)&wrlen);
                        pnt += wrlen;
                        record_len = fstGetVarint32(pnt, (int *)&wrlen);
                        pnt += wrlen;

                        scratchpnt -= record_len;
                        memcpy(scratchpnt, pnt, record_len);

                        scratchpnt = fstCopyVarint32ToLeft(scratchpnt, record_len);
                        scratchpnt = fstCopyVarint32ToLeft(
                            scratchpnt,
                            (time_delta << 1)); /* reserve | 1 case for future expansion */
                    }
                }
            } else {
                wrlen = fstGetVarint32Length(vchg_mem + offs +
                                             4); /* used to advance and determine wrlen */
#ifndef FST_REMOVE_DUPLICATE_VC
                memcpy(xc->curval_mem + vm4ip[0],
                       vchg_mem + offs + 4 + wrlen,
                       vm4ip[1]); /* checkpoint variable */
#endif
                while (offs) {
                    unsigned int idx;
                    char is_binary = 1;
                    unsigned char *pnt;
                    uint32_t time_delta;

                    next_offs = fstGetUint32(vchg_mem + offs);
                    offs += 4;

                    time_delta = fstGetVarint32(vchg_mem + offs, (int *)&wrlen);

                    pnt = vchg_mem + offs + wrlen;
                    offs = next_offs;

                    for (idx = 0; idx < vm4ip[1]; idx++) {
                        if ((pnt[idx] == '0') || (pnt[idx] == '1')) {
                            continue;
                        } else {
                            is_binary = 0;
                            break;
                        }
                    }

                    if (is_binary) {
                        unsigned char acc = 0;
                        /* new algorithm */
                        idx = ((vm4ip[1] + 7) & ~7);
                        switch (vm4ip[1] & 7) {
                            case 0:
                                do {
                                    acc = (pnt[idx + 7 - 8] & 1) << 0; /* fallthrough */
                                    case 7:
                                        acc |= (pnt[idx + 6 - 8] & 1) << 1; /* fallthrough */
                                    case 6:
                                        acc |= (pnt[idx + 5 - 8] & 1) << 2; /* fallthrough */
                                    case 5:
                                        acc |= (pnt[idx + 4 - 8] & 1) << 3; /* fallthrough */
                                    case 4:
                                        acc |= (pnt[idx + 3 - 8] & 1) << 4; /* fallthrough */
                                    case 3:
                                        acc |= (pnt[idx + 2 - 8] & 1) << 5; /* fallthrough */
                                    case 2:
                                        acc |= (pnt[idx + 1 - 8] & 1) << 6; /* fallthrough */
                                    case 1:
                                        acc |= (pnt[idx + 0 - 8] & 1) << 7;
                                        *(--scratchpnt) = acc;
                                        idx -= 8;
                                } while (idx);
                        }

                        scratchpnt = fstCopyVarint32ToLeft(scratchpnt, (time_delta << 1));
                    } else {
                        scratchpnt -= vm4ip[1];
                        memcpy(scratchpnt, pnt, vm4ip[1]);

                        scratchpnt = fstCopyVarint32ToLeft(scratchpnt, (time_delta << 1) | 1);
                    }
                }
            }

            wrlen = scratchpad + xc->vchg_siz - scratchpnt;
            unc_memreq += wrlen;
            if (wrlen > 32) {
                unsigned long destlen = wrlen;
                unsigned char *dmem;
                unsigned int rc;

                if (!xc->fastpack) {
                    if (wrlen <= packmemlen) {
                        dmem = packmem;
                    } else {
                        free(packmem);
                        dmem = packmem = (unsigned char *)malloc(compressBound(packmemlen = wrlen));
                    }

                    rc = compress2(dmem, &destlen, scratchpnt, wrlen, 4);
                    if (rc == Z_OK) {
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                        PPvoid_t pv = JenkinsIns(&PJHSArray, dmem, destlen, hashmask);
                        if (*pv) {
                            uint32_t pvi = (intptr_t)(*pv);
                            vm4ip[2] = -pvi;
                        } else {
                            *pv = (void *)(intptr_t)(i + 1);
#endif
                            fpos += fstWriterVarint(f, wrlen);
                            fpos += destlen;
                            fstFwrite(dmem, destlen, 1, f);
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                        }
#endif
                    } else {
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                        PPvoid_t pv = JenkinsIns(&PJHSArray, scratchpnt, wrlen, hashmask);
                        if (*pv) {
                            uint32_t pvi = (intptr_t)(*pv);
                            vm4ip[2] = -pvi;
                        } else {
                            *pv = (void *)(intptr_t)(i + 1);
#endif
                            fpos += fstWriterVarint(f, 0);
                            fpos += wrlen;
                            fstFwrite(scratchpnt, wrlen, 1, f);
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                        }
#endif
                    }
                } else {
                    /* this is extremely conservative: fastlz needs +5% for worst case, lz4 needs
                     * siz+(siz/255)+16 */
                    if (((wrlen * 2) + 2) <= packmemlen) {
                        dmem = packmem;
                    } else {
                        free(packmem);
                        dmem = packmem = (unsigned char *)malloc(packmemlen = (wrlen * 2) + 2);
                    }

                    rc = (xc->fourpack) ? LZ4_compress_default((char *)scratchpnt,
                                                               (char *)dmem,
                                                               wrlen,
                                                               packmemlen)
                                        : fastlz_compress(scratchpnt, wrlen, dmem);
                    if (rc < destlen) {
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                        PPvoid_t pv = JenkinsIns(&PJHSArray, dmem, rc, hashmask);
                        if (*pv) {
                            uint32_t pvi = (intptr_t)(*pv);
                            vm4ip[2] = -pvi;
                        } else {
                            *pv = (void *)(intptr_t)(i + 1);
#endif
                            fpos += fstWriterVarint(f, wrlen);
                            fpos += rc;
                            fstFwrite(dmem, rc, 1, f);
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                        }
#endif
                    } else {
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                        PPvoid_t pv = JenkinsIns(&PJHSArray, scratchpnt, wrlen, hashmask);
                        if (*pv) {
                            uint32_t pvi = (intptr_t)(*pv);
                            vm4ip[2] = -pvi;
                        } else {
                            *pv = (void *)(intptr_t)(i + 1);
#endif
                            fpos += fstWriterVarint(f, 0);
                            fpos += wrlen;
                            fstFwrite(scratchpnt, wrlen, 1, f);
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                        }
#endif
                    }
                }
            } else {
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                PPvoid_t pv = JenkinsIns(&PJHSArray, scratchpnt, wrlen, hashmask);
                if (*pv) {
                    uint32_t pvi = (intptr_t)(*pv);
                    vm4ip[2] = -pvi;
                } else {
                    *pv = (void *)(intptr_t)(i + 1);
#endif
                    fpos += fstWriterVarint(f, 0);
                    fpos += wrlen;
                    fstFwrite(scratchpnt, wrlen, 1, f);
#ifndef FST_DYNAMIC_ALIAS_DISABLE
                }
#endif
            }

            /* vm4ip[3] = 0; ...redundant with clearing below */
#ifdef FST_DEBUG
            cnt++;
#endif
        }
    }

#ifndef FST_DYNAMIC_ALIAS_DISABLE
    JenkinsFree(&PJHSArray, hashmask);
#endif

    free(packmem);
    packmem = NULL; /* packmemlen = 0; */ /* scan-build */

    prevpos = 0;
    zerocnt = 0;
    free(scratchpad);
    scratchpad = NULL;

    indxpos = ftello(f);
    xc->secnum++;

#ifndef FST_DYNAMIC_ALIAS2_DISABLE
    if (1) {
        uint32_t prev_alias = 0;

        for (i = 0; i < xc->maxhandle; i++) {
            vm4ip = &(xc->valpos_mem[4 * i]);

            if (vm4ip[2]) {
                if (zerocnt) {
                    fpos += fstWriterVarint(f, (zerocnt << 1));
                    zerocnt = 0;
                }

                if (vm4ip[2] & 0x80000000) {
                    if (vm4ip[2] != prev_alias) {
                        int32_t t_i32 =
                            ((int32_t)(prev_alias = vm4ip[2])); /* vm4ip is generic unsigned data */
                        int64_t t_i64 = (int64_t)t_i32; /* convert to signed */
                        uint64_t t_u64 = (uint64_t)t_i64; /* sign extend through 64b */

                        fpos += fstWriterSVarint(
                            f,
                            (int64_t)((t_u64 << 1) |
                                      1)); /* all in this block was: fpos += fstWriterSVarint(f,
                                              (((int64_t)((int32_t)(prev_alias = vm4ip[2]))) << 1) |
                                              1); */
                    } else {
                        fpos += fstWriterSVarint(f, (0 << 1) | 1);
                    }
                } else {
                    fpos += fstWriterSVarint(f, ((vm4ip[2] - prevpos) << 1) | 1);
                    prevpos = vm4ip[2];
                }
                vm4ip[2] = 0;
                vm4ip[3] = 0; /* clear out tchn idx */
            } else {
                zerocnt++;
            }
        }
    } else
#endif
    {
        for (i = 0; i < xc->maxhandle; i++) {
            vm4ip = &(xc->valpos_mem[4 * i]);

            if (vm4ip[2]) {
                if (zerocnt) {
                    fpos += fstWriterVarint(f, (zerocnt << 1));
                    zerocnt = 0;
                }

                if (vm4ip[2] & 0x80000000) {
                    fpos +=
                        fstWriterVarint(f, 0); /* signal, note that using a *signed* varint would be
                                                  more efficient than this byte escape! */
                    fpos += fstWriterVarint(f, (-(int32_t)vm4ip[2]));
                } else {
                    fpos += fstWriterVarint(f, ((vm4ip[2] - prevpos) << 1) | 1);
                    prevpos = vm4ip[2];
                }
                vm4ip[2] = 0;
                vm4ip[3] = 0; /* clear out tchn idx */
            } else {
                zerocnt++;
            }
        }
    }

    if (zerocnt) {
        /* fpos += */ fstWriterVarint(f, (zerocnt << 1)); /* scan-build */
    }
#ifdef FST_DEBUG
    fprintf(stderr, FST_APIMESS "value chains: %d\n", cnt);
#endif

    xc->vchg_mem[0] = '!';
    xc->vchg_siz = 1;

    endpos = ftello(xc->handle);
    fstWriterUint64(xc->handle,
                    endpos - indxpos); /* write delta index position at very end of block */

    /*emit time changes for block */
    fflush(xc->tchn_handle);
    tlen = ftello(xc->tchn_handle);
    fstWriterFseeko(xc, xc->tchn_handle, 0, SEEK_SET);

    errno = 0;
    fstWriterMmapSanity(
        tmem = (unsigned char *)
            fstMmap(NULL, tlen, PROT_READ | PROT_WRITE, MAP_SHARED, fileno(xc->tchn_handle), 0),
        __FILE__,
        __LINE__,
        "tmem");
    if (tmem) {
        unsigned long destlen = tlen;
        unsigned char *dmem = (unsigned char *)malloc(compressBound(destlen));
        int rc = compress2(dmem, &destlen, tmem, tlen, 9);

        if ((rc == Z_OK) && (((fst_off_t)destlen) < tlen)) {
            fstFwrite(dmem, destlen, 1, xc->handle);
        } else /* comparison between compressed / decompressed len tells if compressed */
        {
            fstFwrite(tmem, tlen, 1, xc->handle);
            destlen = tlen;
        }
        free(dmem);
        fstMunmap(tmem, tlen);
        fstWriterUint64(xc->handle, tlen); /* uncompressed */
        fstWriterUint64(xc->handle, destlen); /* compressed */
        fstWriterUint64(xc->handle, xc->tchn_cnt); /* number of time items */
    }

    xc->tchn_cnt = xc->tchn_idx = 0;
    fstWriterFseeko(xc, xc->tchn_handle, 0, SEEK_SET);
    fstFtruncate(fileno(xc->tchn_handle), 0);

    /* write block trailer */
    endpos = ftello(xc->handle);
    fstWriterFseeko(xc, xc->handle, xc->section_start, SEEK_SET);
    fstWriterUint64(xc->handle, endpos - xc->section_start); /* write block length */
    fstWriterFseeko(xc, xc->handle, 8, SEEK_CUR); /* skip begin time */
    fstWriterUint64(xc->handle, xc->curtime); /* write end time for section */
    fstWriterUint64(xc->handle,
                    unc_memreq); /* amount of buffer memory required in reader for full traversal */
    fflush(xc->handle);

    fstWriterFseeko(xc,
                    xc->handle,
                    xc->section_start - 1,
                    SEEK_SET); /* write out FST_BL_VCDATA over FST_BL_SKIP */

#ifndef FST_DYNAMIC_ALIAS_DISABLE
#ifndef FST_DYNAMIC_ALIAS2_DISABLE
    fputc(FST_BL_VCDATA_DYN_ALIAS2, xc->handle);
#else
    fputc(FST_BL_VCDATA_DYN_ALIAS, xc->handle);
#endif
#else
    fputc(FST_BL_VCDATA, xc->handle);
#endif

    fflush(xc->handle);

    fstWriterFseeko(xc, xc->handle, endpos, SEEK_SET); /* seek to end of file */

    xc2->section_header_truncpos = endpos; /* cache in case of need to truncate */
    if (xc->dump_size_limit) {
        if (endpos >= ((fst_off_t)xc->dump_size_limit)) {
            xc2->skip_writing_section_hdr = 1;
            xc2->size_limit_locked = 1;
            xc2->is_initial_time = 1; /* to trick emit value and emit time change */
#ifdef FST_DEBUG
            fprintf(stderr, FST_APIMESS "<< dump file size limit reached, stopping dumping >>\n");
#endif
        }
    }

    if (!xc2->skip_writing_section_hdr) {
        fstWriterEmitSectionHeader(xc); /* emit next section header */
    }
    fflush(xc->handle);

    xc->already_in_flush = 0;
}

#ifdef FST_WRITER_PARALLEL
static void *fstWriterFlushContextPrivate1(void *ctx)
{
    struct fstWriterContext *xc = (struct fstWriterContext *)ctx;
    struct fstWriterContext *xc_parent;

    pthread_mutex_lock(&(xc->xc_parent->mutex));
    fstWriterFlushContextPrivate2(xc);

#ifdef FST_REMOVE_DUPLICATE_VC
    free(xc->curval_mem);
#endif
    free(xc->valpos_mem);
    free(xc->vchg_mem);
    tmpfile_close(&xc->tchn_handle, &xc->tchn_handle_nam);
    xc_parent = xc->xc_parent;
    free(xc);

    xc_parent->in_pthread = 0;
    pthread_mutex_unlock(&(xc_parent->mutex));

    return (NULL);
}

static void fstWriterFlushContextPrivate(fstWriterContext *xc)
{
    if (xc->parallel_enabled) {
        struct fstWriterContext *xc2 =
            (struct fstWriterContext *)malloc(sizeof(struct fstWriterContext));
        unsigned int i;

        pthread_mutex_lock(&xc->mutex);
        pthread_mutex_unlock(&xc->mutex);

        xc->xc_parent = xc;
        memcpy(xc2, xc, sizeof(struct fstWriterContext));

        if (sizeof(size_t) < sizeof(uint64_t)) {
            /* TALOS-2023-1777 for 32b overflow */
            uint64_t chk_64 = xc->maxhandle * 4 * sizeof(uint32_t);
            size_t chk_32 = xc->maxhandle * 4 * sizeof(uint32_t);
            if (chk_64 != chk_32)
                chk_report_abort("TALOS-2023-1777");
        }

        xc2->valpos_mem = (uint32_t *)malloc(xc->maxhandle * 4 * sizeof(uint32_t));
        memcpy(xc2->valpos_mem, xc->valpos_mem, xc->maxhandle * 4 * sizeof(uint32_t));

        /* curval mem is updated in the thread */
#ifdef FST_REMOVE_DUPLICATE_VC
        xc2->curval_mem = (unsigned char *)malloc(xc->maxvalpos);
        memcpy(xc2->curval_mem, xc->curval_mem, xc->maxvalpos);
#endif

        xc->vchg_mem = (unsigned char *)malloc(xc->vchg_alloc_siz);
        xc->vchg_mem[0] = '!';
        xc->vchg_siz = 1;

        for (i = 0; i < xc->maxhandle; i++) {
            uint32_t *vm4ip = &(xc->valpos_mem[4 * i]);
            vm4ip[2] = 0; /* zero out offset val */
            vm4ip[3] = 0; /* zero out last time change val */
        }

        xc->tchn_cnt = xc->tchn_idx = 0;
        xc->tchn_handle =
            tmpfile_open(&xc->tchn_handle_nam); /* child thread will deallocate file/name */
        fstWriterFseeko(xc, xc->tchn_handle, 0, SEEK_SET);
        fstFtruncate(fileno(xc->tchn_handle), 0);

        xc->section_header_only = 0;
        xc->secnum++;

        while (xc->in_pthread) {
            pthread_mutex_lock(&xc->mutex);
            pthread_mutex_unlock(&xc->mutex);
        };

        pthread_mutex_lock(&xc->mutex);
        xc->in_pthread = 1;
        pthread_mutex_unlock(&xc->mutex);

        pthread_create(&xc->thread, &xc->thread_attr, fstWriterFlushContextPrivate1, xc2);
    } else {
        if (xc->parallel_was_enabled) /* conservatively block */
        {
            pthread_mutex_lock(&xc->mutex);
            pthread_mutex_unlock(&xc->mutex);
        }

        xc->xc_parent = xc;
        fstWriterFlushContextPrivate2(xc);
    }
}
#endif

/*
 * queues up a flush context operation
 */
void fstWriterFlushContext(fstWriterContext *xc)
{
    if (xc) {
        if (xc->tchn_idx > 1) {
            xc->flush_context_pending = 1;
        }
    }
}

/*
 * close out FST file
 */
void fstWriterClose(fstWriterContext *xc)
{
#ifdef FST_WRITER_PARALLEL
    if (xc) {
        pthread_mutex_lock(&xc->mutex);
        pthread_mutex_unlock(&xc->mutex);
    }
#endif

    if (xc && !xc->already_in_close && !xc->already_in_flush) {
        unsigned char *tmem = NULL;
        fst_off_t fixup_offs, tlen, hlen;

        xc->already_in_close = 1; /* never need to zero this out as it is freed at bottom */

        if (xc->section_header_only && xc->section_header_truncpos && (xc->vchg_siz <= 1) &&
            (!xc->is_initial_time)) {
            fstFtruncate(fileno(xc->handle), xc->section_header_truncpos);
            fstWriterFseeko(xc, xc->handle, xc->section_header_truncpos, SEEK_SET);
            xc->section_header_only = 0;
        } else {
            xc->skip_writing_section_hdr = 1;
            if (!xc->size_limit_locked) {
                if (FST_UNLIKELY(xc->is_initial_time)) /* simulation time never advanced so mock up
                                                          the changes as time zero ones */
                {
                    fstHandle dupe_idx;

                    fstWriterEmitTimeChange(xc, 0); /* emit some time change just to have one */
                    for (dupe_idx = 0; dupe_idx < xc->maxhandle;
                         dupe_idx++) /* now clone the values */
                    {
                        fstWriterEmitValueChange(xc,
                                                 dupe_idx + 1,
                                                 xc->curval_mem + xc->valpos_mem[4 * dupe_idx]);
                    }
                }
                fstWriterFlushContextPrivate(xc);
#ifdef FST_WRITER_PARALLEL
                pthread_mutex_lock(&xc->mutex);
                pthread_mutex_unlock(&xc->mutex);

                while (xc->in_pthread) {
                    pthread_mutex_lock(&xc->mutex);
                    pthread_mutex_unlock(&xc->mutex);
                };
#endif
            }
        }
        fstDestroyMmaps(xc, 1);
        if (xc->outval_mem) {
            free(xc->outval_mem);
            xc->outval_mem = NULL;
            xc->outval_alloc_siz = 0;
        }

        /* write out geom section */
        fflush(xc->geom_handle);
        tlen = ftello(xc->geom_handle);
        errno = 0;
        if (tlen) {
            fstWriterMmapSanity(tmem = (unsigned char *)fstMmap(NULL,
                                                                tlen,
                                                                PROT_READ | PROT_WRITE,
                                                                MAP_SHARED,
                                                                fileno(xc->geom_handle),
                                                                0),
                                __FILE__,
                                __LINE__,
                                "tmem");
        }

        if (tmem) {
            unsigned long destlen = tlen;
            unsigned char *dmem = (unsigned char *)malloc(compressBound(destlen));
            int rc = compress2(dmem, &destlen, tmem, tlen, 9);

            if ((rc != Z_OK) || (((fst_off_t)destlen) > tlen)) {
                destlen = tlen;
            }

            fixup_offs = ftello(xc->handle);
            fputc(FST_BL_SKIP, xc->handle); /* temporary tag */
            fstWriterUint64(xc->handle, destlen + 24); /* section length */
            fstWriterUint64(xc->handle, tlen); /* uncompressed */
            /* compressed len is section length - 24 */
            fstWriterUint64(xc->handle, xc->maxhandle); /* maxhandle */
            fstFwrite((((fst_off_t)destlen) != tlen) ? dmem : tmem, destlen, 1, xc->handle);
            fflush(xc->handle);

            fstWriterFseeko(xc, xc->handle, fixup_offs, SEEK_SET);
            fputc(FST_BL_GEOM, xc->handle); /* actual tag */

            fstWriterFseeko(xc,
                            xc->handle,
                            0,
                            SEEK_END); /* move file pointer to end for any section adds */
            fflush(xc->handle);

            free(dmem);
            fstMunmap(tmem, tlen);
        }

        if (xc->num_blackouts) {
            uint64_t cur_bl = 0;
            fst_off_t bpos, eos;
            uint32_t i;

            fixup_offs = ftello(xc->handle);
            fputc(FST_BL_SKIP, xc->handle); /* temporary tag */
            bpos = fixup_offs + 1;
            fstWriterUint64(xc->handle, 0); /* section length */
            fstWriterVarint(xc->handle, xc->num_blackouts);

            for (i = 0; i < xc->num_blackouts; i++) {
                fputc(xc->blackout_head->active, xc->handle);
                fstWriterVarint(xc->handle, xc->blackout_head->tim - cur_bl);
                cur_bl = xc->blackout_head->tim;
                xc->blackout_curr = xc->blackout_head->next;
                free(xc->blackout_head);
                xc->blackout_head = xc->blackout_curr;
            }

            eos = ftello(xc->handle);
            fstWriterFseeko(xc, xc->handle, bpos, SEEK_SET);
            fstWriterUint64(xc->handle, eos - bpos);
            fflush(xc->handle);

            fstWriterFseeko(xc, xc->handle, fixup_offs, SEEK_SET);
            fputc(FST_BL_BLACKOUT, xc->handle); /* actual tag */

            fstWriterFseeko(xc,
                            xc->handle,
                            0,
                            SEEK_END); /* move file pointer to end for any section adds */
            fflush(xc->handle);
        }

        if (xc->compress_hier) {
            fst_off_t hl, eos;
            gzFile zhandle;
            int zfd;
            int fourpack_duo = 0;
#ifndef __MINGW32__
            int fnam_len = strlen(xc->filename) + 5 + 1;
            char *fnam = (char *)malloc(fnam_len);
#endif

            fixup_offs = ftello(xc->handle);
            fputc(FST_BL_SKIP, xc->handle); /* temporary tag */
            hlen = ftello(xc->handle);
            fstWriterUint64(xc->handle, 0); /* section length */
            fstWriterUint64(xc->handle, xc->hier_file_len); /* uncompressed length */

            if (!xc->fourpack) {
                unsigned char *mem = (unsigned char *)malloc(FST_GZIO_LEN);
                zfd = dup(fileno(xc->handle));
                fflush(xc->handle);
                zhandle = gzdopen(zfd, "wb4");
                if (zhandle) {
                    fstWriterFseeko(xc, xc->hier_handle, 0, SEEK_SET);
                    for (hl = 0; hl < xc->hier_file_len; hl += FST_GZIO_LEN) {
                        unsigned len = ((xc->hier_file_len - hl) > FST_GZIO_LEN)
                                           ? FST_GZIO_LEN
                                           : (xc->hier_file_len - hl);
                        fstFread(mem, len, 1, xc->hier_handle);
                        gzwrite(zhandle, mem, len);
                    }
                    gzclose(zhandle);
                } else {
                    close(zfd);
                }
                free(mem);
            } else {
                int lz4_maxlen;
                unsigned char *mem;
                unsigned char *hmem = NULL;
                int packed_len;

                fflush(xc->handle);

                lz4_maxlen = LZ4_compressBound(xc->hier_file_len);
                mem = (unsigned char *)malloc(lz4_maxlen);
                errno = 0;
                if (xc->hier_file_len) {
                    fstWriterMmapSanity(hmem = (unsigned char *)fstMmap(NULL,
                                                                        xc->hier_file_len,
                                                                        PROT_READ | PROT_WRITE,
                                                                        MAP_SHARED,
                                                                        fileno(xc->hier_handle),
                                                                        0),
                                        __FILE__,
                                        __LINE__,
                                        "hmem");
                }
                packed_len =
                    LZ4_compress_default((char *)hmem, (char *)mem, xc->hier_file_len, lz4_maxlen);
                fstMunmap(hmem, xc->hier_file_len);

                fourpack_duo =
                    (!xc->repack_on_close) &&
                    (xc->hier_file_len >
                     FST_HDR_FOURPACK_DUO_SIZE); /* double pack when hierarchy is large */

                if (fourpack_duo) /* double packing with LZ4 is faster than gzip */
                {
                    unsigned char *mem_duo;
                    int lz4_maxlen_duo;
                    int packed_len_duo;

                    lz4_maxlen_duo = LZ4_compressBound(packed_len);
                    mem_duo = (unsigned char *)malloc(lz4_maxlen_duo);
                    packed_len_duo = LZ4_compress_default((char *)mem,
                                                          (char *)mem_duo,
                                                          packed_len,
                                                          lz4_maxlen_duo);

                    fstWriterVarint(xc->handle, packed_len); /* 1st round compressed length */
                    fstFwrite(mem_duo, packed_len_duo, 1, xc->handle);
                    free(mem_duo);
                } else {
                    fstFwrite(mem, packed_len, 1, xc->handle);
                }

                free(mem);
            }

            fstWriterFseeko(xc, xc->handle, 0, SEEK_END);
            eos = ftello(xc->handle);
            fstWriterFseeko(xc, xc->handle, hlen, SEEK_SET);
            fstWriterUint64(xc->handle, eos - hlen);
            fflush(xc->handle);

            fstWriterFseeko(xc, xc->handle, fixup_offs, SEEK_SET);
            fputc(xc->fourpack ? (fourpack_duo ? FST_BL_HIER_LZ4DUO : FST_BL_HIER_LZ4)
                               : FST_BL_HIER,
                  xc->handle); /* actual tag now also == compression type */

            fstWriterFseeko(xc,
                            xc->handle,
                            0,
                            SEEK_END); /* move file pointer to end for any section adds */
            fflush(xc->handle);

#ifndef __MINGW32__
            snprintf(fnam, fnam_len, "%s.hier", xc->filename);
            unlink(fnam);
            free(fnam);
#endif
        }

        /* finalize out header */
        fstWriterFseeko(xc, xc->handle, FST_HDR_OFFS_START_TIME, SEEK_SET);
        fstWriterUint64(xc->handle, xc->firsttime);
        fstWriterUint64(xc->handle, xc->curtime);
        fstWriterFseeko(xc, xc->handle, FST_HDR_OFFS_NUM_SCOPES, SEEK_SET);
        fstWriterUint64(xc->handle, xc->numscopes);
        fstWriterUint64(xc->handle, xc->numsigs);
        fstWriterUint64(xc->handle, xc->maxhandle);
        fstWriterUint64(xc->handle, xc->secnum);
        fflush(xc->handle);

        tmpfile_close(&xc->tchn_handle, &xc->tchn_handle_nam);
        free(xc->vchg_mem);
        xc->vchg_mem = NULL;
        tmpfile_close(&xc->curval_handle, &xc->curval_handle_nam);
        tmpfile_close(&xc->valpos_handle, &xc->valpos_handle_nam);
        tmpfile_close(&xc->geom_handle, &xc->geom_handle_nam);
        if (xc->hier_handle) {
            fclose(xc->hier_handle);
            xc->hier_handle = NULL;
        }
        if (xc->handle) {
            if (xc->repack_on_close) {
                FILE *fp;
                fst_off_t offpnt, uclen;
                int flen = strlen(xc->filename);
                char *hf = (char *)calloc(1, flen + 5);

                strcpy(hf, xc->filename);
                strcpy(hf + flen, ".pak");
                fp = fopen(hf, "wb");

                if (fp) {
                    gzFile dsth;
                    int zfd;
                    char gz_membuf[FST_GZIO_LEN];

                    fstWriterFseeko(xc, xc->handle, 0, SEEK_END);
                    uclen = ftello(xc->handle);

                    fputc(FST_BL_ZWRAPPER, fp);
                    fstWriterUint64(fp, 0);
                    fstWriterUint64(fp, uclen);
                    fflush(fp);

                    fstWriterFseeko(xc, xc->handle, 0, SEEK_SET);
                    zfd = dup(fileno(fp));
                    dsth = gzdopen(zfd, "wb4");
                    if (dsth) {
                        for (offpnt = 0; offpnt < uclen; offpnt += FST_GZIO_LEN) {
                            size_t this_len =
                                ((uclen - offpnt) > FST_GZIO_LEN) ? FST_GZIO_LEN : (uclen - offpnt);
                            fstFread(gz_membuf, this_len, 1, xc->handle);
                            gzwrite(dsth, gz_membuf, this_len);
                        }
                        gzclose(dsth);
                    } else {
                        close(zfd);
                    }
                    fstWriterFseeko(xc, fp, 0, SEEK_END);
                    offpnt = ftello(fp);
                    fstWriterFseeko(xc, fp, 1, SEEK_SET);
                    fstWriterUint64(fp, offpnt - 1);
                    fclose(fp);
                    fclose(xc->handle);
                    xc->handle = NULL;

                    unlink(xc->filename);
                    rename(hf, xc->filename);
                } else {
                    xc->repack_on_close = 0;
                    fclose(xc->handle);
                    xc->handle = NULL;
                }

                free(hf);
            } else {
                fclose(xc->handle);
                xc->handle = NULL;
            }
        }

#ifdef __MINGW32__
        {
            int flen = strlen(xc->filename);
            char *hf = (char *)calloc(1, flen + 6);
            strcpy(hf, xc->filename);

            if (xc->compress_hier) {
                strcpy(hf + flen, ".hier");
                unlink(hf); /* no longer needed as a section now exists for this */
            }

            free(hf);
        }
#endif

#ifdef FST_WRITER_PARALLEL
        pthread_mutex_destroy(&xc->mutex);
        pthread_attr_destroy(&xc->thread_attr);
#endif

        if (xc->path_array) {
            const uint32_t hashmask = FST_PATH_HASHMASK;
            JenkinsFree(&(xc->path_array), hashmask);
        }

        free(xc->filename);
        xc->filename = NULL;
        free(xc);
    }
}

/*
 * functions to set miscellaneous header/block information
 */
void fstWriterSetDate(fstWriterContext *xc, const char *dat)
{
    if (xc) {
        char s[FST_HDR_DATE_SIZE];
        fst_off_t fpos = ftello(xc->handle);
        int len = strlen(dat);

        fstWriterFseeko(xc, xc->handle, FST_HDR_OFFS_DATE, SEEK_SET);
        memset(s, 0, FST_HDR_DATE_SIZE);
        memcpy(s, dat, (len < FST_HDR_DATE_SIZE) ? len : FST_HDR_DATE_SIZE);
        fstFwrite(s, FST_HDR_DATE_SIZE, 1, xc->handle);
        fflush(xc->handle);
        fstWriterFseeko(xc, xc->handle, fpos, SEEK_SET);
    }
}

void fstWriterSetVersion(fstWriterContext *xc, const char *vers)
{
    if (xc && vers) {
        char s[FST_HDR_SIM_VERSION_SIZE];
        fst_off_t fpos = ftello(xc->handle);
        int len = strlen(vers);

        fstWriterFseeko(xc, xc->handle, FST_HDR_OFFS_SIM_VERSION, SEEK_SET);
        memset(s, 0, FST_HDR_SIM_VERSION_SIZE);
        memcpy(s, vers, (len < FST_HDR_SIM_VERSION_SIZE) ? len : FST_HDR_SIM_VERSION_SIZE);
        fstFwrite(s, FST_HDR_SIM_VERSION_SIZE, 1, xc->handle);
        fflush(xc->handle);
        fstWriterFseeko(xc, xc->handle, fpos, SEEK_SET);
    }
}

void fstWriterSetFileType(fstWriterContext *xc, enum fstFileType filetype)
{
    if (xc) {
        if (/*(filetype >= FST_FT_MIN) &&*/ (filetype <= FST_FT_MAX)) {
            fst_off_t fpos = ftello(xc->handle);

            xc->filetype = filetype;

            fstWriterFseeko(xc, xc->handle, FST_HDR_OFFS_FILETYPE, SEEK_SET);
            fputc(xc->filetype, xc->handle);
            fflush(xc->handle);
            fstWriterFseeko(xc, xc->handle, fpos, SEEK_SET);
        }
    }
}

static void fstWriterSetAttrDoubleArgGeneric(fstWriterContext *xc,
                                             int typ,
                                             uint64_t arg1,
                                             uint64_t arg2)
{
    if (xc) {
        unsigned char buf[11]; /* ceil(64/7) = 10 + null term */
        unsigned char *pnt = fstCopyVarint64ToRight(buf, arg1);
        if (arg1) {
            *pnt =
                0; /* this converts any *nonzero* arg1 when made a varint into a null-term string */
        }

        fstWriterSetAttrBegin(xc, FST_AT_MISC, typ, (char *)buf, arg2);
    }
}

static void fstWriterSetAttrGeneric(fstWriterContext *xc, const char *comm, int typ, uint64_t arg)
{
    if (xc && comm) {
        char *s = strdup(comm);
        char *sf = s;

        while (*s) {
            if ((*s == '\n') || (*s == '\r'))
                *s = ' ';
            s++;
        }

        fstWriterSetAttrBegin(xc, FST_AT_MISC, typ, sf, arg);
        free(sf);
    }
}

static void fstWriterSetSourceStem_2(fstWriterContext *xc,
                                     const char *path,
                                     unsigned int line,
                                     unsigned int use_realpath,
                                     int typ)
{
    if (xc && path && path[0]) {
        uint64_t sidx = 0;
        int slen = strlen(path);
        const uint32_t hashmask = FST_PATH_HASHMASK;
        const unsigned char *path2 = (const unsigned char *)path;
        PPvoid_t pv;

        pv = JenkinsIns(&(xc->path_array), path2, slen, hashmask);
        if (*pv) {
            sidx = (intptr_t)(*pv);
        } else {
            char *rp = NULL;

            sidx = ++xc->path_array_count;
            *pv = (void *)(intptr_t)(xc->path_array_count);

            if (use_realpath) {
                rp = fstRealpath((const char *)path2, NULL);
            }

            fstWriterSetAttrGeneric(xc, rp ? rp : (const char *)path2, FST_MT_PATHNAME, sidx);

            if (rp) {
                free(rp);
            }
        }

        fstWriterSetAttrDoubleArgGeneric(xc, typ, sidx, line);
    }
}

void fstWriterSetSourceStem(fstWriterContext *ctx,
                            const char *path,
                            unsigned int line,
                            unsigned int use_realpath)
{
    fstWriterSetSourceStem_2(ctx, path, line, use_realpath, FST_MT_SOURCESTEM);
}

void fstWriterSetSourceInstantiationStem(fstWriterContext *ctx,
                                         const char *path,
                                         unsigned int line,
                                         unsigned int use_realpath)
{
    fstWriterSetSourceStem_2(ctx, path, line, use_realpath, FST_MT_SOURCEISTEM);
}

void fstWriterSetComment(fstWriterContext *ctx, const char *comm)
{
    fstWriterSetAttrGeneric(ctx, comm, FST_MT_COMMENT, 0);
}

void fstWriterSetValueList(fstWriterContext *ctx, const char *vl)
{
    fstWriterSetAttrGeneric(ctx, vl, FST_MT_VALUELIST, 0);
}

void fstWriterSetEnvVar(fstWriterContext *ctx, const char *envvar)
{
    fstWriterSetAttrGeneric(ctx, envvar, FST_MT_ENVVAR, 0);
}

void fstWriterSetTimescale(fstWriterContext *xc, int ts)
{
    if (xc) {
        fst_off_t fpos = ftello(xc->handle);
        fstWriterFseeko(xc, xc->handle, FST_HDR_OFFS_TIMESCALE, SEEK_SET);
        fputc(ts & 255, xc->handle);
        fflush(xc->handle);
        fstWriterFseeko(xc, xc->handle, fpos, SEEK_SET);
    }
}

void fstWriterSetTimescaleFromString(fstWriterContext *xc, const char *s)
{
    if (xc && s) {
        int mat = 0;
        int seconds_exp = -9;
        int tv = atoi(s);
        const char *pnt = s;

        while (*pnt) {
            switch (*pnt) {
                case 'm':
                    seconds_exp = -3;
                    mat = 1;
                    break;
                case 'u':
                    seconds_exp = -6;
                    mat = 1;
                    break;
                case 'n':
                    seconds_exp = -9;
                    mat = 1;
                    break;
                case 'p':
                    seconds_exp = -12;
                    mat = 1;
                    break;
                case 'f':
                    seconds_exp = -15;
                    mat = 1;
                    break;
                case 'a':
                    seconds_exp = -18;
                    mat = 1;
                    break;
                case 'z':
                    seconds_exp = -21;
                    mat = 1;
                    break;
                case 's':
                    seconds_exp = 0;
                    mat = 1;
                    break;
                default:
                    break;
            }

            if (mat)
                break;
            pnt++;
        }

        if (tv == 10) {
            seconds_exp++;
        } else if (tv == 100) {
            seconds_exp += 2;
        }

        fstWriterSetTimescale(xc, seconds_exp);
    }
}

void fstWriterSetTimezero(fstWriterContext *xc, int64_t tim)
{
    if (xc) {
        fst_off_t fpos = ftello(xc->handle);
        fstWriterFseeko(xc, xc->handle, FST_HDR_OFFS_TIMEZERO, SEEK_SET);
        fstWriterUint64(xc->handle, (xc->timezero = tim));
        fflush(xc->handle);
        fstWriterFseeko(xc, xc->handle, fpos, SEEK_SET);
    }
}

void fstWriterSetPackType(fstWriterContext *xc, enum fstWriterPackType typ)
{
    if (xc) {
        xc->fastpack = (typ != FST_WR_PT_ZLIB);
        xc->fourpack = (typ == FST_WR_PT_LZ4);
    }
}

void fstWriterSetRepackOnClose(fstWriterContext *xc, int enable)
{
    if (xc) {
        xc->repack_on_close = (enable != 0);
    }
}

void fstWriterSetParallelMode(fstWriterContext *xc, int enable)
{
    if (xc) {
        xc->parallel_was_enabled |= xc->parallel_enabled; /* make sticky */
        xc->parallel_enabled = (enable != 0);
#ifndef FST_WRITER_PARALLEL
        if (xc->parallel_enabled) {
            fprintf(stderr,
                    FST_APIMESS "fstWriterSetParallelMode(), FST_WRITER_PARALLEL not enabled "
                                "during compile, exiting.\n");
            exit(255);
        }
#endif
    }
}

void fstWriterSetDumpSizeLimit(fstWriterContext *xc, uint64_t numbytes)
{
    if (xc) {
        xc->dump_size_limit = numbytes;
    }
}

int fstWriterGetDumpSizeLimitReached(fstWriterContext *xc)
{
    if (xc) {
        return (xc->size_limit_locked != 0);
    }

    return (0);
}

int fstWriterGetFseekFailed(fstWriterContext *xc)
{
    if (xc) {
        return (xc->fseek_failed != 0);
    }

    return (0);
}

static int fstWriterGetFlushContextPendingInternal(fstWriterContext *xc)
{
    return (xc->vchg_siz >= xc->fst_break_size) || (xc->flush_context_pending);
}

int fstWriterGetFlushContextPending(fstWriterContext *xc)
{
    return xc && !xc->is_initial_time && fstWriterGetFlushContextPendingInternal(xc);
}

/*
 * writer attr/scope/var creation:
 * fstWriterCreateVar2() is used to dump VHDL or other languages, but the
 * underlying variable needs to map to Verilog/SV via the proper fstVarType vt
 */
fstHandle fstWriterCreateVar2(fstWriterContext *ctx,
                              enum fstVarType vt,
                              enum fstVarDir vd,
                              uint32_t len,
                              const char *nam,
                              fstHandle aliasHandle,
                              const char *type,
                              enum fstSupplementalVarType svt,
                              enum fstSupplementalDataType sdt)
{
    fstWriterSetAttrGeneric(ctx,
                            type ? type : "",
                            FST_MT_SUPVAR,
                            (svt << FST_SDT_SVT_SHIFT_COUNT) | (sdt & FST_SDT_ABS_MAX));
    return (fstWriterCreateVar(ctx, vt, vd, len, nam, aliasHandle));
}

fstHandle fstWriterCreateVar(fstWriterContext *xc,
                             enum fstVarType vt,
                             enum fstVarDir vd,
                             uint32_t len,
                             const char *nam,
                             fstHandle aliasHandle)
{
    unsigned int i;
    int nlen, is_real;

    if (xc && nam) {
        if (xc->valpos_mem) {
            fstDestroyMmaps(xc, 0);
        }

        fputc(vt, xc->hier_handle);
        fputc(vd, xc->hier_handle);
        nlen = strlen(nam);
        fstFwrite(nam, nlen, 1, xc->hier_handle);
        fputc(0, xc->hier_handle);
        xc->hier_file_len += (nlen + 3);

        if ((vt == FST_VT_VCD_REAL) || (vt == FST_VT_VCD_REAL_PARAMETER) ||
            (vt == FST_VT_VCD_REALTIME) || (vt == FST_VT_SV_SHORTREAL)) {
            is_real = 1;
            len = 8; /* recast number of bytes to that of what a double is */
        } else {
            is_real = 0;
            if (vt == FST_VT_GEN_STRING) {
                len = 0;
            }
        }

        xc->hier_file_len += fstWriterVarint(xc->hier_handle, len);

        if (aliasHandle > xc->maxhandle)
            aliasHandle = 0;
        xc->hier_file_len += fstWriterVarint(xc->hier_handle, aliasHandle);
        xc->numsigs++;
        if (xc->numsigs == xc->next_huge_break) {
            if (xc->fst_break_size < xc->fst_huge_break_size) {
                xc->next_huge_break += FST_ACTIVATE_HUGE_INC;
                xc->fst_break_size += xc->fst_orig_break_size;
                xc->fst_break_add_size += xc->fst_orig_break_add_size;

                xc->vchg_alloc_siz = xc->fst_break_size + xc->fst_break_add_size;
                if (xc->vchg_mem) {
                    xc->vchg_mem = (unsigned char *)realloc(xc->vchg_mem, xc->vchg_alloc_siz);
                }
            }
        }

        if (!aliasHandle) {
            uint32_t zero = 0;

            if (len) {
                fstWriterVarint(xc->geom_handle,
                                !is_real ? len : 0); /* geom section encodes reals as zero byte */
            } else {
                fstWriterVarint(xc->geom_handle,
                                0xFFFFFFFF); /* geom section encodes zero len as 32b -1 */
            }

            fstFwrite(&xc->maxvalpos, sizeof(uint32_t), 1, xc->valpos_handle);
            fstFwrite(&len, sizeof(uint32_t), 1, xc->valpos_handle);
            fstFwrite(&zero, sizeof(uint32_t), 1, xc->valpos_handle);
            fstFwrite(&zero, sizeof(uint32_t), 1, xc->valpos_handle);

            if (!is_real) {
                for (i = 0; i < len; i++) {
                    fputc('x', xc->curval_handle);
                }
            } else {
                fstFwrite(&xc->nan,
                          8,
                          1,
                          xc->curval_handle); /* initialize doubles to NaN rather than x */
            }

            xc->maxvalpos += len;
            xc->maxhandle++;
            return (xc->maxhandle);
        } else {
            return (aliasHandle);
        }
    }

    return (0);
}

void fstWriterSetScope(fstWriterContext *xc,
                       enum fstScopeType scopetype,
                       const char *scopename,
                       const char *scopecomp)
{
    if (xc) {
        fputc(FST_ST_VCD_SCOPE, xc->hier_handle);
        if (/*(scopetype < FST_ST_VCD_MODULE) ||*/ (scopetype > FST_ST_MAX)) {
            scopetype = FST_ST_VCD_MODULE;
        }
        fputc(scopetype, xc->hier_handle);
        fprintf(xc->hier_handle,
                "%s%c%s%c",
                scopename ? scopename : "",
                0,
                scopecomp ? scopecomp : "",
                0);

        if (scopename) {
            xc->hier_file_len += strlen(scopename);
        }
        if (scopecomp) {
            xc->hier_file_len += strlen(scopecomp);
        }

        xc->hier_file_len += 4; /* FST_ST_VCD_SCOPE + scopetype + two string terminating zeros */
        xc->numscopes++;
    }
}

void fstWriterSetUpscope(fstWriterContext *xc)
{
    if (xc) {
        fputc(FST_ST_VCD_UPSCOPE, xc->hier_handle);
        xc->hier_file_len++;
    }
}

void fstWriterSetAttrBegin(fstWriterContext *xc,
                           enum fstAttrType attrtype,
                           int subtype,
                           const char *attrname,
                           uint64_t arg)
{
    if (xc) {
        fputc(FST_ST_GEN_ATTRBEGIN, xc->hier_handle);
        if (/*(attrtype < FST_AT_MISC) ||*/ (attrtype > FST_AT_MAX)) {
            attrtype = FST_AT_MISC;
            subtype = FST_MT_UNKNOWN;
        }
        fputc(attrtype, xc->hier_handle);

        switch (attrtype) {
            case FST_AT_ARRAY:
                if ((subtype < FST_AR_NONE) || (subtype > FST_AR_MAX))
                    subtype = FST_AR_NONE;
                break;
            case FST_AT_ENUM:
                if ((subtype < FST_EV_SV_INTEGER) || (subtype > FST_EV_MAX))
                    subtype = FST_EV_SV_INTEGER;
                break;
            case FST_AT_PACK:
                if ((subtype < FST_PT_NONE) || (subtype > FST_PT_MAX))
                    subtype = FST_PT_NONE;
                break;

            case FST_AT_MISC:
            default:
                break;
        }

        fputc(subtype, xc->hier_handle);
        fprintf(xc->hier_handle, "%s%c", attrname ? attrname : "", 0);

        if (attrname) {
            xc->hier_file_len += strlen(attrname);
        }

        xc->hier_file_len +=
            4; /* FST_ST_GEN_ATTRBEGIN + type + subtype + string terminating zero */
        xc->hier_file_len += fstWriterVarint(xc->hier_handle, arg);
    }
}

void fstWriterSetAttrEnd(fstWriterContext *xc)
{
    if (xc) {
        fputc(FST_ST_GEN_ATTREND, xc->hier_handle);
        xc->hier_file_len++;
    }
}

fstEnumHandle fstWriterCreateEnumTable(fstWriterContext *xc,
                                       const char *name,
                                       uint32_t elem_count,
                                       unsigned int min_valbits,
                                       const char **literal_arr,
                                       const char **val_arr)
{
    fstEnumHandle handle = 0;
    unsigned int *literal_lens = NULL;
    unsigned int *val_lens = NULL;
    int lit_len_tot = 0;
    int val_len_tot = 0;
    int name_len;
    char elem_count_buf[16];
    int elem_count_len;
    int total_len;
    int pos = 0;
    char *attr_str = NULL;

    if (xc && name && literal_arr && val_arr && (elem_count != 0)) {
        uint32_t i;

        name_len = strlen(name);
        elem_count_len = snprintf(elem_count_buf, 16, "%" PRIu32, elem_count);

        literal_lens = (unsigned int *)calloc(elem_count, sizeof(unsigned int));
        val_lens = (unsigned int *)calloc(elem_count, sizeof(unsigned int));

        for (i = 0; i < elem_count; i++) {
            literal_lens[i] = strlen(literal_arr[i]);
            lit_len_tot +=
                fstUtilityBinToEscConvertedLen((unsigned char *)literal_arr[i], literal_lens[i]);

            val_lens[i] = strlen(val_arr[i]);
            val_len_tot += fstUtilityBinToEscConvertedLen((unsigned char *)val_arr[i], val_lens[i]);

            if (min_valbits > 0) {
                if (val_lens[i] < min_valbits) {
                    val_len_tot +=
                        (min_valbits -
                         val_lens[i]); /* additional converted len is same for '0' character */
                }
            }
        }

        total_len =
            name_len + 1 + elem_count_len + 1 + lit_len_tot + elem_count + val_len_tot + elem_count;

        attr_str = (char *)malloc(total_len);
        pos = 0;

        memcpy(attr_str + pos, name, name_len);
        pos += name_len;
        attr_str[pos++] = ' ';

        memcpy(attr_str + pos, elem_count_buf, elem_count_len);
        pos += elem_count_len;
        attr_str[pos++] = ' ';

        for (i = 0; i < elem_count; i++) {
            pos += fstUtilityBinToEsc((unsigned char *)attr_str + pos,
                                      (unsigned char *)literal_arr[i],
                                      literal_lens[i]);
            attr_str[pos++] = ' ';
        }

        for (i = 0; i < elem_count; i++) {
            if (min_valbits > 0) {
                if (val_lens[i] < min_valbits) {
                    memset(attr_str + pos, '0', min_valbits - val_lens[i]);
                    pos += (min_valbits - val_lens[i]);
                }
            }

            pos += fstUtilityBinToEsc((unsigned char *)attr_str + pos,
                                      (unsigned char *)val_arr[i],
                                      val_lens[i]);
            attr_str[pos++] = ' ';
        }

        attr_str[pos - 1] = 0;

#ifdef FST_DEBUG
        fprintf(stderr,
                FST_APIMESS "fstWriterCreateEnumTable() total_len: %d, pos: %d\n",
                total_len,
                pos);
        fprintf(stderr, FST_APIMESS "*%s*\n", attr_str);
#endif

        fstWriterSetAttrBegin(xc,
                              FST_AT_MISC,
                              FST_MT_ENUMTABLE,
                              attr_str,
                              handle = ++xc->max_enumhandle);

        free(attr_str);
        free(val_lens);
        free(literal_lens);
    }

    return (handle);
}

void fstWriterEmitEnumTableRef(fstWriterContext *xc, fstEnumHandle handle)
{
    if (xc && handle) {
        fstWriterSetAttrBegin(xc, FST_AT_MISC, FST_MT_ENUMTABLE, NULL, handle);
    }
}

/*
 * value and time change emission
 */
void fstWriterEmitValueChange(fstWriterContext *xc, fstHandle handle, const void *val)
{
    const unsigned char *buf = (const unsigned char *)val;
    uint32_t offs;
    int len;

    if (FST_LIKELY((xc) && (handle <= xc->maxhandle))) {
        uint32_t fpos;
        uint32_t *vm4ip;

        if (FST_UNLIKELY(!xc->valpos_mem)) {
            xc->vc_emitted = 1;
            fstWriterCreateMmaps(xc);
        }

        handle--; /* move starting at 1 index to starting at 0 */
        vm4ip = &(xc->valpos_mem[4 * handle]);

        len = vm4ip[1];
        if (FST_LIKELY(len)) /* len of zero = variable length, use
                                fstWriterEmitVariableLengthValueChange */
        {
            if (FST_LIKELY(!xc->is_initial_time)) {
                fpos = xc->vchg_siz;

                if (FST_UNLIKELY((fpos + len + 10) > xc->vchg_alloc_siz)) {
                    xc->vchg_alloc_siz +=
                        (xc->fst_break_add_size + len); /* +len added in the case of extremely long
                                                           vectors and small break add sizes */
                    xc->vchg_mem = (unsigned char *)realloc(xc->vchg_mem, xc->vchg_alloc_siz);
                    if (FST_UNLIKELY(!xc->vchg_mem)) {
                        fprintf(stderr,
                                FST_APIMESS
                                "Could not realloc() in fstWriterEmitValueChange, exiting.\n");
                        exit(255);
                    }
                }
#ifdef FST_REMOVE_DUPLICATE_VC
                offs = vm4ip[0];

                if (len != 1) {
                    if ((vm4ip[3] == xc->tchn_idx) && (vm4ip[2])) {
                        unsigned char *old_value =
                            xc->vchg_mem + vm4ip[2] + 4; /* the +4 skips old vm4ip[2] value */
                        while (*(old_value++) &
                               0x80) { /* skips over varint encoded "xc->tchn_idx - vm4ip[3]" */
                        }
                        memcpy(old_value, buf, len); /* overlay new value */

                        memcpy(xc->curval_mem + offs, buf, len);
                        return;
                    } else {
                        if (!memcmp(xc->curval_mem + offs, buf, len)) {
                            if (!xc->curtime) {
                                int i;
                                for (i = 0; i < len; i++) {
                                    if (buf[i] != 'x')
                                        break;
                                }

                                if (i < len)
                                    return;
                            } else {
                                return;
                            }
                        }
                    }

                    memcpy(xc->curval_mem + offs, buf, len);
                } else {
                    if ((vm4ip[3] == xc->tchn_idx) && (vm4ip[2])) {
                        unsigned char *old_value =
                            xc->vchg_mem + vm4ip[2] + 4; /* the +4 skips old vm4ip[2] value */
                        while (*(old_value++) &
                               0x80) { /* skips over varint encoded "xc->tchn_idx - vm4ip[3]" */
                        }
                        *old_value = *buf; /* overlay new value */

                        *(xc->curval_mem + offs) = *buf;
                        return;
                    } else {
                        if ((*(xc->curval_mem + offs)) == (*buf)) {
                            if (!xc->curtime) {
                                if (*buf != 'x')
                                    return;
                            } else {
                                return;
                            }
                        }
                    }

                    *(xc->curval_mem + offs) = *buf;
                }
#endif
                xc->vchg_siz += fstWriterUint32WithVarint32(xc,
                                                            &vm4ip[2],
                                                            xc->tchn_idx - vm4ip[3],
                                                            buf,
                                                            len); /* do one fwrite op only */
                vm4ip[3] = xc->tchn_idx;
                vm4ip[2] = fpos;
            } else {
                offs = vm4ip[0];
                memcpy(xc->curval_mem + offs, buf, len);
            }
        }
    }
}

void fstWriterEmitValueChange32(fstWriterContext *ctx,
                                fstHandle handle,
                                uint32_t bits,
                                uint32_t val)
{
    char buf[32];
    char *s = buf;
    uint32_t i;
    for (i = 0; i < bits; ++i) {
        *s++ = '0' + ((val >> (bits - i - 1)) & 1);
    }
    fstWriterEmitValueChange(ctx, handle, buf);
}

void fstWriterEmitValueChange64(fstWriterContext *ctx,
                                fstHandle handle,
                                uint32_t bits,
                                uint64_t val)
{
    char buf[64];
    char *s = buf;
    uint32_t i;
    for (i = 0; i < bits; ++i) {
        *s++ = '0' + ((val >> (bits - i - 1)) & 1);
    }
    fstWriterEmitValueChange(ctx, handle, buf);
}

void fstWriterEmitValueChangeVec32(fstWriterContext *xc,
                                   fstHandle handle,
                                   uint32_t bits,
                                   const uint32_t *val)
{
    if (FST_UNLIKELY(bits <= 32)) {
        fstWriterEmitValueChange32(xc, handle, bits, val[0]);
    } else if (FST_LIKELY(xc)) {
        int bq = bits / 32;
        int br = bits & 31;
        int i;
        int w;
        uint32_t v;
        unsigned char *s;
        if (FST_UNLIKELY(bits > xc->outval_alloc_siz)) {
            xc->outval_alloc_siz = bits * 2 + 1;
            xc->outval_mem = (unsigned char *)realloc(xc->outval_mem, xc->outval_alloc_siz);
            if (FST_UNLIKELY(!xc->outval_mem)) {
                fprintf(stderr,
                        FST_APIMESS
                        "Could not realloc() in fstWriterEmitValueChangeVec32, exiting.\n");
                exit(255);
            }
        }
        s = xc->outval_mem;
        {
            w = bq;
            v = val[w];
            for (i = 0; i < br; ++i) {
                *s++ = '0' + ((v >> (br - i - 1)) & 1);
            }
        }
        for (w = bq - 1; w >= 0; --w) {
            v = val[w];
            for (i = (32 - 4); i >= 0; i -= 4) {
                s[0] = '0' + ((v >> (i + 3)) & 1);
                s[1] = '0' + ((v >> (i + 2)) & 1);
                s[2] = '0' + ((v >> (i + 1)) & 1);
                s[3] = '0' + ((v >> (i + 0)) & 1);
                s += 4;
            }
        }
        fstWriterEmitValueChange(xc, handle, xc->outval_mem);
    }
}
void fstWriterEmitValueChangeVec64(fstWriterContext *xc,
                                   fstHandle handle,
                                   uint32_t bits,
                                   const uint64_t *val)
{
    if (FST_UNLIKELY(bits <= 64)) {
        fstWriterEmitValueChange64(xc, handle, bits, val[0]);
    } else if (FST_LIKELY(xc)) {
        int bq = bits / 64;
        int br = bits & 63;
        int i;
        int w;
        uint32_t v;
        unsigned char *s;
        if (FST_UNLIKELY(bits > xc->outval_alloc_siz)) {
            xc->outval_alloc_siz = bits * 2 + 1;
            xc->outval_mem = (unsigned char *)realloc(xc->outval_mem, xc->outval_alloc_siz);
            if (FST_UNLIKELY(!xc->outval_mem)) {
                fprintf(stderr,
                        FST_APIMESS
                        "Could not realloc() in fstWriterEmitValueChangeVec64, exiting.\n");
                exit(255);
            }
        }
        s = xc->outval_mem;
        {
            w = bq;
            v = val[w];
            for (i = 0; i < br; ++i) {
                *s++ = '0' + ((v >> (br - i - 1)) & 1);
            }
        }
        for (w = bq - 1; w >= 0; --w) {
            v = val[w];
            for (i = (64 - 4); i >= 0; i -= 4) {
                s[0] = '0' + ((v >> (i + 3)) & 1);
                s[1] = '0' + ((v >> (i + 2)) & 1);
                s[2] = '0' + ((v >> (i + 1)) & 1);
                s[3] = '0' + ((v >> (i + 0)) & 1);
                s += 4;
            }
        }
        fstWriterEmitValueChange(xc, handle, xc->outval_mem);
    }
}

void fstWriterEmitVariableLengthValueChange(fstWriterContext *xc,
                                            fstHandle handle,
                                            const void *val,
                                            uint32_t len)
{
    const unsigned char *buf = (const unsigned char *)val;

    if (FST_LIKELY((xc) && (handle <= xc->maxhandle))) {
        uint32_t fpos;
        uint32_t *vm4ip;

        if (FST_UNLIKELY(!xc->valpos_mem)) {
            xc->vc_emitted = 1;
            fstWriterCreateMmaps(xc);
        }

        handle--; /* move starting at 1 index to starting at 0 */
        vm4ip = &(xc->valpos_mem[4 * handle]);

        /* there is no initial time dump for variable length value changes */
        if (FST_LIKELY(!vm4ip[1])) /* len of zero = variable length */
        {
            fpos = xc->vchg_siz;

            if (FST_UNLIKELY((fpos + len + 10 + 5) > xc->vchg_alloc_siz)) {
                xc->vchg_alloc_siz +=
                    (xc->fst_break_add_size + len + 5); /* +len added in the case of extremely long
                                                           vectors and small break add sizes */
                xc->vchg_mem = (unsigned char *)realloc(xc->vchg_mem, xc->vchg_alloc_siz);
                if (FST_UNLIKELY(!xc->vchg_mem)) {
                    fprintf(stderr,
                            FST_APIMESS "Could not realloc() in "
                                        "fstWriterEmitVariableLengthValueChange, exiting.\n");
                    exit(255);
                }
            }

            xc->vchg_siz += fstWriterUint32WithVarint32AndLength(xc,
                                                                 &vm4ip[2],
                                                                 xc->tchn_idx - vm4ip[3],
                                                                 buf,
                                                                 len); /* do one fwrite op only */
            vm4ip[3] = xc->tchn_idx;
            vm4ip[2] = fpos;
        }
    }
}

void fstWriterEmitTimeChange(fstWriterContext *xc, uint64_t tim)
{
    unsigned int i;
    int skip = 0;
    if (xc) {
        if (FST_UNLIKELY(xc->is_initial_time)) {
            if (xc->size_limit_locked) /* this resets xc->is_initial_time to one */
            {
                return;
            }

            if (!xc->valpos_mem) {
                fstWriterCreateMmaps(xc);
            }

            skip = 1;

            xc->firsttime = (xc->vc_emitted) ? 0 : tim;
            xc->curtime = 0;
            xc->vchg_mem[0] = '!';
            xc->vchg_siz = 1;
            fstWriterEmitSectionHeader(xc);
            for (i = 0; i < xc->maxhandle; i++) {
                xc->valpos_mem[4 * i + 2] = 0; /* zero out offset val */
                xc->valpos_mem[4 * i + 3] = 0; /* zero out last time change val */
            }
            xc->is_initial_time = 0;
        } else {
            if (fstWriterGetFlushContextPendingInternal(xc)) {
                xc->flush_context_pending = 0;
                fstWriterFlushContextPrivate(xc);
                xc->tchn_cnt++;
                fstWriterVarint(xc->tchn_handle, xc->curtime);
            }
        }

        if (!skip) {
            xc->tchn_idx++;
        }
        fstWriterVarint(xc->tchn_handle, tim - xc->curtime);
        xc->tchn_cnt++;
        xc->curtime = tim;
    }
}

void fstWriterEmitDumpActive(fstWriterContext *xc, int enable)
{
    if (xc) {
        struct fstBlackoutChain *b =
            (struct fstBlackoutChain *)calloc(1, sizeof(struct fstBlackoutChain));

        b->tim = xc->curtime;
        b->active = (enable != 0);

        xc->num_blackouts++;
        if (xc->blackout_curr) {
            xc->blackout_curr->next = b;
            xc->blackout_curr = b;
        } else {
            xc->blackout_head = b;
            xc->blackout_curr = b;
        }
    }
}

/***********************/
/***                 ***/
/*** reader function ***/
/***                 ***/
/***********************/

/*
 * private structs
 */
static const char *vartypes[] = {
    "event", "integer",  "parameter", "real",    "real_parameter", "reg",      "supply0", "supply1",
    "time",  "tri",      "triand",    "trior",   "trireg",         "tri0",     "tri1",    "wand",
    "wire",  "wor",      "port",      "sparray", "realtime",       "string",   "bit",     "logic",
    "int",   "shortint", "longint",   "byte",    "enum",           "shortreal"};

static const char *modtypes[] = {"module",
                                 "task",
                                 "function",
                                 "begin",
                                 "fork",
                                 "generate",
                                 "struct",
                                 "union",
                                 "class",
                                 "interface",
                                 "package",
                                 "program",
                                 "vhdl_architecture",
                                 "vhdl_procedure",
                                 "vhdl_function",
                                 "vhdl_record",
                                 "vhdl_process",
                                 "vhdl_block",
                                 "vhdl_for_generate",
                                 "vhdl_if_generate",
                                 "vhdl_generate",
                                 "vhdl_package"};

static const char *attrtypes[] = {"misc", "array", "enum", "class"};

static const char *arraytypes[] = {"none", "unpacked", "packed", "sparse"};

static const char *enumvaluetypes[] = {"integer",
                                       "bit",
                                       "logic",
                                       "int",
                                       "shortint",
                                       "longint",
                                       "byte",
                                       "unsigned_integer",
                                       "unsigned_bit",
                                       "unsigned_logic",
                                       "unsigned_int",
                                       "unsigned_shortint",
                                       "unsigned_longint",
                                       "unsigned_byte"};

static const char *packtypes[] = {"none", "unpacked", "packed", "tagged_packed"};

struct fstCurrHier
{
    struct fstCurrHier *prev;
    void *user_info;
    int len;
};

struct fstReaderContext
{
    /* common entries */

    FILE *f, *fh;

    uint64_t start_time, end_time;
    uint64_t mem_used_by_writer;
    uint64_t scope_count;
    uint64_t var_count;
    fstHandle maxhandle;
    uint64_t num_alias;
    uint64_t vc_section_count;

    uint32_t *signal_lens; /* maxhandle sized */
    unsigned char *signal_typs; /* maxhandle sized */
    unsigned char *process_mask; /* maxhandle-based, bitwise sized */
    uint32_t longest_signal_value_len; /* longest len value encountered */
    unsigned char *temp_signal_value_buf; /* malloced for len in longest_signal_value_len */

    signed char timescale;
    unsigned char filetype;

    unsigned use_vcd_extensions : 1;
    unsigned double_endian_match : 1;
    unsigned native_doubles_for_cb : 1;
    unsigned contains_geom_section : 1;
    unsigned contains_hier_section : 1; /* valid for hier_pos */
    unsigned contains_hier_section_lz4duo : 1; /* valid for hier_pos (contains_hier_section_lz4
                                                  always also set) */
    unsigned contains_hier_section_lz4 : 1; /* valid for hier_pos */
    unsigned limit_range_valid : 1; /* valid for limit_range_start, limit_range_end */

    char version[FST_HDR_SIM_VERSION_SIZE + 1];
    char date[FST_HDR_DATE_SIZE + 1];
    int64_t timezero;

    char *filename, *filename_unpacked;
    fst_off_t hier_pos;

    uint32_t num_blackouts;
    uint64_t *blackout_times;
    unsigned char *blackout_activity;

    uint64_t limit_range_start, limit_range_end;

    /* entries specific to read value at time functions */

    unsigned rvat_data_valid : 1;
    uint64_t *rvat_time_table;
    uint64_t rvat_beg_tim, rvat_end_tim;
    unsigned char *rvat_frame_data;
    uint64_t rvat_frame_maxhandle;
    fst_off_t *rvat_chain_table;
    uint32_t *rvat_chain_table_lengths;
    uint64_t rvat_vc_maxhandle;
    fst_off_t rvat_vc_start;
    uint32_t *rvat_sig_offs;
    int rvat_packtype;

    uint32_t rvat_chain_len;
    unsigned char *rvat_chain_mem;
    fstHandle rvat_chain_facidx;

    uint32_t rvat_chain_pos_tidx;
    uint32_t rvat_chain_pos_idx;
    uint64_t rvat_chain_pos_time;
    unsigned rvat_chain_pos_valid : 1;

    /* entries specific to hierarchy traversal */

    struct fstHier hier;
    struct fstCurrHier *curr_hier;
    fstHandle current_handle;
    char *curr_flat_hier_nam;
    int flat_hier_alloc_len;
    unsigned do_rewind : 1;
    char str_scope_nam[FST_ID_NAM_SIZ + 1];
    char str_scope_comp[FST_ID_NAM_SIZ + 1];
    char *str_scope_attr;

    unsigned fseek_failed : 1;

    /* self-buffered I/O for writes */

#ifndef FST_WRITEX_DISABLE
    int writex_pos;
    int writex_fd;
    unsigned char writex_buf[FST_WRITEX_MAX];
#endif

    char *f_nam;
    char *fh_nam;
};

int fstReaderFseeko(struct fstReaderContext *xc, FILE *stream, fst_off_t offset, int whence)
{
    int rc = fseeko(stream, offset, whence);

    if (rc < 0) {
        xc->fseek_failed = 1;
#ifdef FST_DEBUG
        fprintf(stderr, FST_APIMESS "Seek to #%" PRId64 " (whence = %d) failed!\n", offset, whence);
        perror("Why");
#endif
    }

    return (rc);
}

#ifndef FST_WRITEX_DISABLE
static void fstWritex(struct fstReaderContext *xc,
                      void *v,
                      uint32_t len) /* TALOS-2023-1793: change len to unsigned */
{
    unsigned char *s = (unsigned char *)v;

    if (len) {
        if (len < FST_WRITEX_MAX) {
            if (xc->writex_pos + len >= FST_WRITEX_MAX) {
                fstWritex(xc, NULL, 0);
            }

            memcpy(xc->writex_buf + xc->writex_pos, s, len);
            xc->writex_pos += len;
        } else {
            fstWritex(xc, NULL, 0);
            if (write(xc->writex_fd, s, len)) {
            };
        }
    } else {
        if (xc->writex_pos) {
            if (write(xc->writex_fd, xc->writex_buf, xc->writex_pos)) {
            };
            xc->writex_pos = 0;
        }
    }
}
#endif

/*
 * scope -> flat name handling
 */
static void fstReaderDeallocateScopeData(fstReaderContext *xc)
{
    struct fstCurrHier *chp;

    free(xc->curr_flat_hier_nam);
    xc->curr_flat_hier_nam = NULL;
    while (xc->curr_hier) {
        chp = xc->curr_hier->prev;
        free(xc->curr_hier);
        xc->curr_hier = chp;
    }
}

const char *fstReaderGetCurrentFlatScope(fstReaderContext *xc)
{
    if (xc) {
        return (xc->curr_flat_hier_nam ? xc->curr_flat_hier_nam : "");
    } else {
        return (NULL);
    }
}

void *fstReaderGetCurrentScopeUserInfo(fstReaderContext *xc)
{
    if (xc) {
        return (xc->curr_hier ? xc->curr_hier->user_info : NULL);
    } else {
        return (NULL);
    }
}

const char *fstReaderPopScope(fstReaderContext *xc)
{
    if (xc && xc->curr_hier) {
        struct fstCurrHier *ch = xc->curr_hier;
        if (xc->curr_hier->prev) {
            xc->curr_flat_hier_nam[xc->curr_hier->prev->len] = 0;
        } else {
            *xc->curr_flat_hier_nam = 0;
        }
        xc->curr_hier = xc->curr_hier->prev;
        free(ch);
        return (xc->curr_flat_hier_nam ? xc->curr_flat_hier_nam : "");
    }

    return (NULL);
}

void fstReaderResetScope(fstReaderContext *xc)
{
    if (xc) {
        while (fstReaderPopScope(xc))
            ; /* remove any already-built scoping info */
    }
}

const char *fstReaderPushScope(fstReaderContext *xc, const char *nam, void *user_info)
{
    if (xc) {
        struct fstCurrHier *ch = (struct fstCurrHier *)malloc(sizeof(struct fstCurrHier));
        int chl = xc->curr_hier ? xc->curr_hier->len : 0;
        int len = chl + 1 + strlen(nam);
        if (len >= xc->flat_hier_alloc_len) {
            xc->curr_flat_hier_nam = xc->curr_flat_hier_nam
                                         ? (char *)realloc(xc->curr_flat_hier_nam, len + 1)
                                         : (char *)malloc(len + 1);
        }

        if (chl) {
            xc->curr_flat_hier_nam[chl] = '.';
            strcpy(xc->curr_flat_hier_nam + chl + 1, nam);
        } else {
            strcpy(xc->curr_flat_hier_nam, nam);
            len--;
        }

        ch->len = len;
        ch->prev = xc->curr_hier;
        ch->user_info = user_info;
        xc->curr_hier = ch;
        return (xc->curr_flat_hier_nam);
    }

    return (NULL);
}

int fstReaderGetCurrentScopeLen(fstReaderContext *xc)
{
    if (xc && xc->curr_hier) {
        return (xc->curr_hier->len);
    }

    return (0);
}

int fstReaderGetFseekFailed(fstReaderContext *xc)
{
    if (xc) {
        return (xc->fseek_failed != 0);
    }

    return (0);
}

/*
 * iter mask manipulation util functions
 */
int fstReaderGetFacProcessMask(fstReaderContext *xc, fstHandle facidx)
{
    if (xc) {
        facidx--;
        if (facidx < xc->maxhandle) {
            int process_idx = facidx / 8;
            int process_bit = facidx & 7;

            return ((xc->process_mask[process_idx] & (1 << process_bit)) != 0);
        }
    }
    return (0);
}

void fstReaderSetFacProcessMask(fstReaderContext *xc, fstHandle facidx)
{
    if (xc) {
        facidx--;
        if (facidx < xc->maxhandle) {
            int idx = facidx / 8;
            int bitpos = facidx & 7;

            xc->process_mask[idx] |= (1 << bitpos);
        }
    }
}

void fstReaderClrFacProcessMask(fstReaderContext *xc, fstHandle facidx)
{
    if (xc) {
        facidx--;
        if (facidx < xc->maxhandle) {
            int idx = facidx / 8;
            int bitpos = facidx & 7;

            xc->process_mask[idx] &= (~(1 << bitpos));
        }
    }
}

void fstReaderSetFacProcessMaskAll(fstReaderContext *xc)
{
    if (xc) {
        memset(xc->process_mask, 0xff, (xc->maxhandle + 7) / 8);
    }
}

void fstReaderClrFacProcessMaskAll(fstReaderContext *xc)
{
    if (xc) {
        memset(xc->process_mask, 0x00, (xc->maxhandle + 7) / 8);
    }
}

/*
 * various utility read/write functions
 */
signed char fstReaderGetTimescale(fstReaderContext *xc)
{
    return (xc ? xc->timescale : 0);
}

uint64_t fstReaderGetStartTime(fstReaderContext *xc)
{
    return (xc ? xc->start_time : 0);
}

uint64_t fstReaderGetEndTime(fstReaderContext *xc)
{
    return (xc ? xc->end_time : 0);
}

uint64_t fstReaderGetMemoryUsedByWriter(fstReaderContext *xc)
{
    return (xc ? xc->mem_used_by_writer : 0);
}

uint64_t fstReaderGetScopeCount(fstReaderContext *xc)
{
    return (xc ? xc->scope_count : 0);
}

uint64_t fstReaderGetVarCount(fstReaderContext *xc)
{
    return (xc ? xc->var_count : 0);
}

fstHandle fstReaderGetMaxHandle(fstReaderContext *xc)
{
    return (xc ? xc->maxhandle : 0);
}

uint64_t fstReaderGetAliasCount(fstReaderContext *xc)
{
    return (xc ? xc->num_alias : 0);
}

uint64_t fstReaderGetValueChangeSectionCount(fstReaderContext *xc)
{
    return (xc ? xc->vc_section_count : 0);
}

int fstReaderGetDoubleEndianMatchState(fstReaderContext *xc)
{
    return (xc ? xc->double_endian_match : 0);
}

const char *fstReaderGetVersionString(fstReaderContext *xc)
{
    return (xc ? xc->version : NULL);
}

const char *fstReaderGetDateString(fstReaderContext *xc)
{
    return (xc ? xc->date : NULL);
}

int fstReaderGetFileType(fstReaderContext *xc)
{
    return (xc ? (int)xc->filetype : (int)FST_FT_VERILOG);
}

int64_t fstReaderGetTimezero(fstReaderContext *xc)
{
    return (xc ? xc->timezero : 0);
}

uint32_t fstReaderGetNumberDumpActivityChanges(fstReaderContext *xc)
{
    return (xc ? xc->num_blackouts : 0);
}

uint64_t fstReaderGetDumpActivityChangeTime(fstReaderContext *xc, uint32_t idx)
{
    if (xc && (idx < xc->num_blackouts) && (xc->blackout_times)) {
        return (xc->blackout_times[idx]);
    } else {
        return (0);
    }
}

unsigned char fstReaderGetDumpActivityChangeValue(fstReaderContext *xc, uint32_t idx)
{
    if (xc && (idx < xc->num_blackouts) && (xc->blackout_activity)) {
        return (xc->blackout_activity[idx]);
    } else {
        return (0);
    }
}

void fstReaderSetLimitTimeRange(fstReaderContext *xc, uint64_t start_time, uint64_t end_time)
{
    if (xc) {
        xc->limit_range_valid = 1;
        xc->limit_range_start = start_time;
        xc->limit_range_end = end_time;
    }
}

void fstReaderSetUnlimitedTimeRange(fstReaderContext *xc)
{
    if (xc) {
        xc->limit_range_valid = 0;
    }
}

void fstReaderSetVcdExtensions(fstReaderContext *xc, int enable)
{
    if (xc) {
        xc->use_vcd_extensions = (enable != 0);
    }
}

void fstReaderIterBlocksSetNativeDoublesOnCallback(fstReaderContext *xc, int enable)
{
    if (xc) {
        xc->native_doubles_for_cb = (enable != 0);
    }
}

/*
 * hierarchy processing
 */
static void fstVcdID(char *buf, unsigned int value)
{
    char *pnt = buf;

    /* zero is illegal for a value...it is assumed they start at one */
    while (value) {
        value--;
        *(pnt++) = (char)('!' + value % 94);
        value = value / 94;
    }

    *pnt = 0;
}

static int fstVcdIDForFwrite(char *buf, unsigned int value)
{
    char *pnt = buf;
    int len = 0;

    /* zero is illegal for a value...it is assumed they start at one */
    while (value && len < 14) {
        value--;
        ++len;
        *(pnt++) = (char)('!' + value % 94);
        value = value / 94;
    }

    return len;
}

static int fstReaderRecreateHierFile(struct fstReaderContext *xc)
{
    int pass_status = 1;

    if (!xc->fh) {
        fst_off_t offs_cache = ftello(xc->f);
        int fnam_len = strlen(xc->filename) + 6 + 16 + 32 + 1;
        char *fnam = (char *)malloc(fnam_len);
        unsigned char *mem = (unsigned char *)malloc(FST_GZIO_LEN);
        fst_off_t hl, uclen;
        fst_off_t clen = 0;
        gzFile zhandle = NULL;
        int zfd;
        int htyp = FST_BL_SKIP;

        /* can't handle both set at once should never happen in a real file */
        if (!xc->contains_hier_section_lz4 && xc->contains_hier_section) {
            htyp = FST_BL_HIER;
        } else if (xc->contains_hier_section_lz4 && !xc->contains_hier_section) {
            htyp = xc->contains_hier_section_lz4duo ? FST_BL_HIER_LZ4DUO : FST_BL_HIER_LZ4;
        }

        snprintf(fnam, fnam_len, "%s.hier_%d_%p", xc->filename, getpid(), (void *)xc);
        fstReaderFseeko(xc, xc->f, xc->hier_pos, SEEK_SET);
        uclen = fstReaderUint64(xc->f);
#ifndef __MINGW32__
        fflush(xc->f);
#endif
        if (htyp == FST_BL_HIER) {
            fstReaderFseeko(xc, xc->f, xc->hier_pos, SEEK_SET);
            uclen = fstReaderUint64(xc->f);
#ifndef __MINGW32__
            fflush(xc->f);
#endif
            zfd = dup(fileno(xc->f));
            zhandle = gzdopen(zfd, "rb");
            if (!zhandle) {
                close(zfd);
                free(mem);
                free(fnam);
                return (0);
            }
        } else if ((htyp == FST_BL_HIER_LZ4) || (htyp == FST_BL_HIER_LZ4DUO)) {
            fstReaderFseeko(xc, xc->f, xc->hier_pos - 8, SEEK_SET); /* get section len */
            clen = fstReaderUint64(xc->f) - 16;
            uclen = fstReaderUint64(xc->f);
#ifndef __MINGW32__
            fflush(xc->f);
#endif
        }

#ifndef __MINGW32__
        xc->fh = fopen(fnam, "w+b");
        if (!xc->fh)
#endif
        {
            xc->fh = tmpfile_open(&xc->fh_nam);
            free(fnam);
            fnam = NULL;
            if (!xc->fh) {
                tmpfile_close(&xc->fh, &xc->fh_nam);
                free(mem);
                return (0);
            }
        }

#ifndef __MINGW32__
        if (fnam)
            unlink(fnam);
#endif

        if (htyp == FST_BL_HIER) {
            for (hl = 0; hl < uclen; hl += FST_GZIO_LEN) {
                size_t len = ((uclen - hl) > FST_GZIO_LEN) ? FST_GZIO_LEN : (uclen - hl);
                size_t gzreadlen = gzread(zhandle, mem, len); /* rc should equal len... */
                size_t fwlen;

                if (gzreadlen != len) {
                    pass_status = 0;
                    break;
                }

                fwlen = fstFwrite(mem, len, 1, xc->fh);
                if (fwlen != 1) {
                    pass_status = 0;
                    break;
                }
            }
            gzclose(zhandle);
        } else if (htyp == FST_BL_HIER_LZ4DUO) {
            unsigned char *lz4_cmem = (unsigned char *)malloc(clen);
            unsigned char *lz4_ucmem = (unsigned char *)malloc(uclen);
            unsigned char *lz4_ucmem2;
            uint64_t uclen2;
            int skiplen2 = 0;

            fstFread(lz4_cmem, clen, 1, xc->f);

            uclen2 = fstGetVarint64(lz4_cmem, &skiplen2);
            lz4_ucmem2 = (unsigned char *)malloc(uclen2);
            pass_status =
                (uclen2 == (uint64_t)LZ4_decompress_safe_partial((char *)lz4_cmem + skiplen2,
                                                                 (char *)lz4_ucmem2,
                                                                 clen - skiplen2,
                                                                 uclen2,
                                                                 uclen2));
            if (pass_status) {
                pass_status = (uclen == LZ4_decompress_safe_partial((char *)lz4_ucmem2,
                                                                    (char *)lz4_ucmem,
                                                                    uclen2,
                                                                    uclen,
                                                                    uclen));

                if (fstFwrite(lz4_ucmem, uclen, 1, xc->fh) != 1) {
                    pass_status = 0;
                }
            }

            free(lz4_ucmem2);
            free(lz4_ucmem);
            free(lz4_cmem);
        } else if (htyp == FST_BL_HIER_LZ4) {
            unsigned char *lz4_cmem = (unsigned char *)malloc(clen);
            unsigned char *lz4_ucmem = (unsigned char *)malloc(uclen);

            fstFread(lz4_cmem, clen, 1, xc->f);
            pass_status = (uclen == LZ4_decompress_safe_partial((char *)lz4_cmem,
                                                                (char *)lz4_ucmem,
                                                                clen,
                                                                uclen,
                                                                uclen));

            if (fstFwrite(lz4_ucmem, uclen, 1, xc->fh) != 1) {
                pass_status = 0;
            }

            free(lz4_ucmem);
            free(lz4_cmem);
        } else /* FST_BL_SKIP */
        {
            pass_status = 0;
            if (xc->fh) {
                fclose(xc->fh);
                xc->fh =
                    NULL; /* needed in case .hier file is missing and there are no hier sections */
            }
        }

        free(mem);
        free(fnam);

        fstReaderFseeko(xc, xc->f, offs_cache, SEEK_SET);
    }

    return (pass_status);
}

int fstReaderIterateHierRewind(fstReaderContext *xc)
{
    int pass_status = 0;

    if (xc) {
        pass_status = 1;
        if (!xc->fh) {
            pass_status = fstReaderRecreateHierFile(xc);
        }

        xc->do_rewind = 1;
    }

    return (pass_status);
}

struct fstHier *fstReaderIterateHier(fstReaderContext *xc)
{
    int isfeof;
    fstHandle alias;
    char *pnt;
    int ch;
    int unnamed_scope_idx = 0;

    if (!xc)
        return (NULL);

    if (!xc->fh) {
        if (!fstReaderRecreateHierFile(xc)) {
            return (NULL);
        }
    }

    if (xc->do_rewind) {
        xc->do_rewind = 0;
        xc->current_handle = 0;
        fstReaderFseeko(xc, xc->fh, 0, SEEK_SET);
        clearerr(xc->fh);
    }

    if (!(isfeof = feof(xc->fh))) {
        int tag = fgetc(xc->fh);
        int cl;
        switch (tag) {
            case FST_ST_VCD_SCOPE:
                xc->hier.htyp = FST_HT_SCOPE;
                xc->hier.u.scope.typ = fgetc(xc->fh);
                xc->hier.u.scope.name = pnt = xc->str_scope_nam;
                cl = 0;
                while ((ch = fgetc(xc->fh))) {
                    if (cl < FST_ID_NAM_SIZ) {
                        pnt[cl++] = ch;
                    }
                }; /* scopename */
                if (!cl) {
                    cl = snprintf(pnt, FST_ID_NAM_SIZ, "$unnamed_scope_%d", unnamed_scope_idx++);
                }
                pnt[cl] = 0;
                xc->hier.u.scope.name_length = cl;

                xc->hier.u.scope.component = pnt = xc->str_scope_comp;
                cl = 0;
                while ((ch = fgetc(xc->fh))) {
                    if (cl < FST_ID_NAM_SIZ) {
                        pnt[cl++] = ch;
                    }
                }; /* scopecomp */
                pnt[cl] = 0;
                xc->hier.u.scope.component_length = cl;
                break;

            case FST_ST_VCD_UPSCOPE:
                xc->hier.htyp = FST_HT_UPSCOPE;
                break;

            case FST_ST_GEN_ATTRBEGIN:
                xc->hier.htyp = FST_HT_ATTRBEGIN;
                xc->hier.u.attr.typ = fgetc(xc->fh);
                xc->hier.u.attr.subtype = fgetc(xc->fh);
                if (!xc->str_scope_attr) {
                    xc->str_scope_attr = (char *)calloc(1, FST_ID_NAM_ATTR_SIZ + 1);
                }
                xc->hier.u.attr.name = pnt = xc->str_scope_attr;
                cl = 0;
                while ((ch = fgetc(xc->fh))) {
                    if (cl < FST_ID_NAM_ATTR_SIZ) {
                        pnt[cl++] = ch;
                    }
                }; /* attrname */
                pnt[cl] = 0;
                xc->hier.u.attr.name_length = cl;

                xc->hier.u.attr.arg = fstReaderVarint64(xc->fh);

                if (xc->hier.u.attr.typ == FST_AT_MISC) {
                    if ((xc->hier.u.attr.subtype == FST_MT_SOURCESTEM) ||
                        (xc->hier.u.attr.subtype == FST_MT_SOURCEISTEM)) {
                        int sidx_skiplen_dummy = 0;
                        xc->hier.u.attr.arg_from_name =
                            fstGetVarint64((unsigned char *)xc->str_scope_attr,
                                           &sidx_skiplen_dummy);
                    }
                }
                break;

            case FST_ST_GEN_ATTREND:
                xc->hier.htyp = FST_HT_ATTREND;
                break;

            case FST_VT_VCD_EVENT:
            case FST_VT_VCD_INTEGER:
            case FST_VT_VCD_PARAMETER:
            case FST_VT_VCD_REAL:
            case FST_VT_VCD_REAL_PARAMETER:
            case FST_VT_VCD_REG:
            case FST_VT_VCD_SUPPLY0:
            case FST_VT_VCD_SUPPLY1:
            case FST_VT_VCD_TIME:
            case FST_VT_VCD_TRI:
            case FST_VT_VCD_TRIAND:
            case FST_VT_VCD_TRIOR:
            case FST_VT_VCD_TRIREG:
            case FST_VT_VCD_TRI0:
            case FST_VT_VCD_TRI1:
            case FST_VT_VCD_WAND:
            case FST_VT_VCD_WIRE:
            case FST_VT_VCD_WOR:
            case FST_VT_VCD_PORT:
            case FST_VT_VCD_SPARRAY:
            case FST_VT_VCD_REALTIME:
            case FST_VT_GEN_STRING:
            case FST_VT_SV_BIT:
            case FST_VT_SV_LOGIC:
            case FST_VT_SV_INT:
            case FST_VT_SV_SHORTINT:
            case FST_VT_SV_LONGINT:
            case FST_VT_SV_BYTE:
            case FST_VT_SV_ENUM:
            case FST_VT_SV_SHORTREAL:
                xc->hier.htyp = FST_HT_VAR;
                xc->hier.u.var.svt_workspace = FST_SVT_NONE;
                xc->hier.u.var.sdt_workspace = FST_SDT_NONE;
                xc->hier.u.var.sxt_workspace = 0;
                xc->hier.u.var.typ = tag;
                xc->hier.u.var.direction = fgetc(xc->fh);
                xc->hier.u.var.name = pnt = xc->str_scope_nam;
                cl = 0;
                while ((ch = fgetc(xc->fh))) {
                    if (cl < FST_ID_NAM_SIZ) {
                        pnt[cl++] = ch;
                    }
                }; /* varname */
                pnt[cl] = 0;
                xc->hier.u.var.name_length = cl;
                xc->hier.u.var.length = fstReaderVarint32(xc->fh);
                if (tag == FST_VT_VCD_PORT) {
                    xc->hier.u.var.length -= 2; /* removal of delimiting spaces */
                    xc->hier.u.var.length /= 3; /* port -> signal size adjust */
                }

                alias = fstReaderVarint32(xc->fh);

                if (!alias) {
                    xc->current_handle++;
                    xc->hier.u.var.handle = xc->current_handle;
                    xc->hier.u.var.is_alias = 0;
                } else {
                    xc->hier.u.var.handle = alias;
                    xc->hier.u.var.is_alias = 1;
                }

                break;

            default:
                isfeof = 1;
                break;
        }
    }

    return (!isfeof ? &xc->hier : NULL);
}

int fstReaderProcessHier(fstReaderContext *xc, FILE *fv)
{
    char *str;
    char *pnt;
    int ch, scopetype;
    int vartype;
    uint32_t len, alias;
    /* uint32_t maxvalpos=0; */
    unsigned int num_signal_dyn = 65536;
    int attrtype, subtype;
    uint64_t attrarg;
    fstHandle maxhandle_scanbuild;
    int cl;
    int unnamed_scope_idx = 0;

    if (!xc)
        return (0);

    xc->longest_signal_value_len =
        32; /* arbitrarily set at 32...this is much longer than an expanded double */

    if (!xc->fh) {
        if (!fstReaderRecreateHierFile(xc)) {
            return (0);
        }
    }

    str = (char *)malloc(FST_ID_NAM_ATTR_SIZ + 1);

    if (fv) {
        char time_dimension[2] = {0, 0};
        int time_scale = 1;

        fprintf(fv, "$date\n\t%s\n$end\n", xc->date);
        fprintf(fv, "$version\n\t%s\n$end\n", xc->version);
        if (xc->timezero)
            fprintf(fv, "$timezero\n\t%" PRId64 "\n$end\n", xc->timezero);

        switch (xc->timescale) {
            case 2:
                time_scale = 100;
                time_dimension[0] = 0;
                break;
            case 1:
                time_scale = 10; /* fallthrough */
            case 0:
                time_dimension[0] = 0;
                break;

            case -1:
                time_scale = 100;
                time_dimension[0] = 'm';
                break;
            case -2:
                time_scale = 10; /* fallthrough */
            case -3:
                time_dimension[0] = 'm';
                break;

            case -4:
                time_scale = 100;
                time_dimension[0] = 'u';
                break;
            case -5:
                time_scale = 10; /* fallthrough */
            case -6:
                time_dimension[0] = 'u';
                break;

            case -10:
                time_scale = 100;
                time_dimension[0] = 'p';
                break;
            case -11:
                time_scale = 10; /* fallthrough */
            case -12:
                time_dimension[0] = 'p';
                break;

            case -13:
                time_scale = 100;
                time_dimension[0] = 'f';
                break;
            case -14:
                time_scale = 10; /* fallthrough */
            case -15:
                time_dimension[0] = 'f';
                break;

            case -16:
                time_scale = 100;
                time_dimension[0] = 'a';
                break;
            case -17:
                time_scale = 10; /* fallthrough */
            case -18:
                time_dimension[0] = 'a';
                break;

            case -19:
                time_scale = 100;
                time_dimension[0] = 'z';
                break;
            case -20:
                time_scale = 10; /* fallthrough */
            case -21:
                time_dimension[0] = 'z';
                break;

            case -7:
                time_scale = 100;
                time_dimension[0] = 'n';
                break;
            case -8:
                time_scale = 10; /* fallthrough */
            case -9:
            default:
                time_dimension[0] = 'n';
                break;
        }

        if (fv)
            fprintf(fv, "$timescale\n\t%d%ss\n$end\n", time_scale, time_dimension);
    }

    xc->maxhandle = 0;
    xc->num_alias = 0;

    free(xc->signal_lens);
    xc->signal_lens = (uint32_t *)malloc(num_signal_dyn * sizeof(uint32_t));

    free(xc->signal_typs);
    xc->signal_typs = (unsigned char *)malloc(num_signal_dyn * sizeof(unsigned char));

    fstReaderFseeko(xc, xc->fh, 0, SEEK_SET);
    while (!feof(xc->fh)) {
        int tag = fgetc(xc->fh);
        switch (tag) {
            case FST_ST_VCD_SCOPE:
                scopetype = fgetc(xc->fh);
                if ((scopetype < FST_ST_MIN) || (scopetype > FST_ST_MAX))
                    scopetype = FST_ST_VCD_MODULE;
                pnt = str;
                cl = 0;
                while ((ch = fgetc(xc->fh))) {
                    if (cl < FST_ID_NAM_ATTR_SIZ) {
                        pnt[cl++] = ch;
                    }
                }; /* scopename */
                if (!cl) {
                    cl = snprintf(pnt, FST_ID_NAM_SIZ, "$unnamed_scope_%d", unnamed_scope_idx++);
                }
                pnt[cl] = 0;
                while (fgetc(xc->fh)) {
                }; /* scopecomp */

                if (fv)
                    fprintf(fv, "$scope %s %s $end\n", modtypes[scopetype], str);
                break;

            case FST_ST_VCD_UPSCOPE:
                if (fv)
                    fprintf(fv, "$upscope $end\n");
                break;

            case FST_ST_GEN_ATTRBEGIN:
                attrtype = fgetc(xc->fh);
                subtype = fgetc(xc->fh);
                pnt = str;
                cl = 0;
                while ((ch = fgetc(xc->fh))) {
                    if (cl < FST_ID_NAM_ATTR_SIZ) {
                        pnt[cl++] = ch;
                    }
                }; /* attrname */
                pnt[cl] = 0;

                if (!str[0]) {
                    strcpy(str, "\"\"");
                }

                attrarg = fstReaderVarint64(xc->fh);

                if (fv && xc->use_vcd_extensions) {
                    switch (attrtype) {
                        case FST_AT_ARRAY:
                            if ((subtype < FST_AR_NONE) || (subtype > FST_AR_MAX))
                                subtype = FST_AR_NONE;
                            fprintf(fv,
                                    "$attrbegin %s %s %s %" PRId64 " $end\n",
                                    attrtypes[attrtype],
                                    arraytypes[subtype],
                                    str,
                                    attrarg);
                            break;
                        case FST_AT_ENUM:
                            if ((subtype < FST_EV_SV_INTEGER) || (subtype > FST_EV_MAX))
                                subtype = FST_EV_SV_INTEGER;
                            fprintf(fv,
                                    "$attrbegin %s %s %s %" PRId64 " $end\n",
                                    attrtypes[attrtype],
                                    enumvaluetypes[subtype],
                                    str,
                                    attrarg);
                            break;
                        case FST_AT_PACK:
                            if ((subtype < FST_PT_NONE) || (subtype > FST_PT_MAX))
                                subtype = FST_PT_NONE;
                            fprintf(fv,
                                    "$attrbegin %s %s %s %" PRId64 " $end\n",
                                    attrtypes[attrtype],
                                    packtypes[subtype],
                                    str,
                                    attrarg);
                            break;
                        case FST_AT_MISC:
                        default:
                            attrtype = FST_AT_MISC;
                            if (subtype == FST_MT_COMMENT) {
                                fprintf(fv, "$comment\n\t%s\n$end\n", str);
                            } else {
                                if ((subtype == FST_MT_SOURCESTEM) ||
                                    (subtype == FST_MT_SOURCEISTEM)) {
                                    int sidx_skiplen_dummy = 0;
                                    uint64_t sidx =
                                        fstGetVarint64((unsigned char *)str, &sidx_skiplen_dummy);

                                    fprintf(fv,
                                            "$attrbegin %s %02x %" PRId64 " %" PRId64 " $end\n",
                                            attrtypes[attrtype],
                                            subtype,
                                            sidx,
                                            attrarg);
                                } else {
                                    fprintf(fv,
                                            "$attrbegin %s %02x %s %" PRId64 " $end\n",
                                            attrtypes[attrtype],
                                            subtype,
                                            str,
                                            attrarg);
                                }
                            }
                            break;
                    }
                }
                break;

            case FST_ST_GEN_ATTREND:
                if (fv && xc->use_vcd_extensions)
                    fprintf(fv, "$attrend $end\n");
                break;

            case FST_VT_VCD_EVENT:
            case FST_VT_VCD_INTEGER:
            case FST_VT_VCD_PARAMETER:
            case FST_VT_VCD_REAL:
            case FST_VT_VCD_REAL_PARAMETER:
            case FST_VT_VCD_REG:
            case FST_VT_VCD_SUPPLY0:
            case FST_VT_VCD_SUPPLY1:
            case FST_VT_VCD_TIME:
            case FST_VT_VCD_TRI:
            case FST_VT_VCD_TRIAND:
            case FST_VT_VCD_TRIOR:
            case FST_VT_VCD_TRIREG:
            case FST_VT_VCD_TRI0:
            case FST_VT_VCD_TRI1:
            case FST_VT_VCD_WAND:
            case FST_VT_VCD_WIRE:
            case FST_VT_VCD_WOR:
            case FST_VT_VCD_PORT:
            case FST_VT_VCD_SPARRAY:
            case FST_VT_VCD_REALTIME:
            case FST_VT_GEN_STRING:
            case FST_VT_SV_BIT:
            case FST_VT_SV_LOGIC:
            case FST_VT_SV_INT:
            case FST_VT_SV_SHORTINT:
            case FST_VT_SV_LONGINT:
            case FST_VT_SV_BYTE:
            case FST_VT_SV_ENUM:
            case FST_VT_SV_SHORTREAL:
                vartype = tag;
                /* vardir = */ fgetc(
                    xc->fh); /* unused in VCD reader, but need to advance read pointer */
                pnt = str;
                cl = 0;
                while ((ch = fgetc(xc->fh))) {
                    if (cl < FST_ID_NAM_ATTR_SIZ) {
                        pnt[cl++] = ch;
                    }
                }; /* varname */
                pnt[cl] = 0;
                len = fstReaderVarint32(xc->fh);
                alias = fstReaderVarint32(xc->fh);

                if (!alias) {
                    if (xc->maxhandle == num_signal_dyn) {
                        num_signal_dyn *= 2;
                        xc->signal_lens =
                            (uint32_t *)realloc(xc->signal_lens, num_signal_dyn * sizeof(uint32_t));
                        xc->signal_typs =
                            (unsigned char *)realloc(xc->signal_typs,
                                                     num_signal_dyn * sizeof(unsigned char));
                    }
                    xc->signal_lens[xc->maxhandle] = len;
                    xc->signal_typs[xc->maxhandle] = vartype;

                    /* maxvalpos+=len; */
                    if (len > xc->longest_signal_value_len) {
                        xc->longest_signal_value_len = len;
                    }

                    if ((vartype == FST_VT_VCD_REAL) || (vartype == FST_VT_VCD_REAL_PARAMETER) ||
                        (vartype == FST_VT_VCD_REALTIME) || (vartype == FST_VT_SV_SHORTREAL)) {
                        len = (vartype != FST_VT_SV_SHORTREAL) ? 64 : 32;
                        xc->signal_typs[xc->maxhandle] = FST_VT_VCD_REAL;
                    }
                    if (fv) {
                        char vcdid_buf[16];
                        uint32_t modlen = (vartype != FST_VT_VCD_PORT) ? len : ((len - 2) / 3);
                        fstVcdID(vcdid_buf, xc->maxhandle + 1);
                        fprintf(fv,
                                "$var %s %" PRIu32 " %s %s $end\n",
                                vartypes[vartype],
                                modlen,
                                vcdid_buf,
                                str);
                    }
                    xc->maxhandle++;
                } else {
                    if ((vartype == FST_VT_VCD_REAL) || (vartype == FST_VT_VCD_REAL_PARAMETER) ||
                        (vartype == FST_VT_VCD_REALTIME) || (vartype == FST_VT_SV_SHORTREAL)) {
                        len = (vartype != FST_VT_SV_SHORTREAL) ? 64 : 32;
                        xc->signal_typs[xc->maxhandle] = FST_VT_VCD_REAL;
                    }
                    if (fv) {
                        char vcdid_buf[16];
                        uint32_t modlen = (vartype != FST_VT_VCD_PORT) ? len : ((len - 2) / 3);
                        fstVcdID(vcdid_buf, alias);
                        fprintf(fv,
                                "$var %s %" PRIu32 " %s %s $end\n",
                                vartypes[vartype],
                                modlen,
                                vcdid_buf,
                                str);
                    }
                    xc->num_alias++;
                }

                break;

            default:
                break;
        }
    }
    if (fv)
        fprintf(fv, "$enddefinitions $end\n");

    maxhandle_scanbuild =
        xc->maxhandle
            ? xc->maxhandle
            : 1; /*scan-build warning suppression, in reality we have at least one signal */

    xc->signal_lens = (uint32_t *)realloc(xc->signal_lens, maxhandle_scanbuild * sizeof(uint32_t));
    xc->signal_typs =
        (unsigned char *)realloc(xc->signal_typs, maxhandle_scanbuild * sizeof(unsigned char));

    free(xc->process_mask);
    xc->process_mask = (unsigned char *)calloc(1, (maxhandle_scanbuild + 7) / 8);

    free(xc->temp_signal_value_buf);
    xc->temp_signal_value_buf = (unsigned char *)malloc(xc->longest_signal_value_len + 1);

    xc->var_count = xc->maxhandle + xc->num_alias;

    free(str);
    return (1);
}

/*
 * reader file open/close functions
 */
int fstReaderInit(struct fstReaderContext *xc)
{
    fst_off_t blkpos = 0;
    fst_off_t endfile;
    uint64_t seclen;
    int sectype;
    uint64_t vc_section_count_actual = 0;
    int hdr_incomplete = 0;
    int hdr_seen = 0;
    int gzread_pass_status = 1;

    sectype = fgetc(xc->f);
    if (sectype == FST_BL_ZWRAPPER) {
        FILE *fcomp;
        fst_off_t offpnt, uclen;
        char gz_membuf[FST_GZIO_LEN];
        gzFile zhandle;
        int zfd;
        int flen = strlen(xc->filename);
        char *hf;
        int hf_len;

        seclen = fstReaderUint64(xc->f);
        uclen = fstReaderUint64(xc->f);

        if (!seclen)
            return (0); /* not finished compressing, this is a failed read */

        hf_len = flen + 16 + 32 + 1;
        hf = (char *)calloc(1, hf_len);

        snprintf(hf, hf_len, "%s.upk_%d_%p", xc->filename, getpid(), (void *)xc);
        fcomp = fopen(hf, "w+b");
        if (!fcomp) {
            fcomp = tmpfile_open(&xc->f_nam);
            free(hf);
            hf = NULL;
            if (!fcomp) {
                tmpfile_close(&fcomp, &xc->f_nam);
                return (0);
            }
        }

#if defined(FST_UNBUFFERED_IO)
        setvbuf(fcomp,
                (char *)NULL,
                _IONBF,
                0); /* keeps gzip from acting weird in tandem with fopen */
#endif

#ifdef __MINGW32__
        xc->filename_unpacked = hf;
#else
        if (hf) {
            unlink(hf);
            free(hf);
        }
#endif

        fstReaderFseeko(xc, xc->f, FST_ZWRAPPER_HDR_SIZE, SEEK_SET);
#if !defined(__MINGW32__) && !defined(_MSC_VER)
        fflush(xc->f);
#else
        /* Windows UCRT runtime library reads one byte ahead in the file
           even with buffering disabled and does not synchronise the
           file position after fseek. */
        _lseek(fileno(xc->f), FST_ZWRAPPER_HDR_SIZE, SEEK_SET);
#endif

        zfd = dup(fileno(xc->f));
        zhandle = gzdopen(zfd, "rb");
        if (zhandle) {
            for (offpnt = 0; offpnt < uclen; offpnt += FST_GZIO_LEN) {
                size_t this_len =
                    ((uclen - offpnt) > FST_GZIO_LEN) ? FST_GZIO_LEN : (uclen - offpnt);
                size_t gzreadlen = gzread(zhandle, gz_membuf, this_len);
                size_t fwlen;

                if (gzreadlen != this_len) {
                    gzread_pass_status = 0;
                    break;
                }
                fwlen = fstFwrite(gz_membuf, this_len, 1, fcomp);
                if (fwlen != 1) {
                    gzread_pass_status = 0;
                    break;
                }
            }
            gzclose(zhandle);
        } else {
            close(zfd);
        }
        fflush(fcomp);
        fclose(xc->f);
        xc->f = fcomp;
    }

    if (gzread_pass_status) {
        fstReaderFseeko(xc, xc->f, 0, SEEK_END);
        endfile = ftello(xc->f);

        while (blkpos < endfile) {
            fstReaderFseeko(xc, xc->f, blkpos, SEEK_SET);

            sectype = fgetc(xc->f);
            seclen = fstReaderUint64(xc->f);

            if (sectype == EOF) {
                break;
            }

            if ((hdr_incomplete) && (!seclen)) {
                break;
            }

            if (!hdr_seen && (sectype != FST_BL_HDR)) {
                break;
            }

            blkpos++;
            if (sectype == FST_BL_HDR) {
                if (!hdr_seen) {
                    int ch;
                    double dcheck;

                    xc->start_time = fstReaderUint64(xc->f);
                    xc->end_time = fstReaderUint64(xc->f);

                    hdr_incomplete = (xc->start_time == 0) && (xc->end_time == 0);

                    fstFread(&dcheck, 8, 1, xc->f);
                   /*
                    * Yosys patch: Fix double endian check for i386 targets built in modern gcc
                    */
                    xc->double_endian_match = (dcheck == (double)FST_DOUBLE_ENDTEST);
                    if (!xc->double_endian_match) {
                        union
                        {
                            unsigned char rvs_buf[8];
                            double d;
                        } vu;

                        unsigned char *dcheck_alias = (unsigned char *)&dcheck;
                        int rvs_idx;

                        for (rvs_idx = 0; rvs_idx < 8; rvs_idx++) {
                            vu.rvs_buf[rvs_idx] = dcheck_alias[7 - rvs_idx];
                        }
                        if (vu.d != FST_DOUBLE_ENDTEST) {
                            break; /* either corrupt file or wrong architecture (offset +33 also
                                      functions as matchword) */
                        }
                    }

                    hdr_seen = 1;

                    xc->mem_used_by_writer = fstReaderUint64(xc->f);
                    xc->scope_count = fstReaderUint64(xc->f);
                    xc->var_count = fstReaderUint64(xc->f);
                    xc->maxhandle = fstReaderUint64(xc->f);
                    xc->num_alias = xc->var_count - xc->maxhandle;
                    xc->vc_section_count = fstReaderUint64(xc->f);
                    ch = fgetc(xc->f);
                    xc->timescale = (signed char)ch;
                    fstFread(xc->version, FST_HDR_SIM_VERSION_SIZE, 1, xc->f);
                    xc->version[FST_HDR_SIM_VERSION_SIZE] = 0;
                    fstFread(xc->date, FST_HDR_DATE_SIZE, 1, xc->f);
                    xc->date[FST_HDR_DATE_SIZE] = 0;
                    ch = fgetc(xc->f);
                    xc->filetype = (unsigned char)ch;
                    xc->timezero = fstReaderUint64(xc->f);
                }
            } else if ((sectype == FST_BL_VCDATA) || (sectype == FST_BL_VCDATA_DYN_ALIAS) ||
                       (sectype == FST_BL_VCDATA_DYN_ALIAS2)) {
                if (hdr_incomplete) {
                    uint64_t bt = fstReaderUint64(xc->f);
                    xc->end_time = fstReaderUint64(xc->f);

                    if (!vc_section_count_actual) {
                        xc->start_time = bt;
                    }
                }

                vc_section_count_actual++;
            } else if (sectype == FST_BL_GEOM) {
                if (!hdr_incomplete) {
                    uint64_t clen = seclen - 24;
                    uint64_t uclen = fstReaderUint64(xc->f);
                    unsigned char *ucdata = (unsigned char *)malloc(uclen);
                    unsigned char *pnt = ucdata;
                    unsigned int i;

                    xc->contains_geom_section = 1;
                    xc->maxhandle = fstReaderUint64(xc->f);
                    xc->longest_signal_value_len = 32; /* arbitrarily set at 32...this is much
                                                          longer than an expanded double */

                    free(xc->process_mask);
                    xc->process_mask = (unsigned char *)calloc(1, (xc->maxhandle + 7) / 8);

                    if (clen != uclen) {
                        unsigned char *cdata = (unsigned char *)malloc(clen);
                        unsigned long destlen = uclen;
                        unsigned long sourcelen = clen;
                        int rc;

                        fstFread(cdata, clen, 1, xc->f);
                        rc = uncompress(ucdata, &destlen, cdata, sourcelen);

                        if (rc != Z_OK) {
                            fprintf(stderr,
                                    FST_APIMESS
                                    "fstReaderInit(), geom uncompress rc = %d, exiting.\n",
                                    rc);
                            exit(255);
                        }

                        free(cdata);
                    } else {
                        fstFread(ucdata, uclen, 1, xc->f);
                    }

                    free(xc->signal_lens);
                    xc->signal_lens = (uint32_t *)malloc(sizeof(uint32_t) * xc->maxhandle);
                    free(xc->signal_typs);
                    xc->signal_typs =
                        (unsigned char *)malloc(sizeof(unsigned char) * xc->maxhandle);

                    for (i = 0; i < xc->maxhandle; i++) {
                        int skiplen;
                        uint64_t val = fstGetVarint32(pnt, &skiplen);

                        pnt += skiplen;

                        if (val) {
                            xc->signal_lens[i] = (val != 0xFFFFFFFF) ? val : 0;
                            xc->signal_typs[i] = FST_VT_VCD_WIRE;
                            if (xc->signal_lens[i] > xc->longest_signal_value_len) {
                                xc->longest_signal_value_len = xc->signal_lens[i];
                            }
                        } else {
                            xc->signal_lens[i] = 8; /* backpatch in real */
                            xc->signal_typs[i] = FST_VT_VCD_REAL;
                            /* xc->longest_signal_value_len handled above by overly large init size
                             */
                        }
                    }

                    free(xc->temp_signal_value_buf);
                    xc->temp_signal_value_buf =
                        (unsigned char *)malloc(xc->longest_signal_value_len + 1);

                    free(ucdata);
                }
            } else if (sectype == FST_BL_HIER) {
                xc->contains_hier_section = 1;
                xc->hier_pos = ftello(xc->f);
            } else if (sectype == FST_BL_HIER_LZ4DUO) {
                xc->contains_hier_section_lz4 = 1;
                xc->contains_hier_section_lz4duo = 1;
                xc->hier_pos = ftello(xc->f);
            } else if (sectype == FST_BL_HIER_LZ4) {
                xc->contains_hier_section_lz4 = 1;
                xc->hier_pos = ftello(xc->f);
            } else if (sectype == FST_BL_BLACKOUT) {
                uint32_t i;
                uint64_t cur_bl = 0;
                uint64_t delta;

                xc->num_blackouts = fstReaderVarint32(xc->f);
                free(xc->blackout_times);
                xc->blackout_times = (uint64_t *)calloc(xc->num_blackouts, sizeof(uint64_t));
                free(xc->blackout_activity);
                xc->blackout_activity =
                    (unsigned char *)calloc(xc->num_blackouts, sizeof(unsigned char));

                for (i = 0; i < xc->num_blackouts; i++) {
                    xc->blackout_activity[i] = fgetc(xc->f) != 0;
                    delta = fstReaderVarint64(xc->f);
                    cur_bl += delta;
                    xc->blackout_times[i] = cur_bl;
                }
            }

            blkpos += seclen;
            if (!hdr_seen)
                break;
        }

        if (hdr_seen) {
            if (xc->vc_section_count != vc_section_count_actual) {
                xc->vc_section_count = vc_section_count_actual;
            }

            if (!xc->contains_geom_section) {
                fstReaderProcessHier(xc, NULL); /* recreate signal_lens/signal_typs info */
            }
        }
    }

    return (hdr_seen);
}

fstReaderContext *fstReaderOpenForUtilitiesOnly(void)
{
    fstReaderContext *xc = (fstReaderContext *)calloc(1, sizeof(fstReaderContext));

    return (xc);
}

fstReaderContext *fstReaderOpen(const char *nam)
{
    fstReaderContext *xc = (fstReaderContext *)calloc(1, sizeof(fstReaderContext));

    if ((!nam) || (!(xc->f = fopen(nam, "rb")))) {
        free(xc);
        xc = NULL;
    } else {
        int flen = strlen(nam);
        char *hf = (char *)calloc(1, flen + 6);
        int rc;

#if defined(FST_UNBUFFERED_IO)
        setvbuf(xc->f,
                (char *)NULL,
                _IONBF,
                0); /* keeps gzip from acting weird in tandem with fopen */
#endif

        memcpy(hf, nam, flen);
        strcpy(hf + flen, ".hier");
        xc->fh = fopen(hf, "rb");

        free(hf);
        xc->filename = strdup(nam);
        rc = fstReaderInit(xc);

        if ((rc) && (xc->vc_section_count) && (xc->maxhandle) &&
            ((xc->fh) || (xc->contains_hier_section || (xc->contains_hier_section_lz4)))) {
            /* more init */
            xc->do_rewind = 1;
        } else if (!rc) {
            fstReaderClose(xc);
            xc = NULL;
        }
    }

    return (xc);
}

static void fstReaderDeallocateRvatData(fstReaderContext *xc)
{
    if (xc) {
        free(xc->rvat_chain_mem);
        xc->rvat_chain_mem = NULL;
        free(xc->rvat_frame_data);
        xc->rvat_frame_data = NULL;
        free(xc->rvat_time_table);
        xc->rvat_time_table = NULL;
        free(xc->rvat_chain_table);
        xc->rvat_chain_table = NULL;
        free(xc->rvat_chain_table_lengths);
        xc->rvat_chain_table_lengths = NULL;

        xc->rvat_data_valid = 0;
    }
}

void fstReaderClose(fstReaderContext *xc)
{
    if (xc) {
        fstReaderDeallocateScopeData(xc);
        fstReaderDeallocateRvatData(xc);
        free(xc->rvat_sig_offs);
        xc->rvat_sig_offs = NULL;

        free(xc->process_mask);
        xc->process_mask = NULL;
        free(xc->blackout_times);
        xc->blackout_times = NULL;
        free(xc->blackout_activity);
        xc->blackout_activity = NULL;
        free(xc->temp_signal_value_buf);
        xc->temp_signal_value_buf = NULL;
        free(xc->signal_typs);
        xc->signal_typs = NULL;
        free(xc->signal_lens);
        xc->signal_lens = NULL;
        free(xc->filename);
        xc->filename = NULL;
        free(xc->str_scope_attr);
        xc->str_scope_attr = NULL;

        if (xc->fh) {
            tmpfile_close(&xc->fh, &xc->fh_nam);
        }

        if (xc->f) {
            tmpfile_close(&xc->f, &xc->f_nam);
            if (xc->filename_unpacked) {
                unlink(xc->filename_unpacked);
                free(xc->filename_unpacked);
            }
        }

        free(xc);
    }
}

/*
 * read processing
 */

/* normal read which re-interleaves the value change data */
int fstReaderIterBlocks(fstReaderContext *ctx,
                        void (*value_change_callback)(void *user_callback_data_pointer,
                                                      uint64_t time,
                                                      fstHandle facidx,
                                                      const unsigned char *value),
                        void *user_callback_data_pointer,
                        FILE *fv)
{
    return (fstReaderIterBlocks2(ctx, value_change_callback, NULL, user_callback_data_pointer, fv));
}

int fstReaderIterBlocks2(fstReaderContext *ctx,
                         void (*value_change_callback)(void *user_callback_data_pointer,
                                                       uint64_t time,
                                                       fstHandle facidx,
                                                       const unsigned char *value),
                         void (*value_change_callback_varlen)(void *user_callback_data_pointer,
                                                              uint64_t time,
                                                              fstHandle facidx,
                                                              const unsigned char *value,
                                                              uint32_t len),
                         void *user_callback_data_pointer,
                         FILE *fv)
{
    struct fstReaderContext *xc = (struct fstReaderContext *)ctx;

    uint64_t previous_time = UINT64_MAX;
    uint64_t *time_table = NULL;
    uint64_t tsec_nitems;
    unsigned int secnum = 0;
    int blocks_skipped = 0;
    fst_off_t blkpos = 0;
    uint64_t seclen, beg_tim;
    uint64_t end_tim;
    uint64_t frame_uclen, frame_clen, frame_maxhandle, vc_maxhandle;
    fst_off_t vc_start;
    fst_off_t indx_pntr, indx_pos;
    fst_off_t *chain_table = NULL;
    uint32_t *chain_table_lengths = NULL;
    unsigned char *chain_cmem;
    unsigned char *pnt;
    long chain_clen;
    fstHandle idx, pidx = 0, i;
    uint64_t pval;
    uint64_t vc_maxhandle_largest = 0;
    uint64_t tsec_uclen = 0, tsec_clen = 0;
    int sectype;
    uint64_t mem_required_for_traversal;
    unsigned char *mem_for_traversal = NULL;
    uint32_t traversal_mem_offs;
    uint32_t *scatterptr, *headptr, *length_remaining;
    uint32_t cur_blackout = 0;
    int packtype;
    unsigned char *mc_mem = NULL;
    uint32_t mc_mem_len; /* corresponds to largest value encountered in chain_table_lengths[i] */
    int dumpvars_state = 0;

    if (!xc)
        return (0);

    scatterptr = (uint32_t *)calloc(xc->maxhandle, sizeof(uint32_t));
    headptr = (uint32_t *)calloc(xc->maxhandle, sizeof(uint32_t));
    length_remaining = (uint32_t *)calloc(xc->maxhandle, sizeof(uint32_t));

    if (fv) {
#ifndef FST_WRITEX_DISABLE
        fflush(fv);
        setvbuf(fv, (char *)NULL, _IONBF, 0); /* even buffered IO is slow so disable it and use our
                                                 own routines that don't need seeking */
        xc->writex_fd = fileno(fv);
#endif
    }

    for (;;) {
        uint32_t *tc_head = NULL;
        uint32_t tc_head_items = 0;
        traversal_mem_offs = 0;

        fstReaderFseeko(xc, xc->f, blkpos, SEEK_SET);

        sectype = fgetc(xc->f);
        seclen = fstReaderUint64(xc->f);

        if ((sectype == EOF) || (sectype == FST_BL_SKIP)) {
#ifdef FST_DEBUG
            fprintf(stderr, FST_APIMESS "<< EOF >>\n");
#endif
            break;
        }

        blkpos++;
        if ((sectype != FST_BL_VCDATA) && (sectype != FST_BL_VCDATA_DYN_ALIAS) &&
            (sectype != FST_BL_VCDATA_DYN_ALIAS2)) {
            blkpos += seclen;
            continue;
        }

        if (!seclen)
            break;

        beg_tim = fstReaderUint64(xc->f);
        end_tim = fstReaderUint64(xc->f);

        if (xc->limit_range_valid) {
            if (end_tim < xc->limit_range_start) {
                blocks_skipped++;
                blkpos += seclen;
                continue;
            }

            if (beg_tim > xc->limit_range_end) /* likely the compare in for(i=0;i<tsec_nitems;i++)
                                                  below would do this earlier */
            {
                break;
            }
        }

        mem_required_for_traversal =
            fstReaderUint64(xc->f) + 66; /* add in potential fastlz overhead */
        mem_for_traversal = (unsigned char *)malloc(mem_required_for_traversal);
#ifdef FST_DEBUG
        fprintf(stderr,
                FST_APIMESS "sec: %u seclen: %d begtim: %d endtim: %d\n",
                secnum,
                (int)seclen,
                (int)beg_tim,
                (int)end_tim);
        fprintf(stderr,
                FST_APIMESS "mem_required_for_traversal: %d\n",
                (int)mem_required_for_traversal - 66);
#endif
        /* process time block */
        {
            unsigned char *ucdata;
            unsigned char *cdata;
            unsigned long destlen /* = tsec_uclen */; /* scan-build */
            unsigned long sourcelen /*= tsec_clen */; /* scan-build */
            int rc;
            unsigned char *tpnt;
            uint64_t tpval;
            unsigned int ti;

            if (fstReaderFseeko(xc, xc->f, blkpos + seclen - 24, SEEK_SET) != 0)
                break;
            tsec_uclen = fstReaderUint64(xc->f);
            tsec_clen = fstReaderUint64(xc->f);
            tsec_nitems = fstReaderUint64(xc->f);
#ifdef FST_DEBUG
            fprintf(stderr,
                    FST_APIMESS "time section unc: %d, com: %d (%d items)\n",
                    (int)tsec_uclen,
                    (int)tsec_clen,
                    (int)tsec_nitems);
#endif
            if (tsec_clen > seclen)
                break; /* corrupted tsec_clen: by definition it can't be larger than size of section
                        */
            ucdata = (unsigned char *)malloc(tsec_uclen);
            if (!ucdata)
                break; /* malloc fail as tsec_uclen out of range from corrupted file */
            destlen = tsec_uclen;
            sourcelen = tsec_clen;

            fstReaderFseeko(xc, xc->f, -24 - ((fst_off_t)tsec_clen), SEEK_CUR);

            if (tsec_uclen != tsec_clen) {
                cdata = (unsigned char *)malloc(tsec_clen);
                fstFread(cdata, tsec_clen, 1, xc->f);

                rc = uncompress(ucdata, &destlen, cdata, sourcelen);

                if (rc != Z_OK) {
                    fprintf(stderr,
                            FST_APIMESS
                            "fstReaderIterBlocks2(), tsec uncompress rc = %d, exiting.\n",
                            rc);
                    exit(255);
                }

                free(cdata);
            } else {
                fstFread(ucdata, tsec_uclen, 1, xc->f);
            }

            free(time_table);

            if (sizeof(size_t) < sizeof(uint64_t)) {
                /* TALOS-2023-1792 for 32b overflow */
                uint64_t chk_64 = tsec_nitems * sizeof(uint64_t);
                size_t chk_32 = ((size_t)tsec_nitems) * sizeof(uint64_t);
                if (chk_64 != chk_32)
                    chk_report_abort("TALOS-2023-1792");
            } else {
                uint64_t chk_64 = tsec_nitems * sizeof(uint64_t);
                if ((chk_64 / sizeof(uint64_t)) != tsec_nitems) {
                    chk_report_abort("TALOS-2023-1792");
                }
            }
            time_table = (uint64_t *)calloc(tsec_nitems, sizeof(uint64_t));
            tpnt = ucdata;
            tpval = 0;
            for (ti = 0; ti < tsec_nitems; ti++) {
                int skiplen;
                uint64_t val = fstGetVarint64(tpnt, &skiplen);
                tpval = time_table[ti] = tpval + val;
                tpnt += skiplen;
            }

            tc_head_items = tsec_nitems /* scan-build */ ? tsec_nitems : 1;
            if (sizeof(size_t) < sizeof(uint64_t)) {
                /* TALOS-2023-1792 for 32b overflow */
                uint64_t chk_64 = tc_head_items * sizeof(uint32_t);
                size_t chk_32 = ((size_t)tc_head_items) * sizeof(uint32_t);
                if (chk_64 != chk_32)
                    chk_report_abort("TALOS-2023-1792");
            } else {
                uint64_t chk_64 = tc_head_items * sizeof(uint32_t);
                if ((chk_64 / sizeof(uint32_t)) != tc_head_items) {
                    chk_report_abort("TALOS-2023-1792");
                }
            }
            tc_head = (uint32_t *)calloc(tc_head_items, sizeof(uint32_t));
            free(ucdata);
        }

        fstReaderFseeko(xc, xc->f, blkpos + 32, SEEK_SET);

        frame_uclen = fstReaderVarint64(xc->f);
        frame_clen = fstReaderVarint64(xc->f);
        frame_maxhandle = fstReaderVarint64(xc->f);

        if (secnum == 0) {
            if ((beg_tim != time_table[0]) || (blocks_skipped)) {
                unsigned char *mu = (unsigned char *)malloc(frame_uclen);
                uint32_t sig_offs = 0;

                if (fv) {
                    char wx_buf[32];
                    int wx_len;

                    if (beg_tim) {
                        if (dumpvars_state == 1) {
                            wx_len = snprintf(wx_buf, 32, "$end\n");
                            fstWritex(xc, wx_buf, wx_len);
                            dumpvars_state = 2;
                        }
                        wx_len = snprintf(wx_buf, 32, "#%" PRIu64 "\n", beg_tim);
                        fstWritex(xc, wx_buf, wx_len);
                        if (!dumpvars_state) {
                            wx_len = snprintf(wx_buf, 32, "$dumpvars\n");
                            fstWritex(xc, wx_buf, wx_len);
                            dumpvars_state = 1;
                        }
                    }
                    if ((xc->num_blackouts) && (cur_blackout != xc->num_blackouts)) {
                        if (beg_tim == xc->blackout_times[cur_blackout]) {
                            wx_len =
                                snprintf(wx_buf,
                                         32,
                                         "$dump%s $end\n",
                                         (xc->blackout_activity[cur_blackout++]) ? "on" : "off");
                            fstWritex(xc, wx_buf, wx_len);
                        }
                    }
                }

                if (frame_uclen == frame_clen) {
                    fstFread(mu, frame_uclen, 1, xc->f);
                } else {
                    unsigned char *mc = (unsigned char *)malloc(frame_clen);
                    int rc;

                    unsigned long destlen = frame_uclen;
                    unsigned long sourcelen = frame_clen;

                    fstFread(mc, sourcelen, 1, xc->f);
                    rc = uncompress(mu, &destlen, mc, sourcelen);
                    if (rc != Z_OK) {
                        fprintf(stderr,
                                FST_APIMESS
                                "fstReaderIterBlocks2(), frame uncompress rc: %d, exiting.\n",
                                rc);
                        exit(255);
                    }
                    free(mc);
                }

                for (idx = 0; idx < frame_maxhandle; idx++) {
                    int process_idx = idx / 8;
                    int process_bit = idx & 7;

                    if (xc->process_mask[process_idx] & (1 << process_bit)) {
                        if (xc->signal_lens[idx] <= 1) {
                            if (xc->signal_lens[idx] == 1) {
                                unsigned char val = mu[sig_offs];
                                if (value_change_callback) {
                                    xc->temp_signal_value_buf[0] = val;
                                    xc->temp_signal_value_buf[1] = 0;
                                    value_change_callback(user_callback_data_pointer,
                                                          beg_tim,
                                                          idx + 1,
                                                          xc->temp_signal_value_buf);
                                } else {
                                    if (fv) {
                                        char vcd_id[16];

                                        int vcdid_len = fstVcdIDForFwrite(vcd_id + 1, idx + 1);
                                        vcd_id[0] = val; /* collapse 3 writes into one I/O call */
                                        vcd_id[vcdid_len + 1] = '\n';
                                        fstWritex(xc, vcd_id, vcdid_len + 2);
                                    }
                                }
                            } else {
                                /* variable-length ("0" length) records have no initial state */
                            }
                        } else {
                            if (xc->signal_typs[idx] != FST_VT_VCD_REAL) {
                                if (value_change_callback) {
                                    if (xc->signal_lens[idx] > xc->longest_signal_value_len) {
                                        chk_report_abort("TALOS-2023-1797");
                                    }
                                    memcpy(xc->temp_signal_value_buf,
                                           mu + sig_offs,
                                           xc->signal_lens[idx]);
                                    xc->temp_signal_value_buf[xc->signal_lens[idx]] = 0;
                                    value_change_callback(user_callback_data_pointer,
                                                          beg_tim,
                                                          idx + 1,
                                                          xc->temp_signal_value_buf);
                                } else {
                                    if (fv) {
                                        char vcd_id[16];
                                        int vcdid_len = fstVcdIDForFwrite(vcd_id + 1, idx + 1);

                                        vcd_id[0] =
                                            (xc->signal_typs[idx] != FST_VT_VCD_PORT) ? 'b' : 'p';
                                        fstWritex(xc, vcd_id, 1);
                                        if ((sig_offs + xc->signal_lens[idx]) > frame_uclen) {
                                            chk_report_abort("TALOS-2023-1793");
                                        }
                                        fstWritex(xc, mu + sig_offs, xc->signal_lens[idx]);

                                        vcd_id[0] = ' '; /* collapse 3 writes into one I/O call */
                                        vcd_id[vcdid_len + 1] = '\n';
                                        fstWritex(xc, vcd_id, vcdid_len + 2);
                                    }
                                }
                            } else {
                                double d;
                                unsigned char *clone_d;
                                unsigned char *srcdata = mu + sig_offs;

                                if (value_change_callback) {
                                    if (xc->native_doubles_for_cb) {
                                        if (xc->double_endian_match) {
                                            clone_d = srcdata;
                                        } else {
                                            int j;

                                            clone_d = (unsigned char *)&d;
                                            for (j = 0; j < 8; j++) {
                                                clone_d[j] = srcdata[7 - j];
                                            }
                                        }
                                        value_change_callback(user_callback_data_pointer,
                                                              beg_tim,
                                                              idx + 1,
                                                              clone_d);
                                    } else {
                                        clone_d = (unsigned char *)&d;
                                        if (xc->double_endian_match) {
                                            memcpy(clone_d, srcdata, 8);
                                        } else {
                                            int j;

                                            for (j = 0; j < 8; j++) {
                                                clone_d[j] = srcdata[7 - j];
                                            }
                                        }
                                        snprintf((char *)xc->temp_signal_value_buf,
                                                 xc->longest_signal_value_len + 1,
                                                 "%.16g",
                                                 d);
                                        value_change_callback(user_callback_data_pointer,
                                                              beg_tim,
                                                              idx + 1,
                                                              xc->temp_signal_value_buf);
                                    }
                                } else {
                                    if (fv) {
                                        char vcdid_buf[16];
                                        char wx_buf[64];
                                        int wx_len;

                                        clone_d = (unsigned char *)&d;
                                        if (xc->double_endian_match) {
                                            memcpy(clone_d, srcdata, 8);
                                        } else {
                                            int j;

                                            for (j = 0; j < 8; j++) {
                                                clone_d[j] = srcdata[7 - j];
                                            }
                                        }

                                        fstVcdID(vcdid_buf, idx + 1);
                                        wx_len = snprintf(wx_buf, 64, "r%.16g %s\n", d, vcdid_buf);
                                        fstWritex(xc, wx_buf, wx_len);
                                    }
                                }
                            }
                        }
                    }

                    sig_offs += xc->signal_lens[idx];
                }

                free(mu);
                fstReaderFseeko(xc, xc->f, -((fst_off_t)frame_clen), SEEK_CUR);
            }
        }

        fstReaderFseeko(xc, xc->f, (fst_off_t)frame_clen, SEEK_CUR); /* skip past compressed data */

        vc_maxhandle = fstReaderVarint64(xc->f);
        vc_start = ftello(xc->f); /* points to '!' character */
        packtype = fgetc(xc->f);

#ifdef FST_DEBUG
        fprintf(stderr,
                FST_APIMESS "frame_uclen: %d, frame_clen: %d, frame_maxhandle: %d\n",
                (int)frame_uclen,
                (int)frame_clen,
                (int)frame_maxhandle);
        fprintf(stderr,
                FST_APIMESS "vc_maxhandle: %d, packtype: %c\n",
                (int)vc_maxhandle,
                packtype);
#endif

        indx_pntr = blkpos + seclen - 24 - tsec_clen - 8;
        fstReaderFseeko(xc, xc->f, indx_pntr, SEEK_SET);
        chain_clen = fstReaderUint64(xc->f);
        indx_pos = indx_pntr - chain_clen;
#ifdef FST_DEBUG
        fprintf(stderr, FST_APIMESS "indx_pos: %d (%d bytes)\n", (int)indx_pos, (int)chain_clen);
#endif
        chain_cmem = (unsigned char *)malloc(chain_clen);
        if (!chain_cmem)
            goto block_err;
        fstReaderFseeko(xc, xc->f, indx_pos, SEEK_SET);
        fstFread(chain_cmem, chain_clen, 1, xc->f);

        if (vc_maxhandle > vc_maxhandle_largest) {
            free(chain_table);
            free(chain_table_lengths);

            vc_maxhandle_largest = vc_maxhandle;

            if (!(vc_maxhandle + 1)) {
                chk_report_abort("TALOS-2023-1798");
            }

            if (sizeof(size_t) < sizeof(uint64_t)) {
                /* TALOS-2023-1798 for 32b overflow */
                uint64_t chk_64 = (vc_maxhandle + 1) * sizeof(fst_off_t);
                size_t chk_32 = ((size_t)(vc_maxhandle + 1)) * sizeof(fst_off_t);
                if (chk_64 != chk_32)
                    chk_report_abort("TALOS-2023-1798");
            } else {
                uint64_t chk_64 = (vc_maxhandle + 1) * sizeof(fst_off_t);
                if ((chk_64 / sizeof(fst_off_t)) != (vc_maxhandle + 1)) {
                    chk_report_abort("TALOS-2023-1798");
                }
            }
            chain_table = (fst_off_t *)calloc((vc_maxhandle + 1), sizeof(fst_off_t));

            if (sizeof(size_t) < sizeof(uint64_t)) {
                /* TALOS-2023-1798 for 32b overflow */
                uint64_t chk_64 = (vc_maxhandle + 1) * sizeof(uint32_t);
                size_t chk_32 = ((size_t)(vc_maxhandle + 1)) * sizeof(uint32_t);
                if (chk_64 != chk_32)
                    chk_report_abort("TALOS-2023-1798");
            } else {
                uint64_t chk_64 = (vc_maxhandle + 1) * sizeof(uint32_t);
                if ((chk_64 / sizeof(uint32_t)) != (vc_maxhandle + 1)) {
                    chk_report_abort("TALOS-2023-1798");
                }
            }
            chain_table_lengths = (uint32_t *)calloc((vc_maxhandle + 1), sizeof(uint32_t));
        }

        if (!chain_table || !chain_table_lengths)
            goto block_err;

        pnt = chain_cmem;
        idx = 0;
        pval = 0;

        if (sectype == FST_BL_VCDATA_DYN_ALIAS2) {
            uint32_t prev_alias = 0;

            do {
                int skiplen;

                if (*pnt & 0x01) {
                    int64_t shval = fstGetSVarint64(pnt, &skiplen) >> 1;
                    if (shval > 0) {
                        pval = chain_table[idx] = pval + shval;
                        if (idx) {
                            chain_table_lengths[pidx] = pval - chain_table[pidx];
                        }
                        pidx = idx++;
                    } else if (shval < 0) {
                        chain_table[idx] =
                            0; /* need to explicitly zero as calloc above might not run */
                        chain_table_lengths[idx] = prev_alias =
                            shval; /* because during this loop iter would give stale data! */
                        idx++;
                    } else {
                        chain_table[idx] =
                            0; /* need to explicitly zero as calloc above might not run */
                        chain_table_lengths[idx] =
                            prev_alias; /* because during this loop iter would give stale data! */
                        idx++;
                    }
                } else {
                    uint64_t val = fstGetVarint32(pnt, &skiplen);

                    fstHandle loopcnt = val >> 1;
                    if ((idx + loopcnt - 1) > vc_maxhandle) /* TALOS-2023-1789 */
                    {
                        chk_report_abort("TALOS-2023-1789");
                    }

                    for (i = 0; i < loopcnt; i++) {
                        chain_table[idx++] = 0;
                    }
                }

                pnt += skiplen;
            } while (pnt != (chain_cmem + chain_clen));
        } else {
            do {
                int skiplen;
                uint64_t val = fstGetVarint32(pnt, &skiplen);

                if (!val) {
                    pnt += skiplen;
                    val = fstGetVarint32(pnt, &skiplen);
                    chain_table[idx] =
                        0; /* need to explicitly zero as calloc above might not run */
                    chain_table_lengths[idx] =
                        -val; /* because during this loop iter would give stale data! */
                    idx++;
                } else if (val & 1) {
                    pval = chain_table[idx] = pval + (val >> 1);
                    if (idx) {
                        chain_table_lengths[pidx] = pval - chain_table[pidx];
                    }
                    pidx = idx++;
                } else {
                    fstHandle loopcnt = val >> 1;

                    if ((idx + loopcnt - 1) > vc_maxhandle) /* TALOS-2023-1789 */
                    {
                        chk_report_abort("TALOS-2023-1789");
                    }

                    for (i = 0; i < loopcnt; i++) {
                        chain_table[idx++] = 0;
                    }
                }

                pnt += skiplen;
            } while (pnt != (chain_cmem + chain_clen));
        }

        chain_table[idx] = indx_pos - vc_start;
        chain_table_lengths[pidx] = chain_table[idx] - chain_table[pidx];

        for (i = 0; i < idx; i++) {
            int32_t v32 = chain_table_lengths[i];
            if ((v32 < 0) && (!chain_table[i])) {
                v32 = -v32;
                v32--;
                if (((uint32_t)v32) < i) /* sanity check */
                {
                    chain_table[i] = chain_table[v32];
                    chain_table_lengths[i] = chain_table_lengths[v32];
                }
            }
        }

#ifdef FST_DEBUG
        fprintf(stderr, FST_APIMESS "decompressed chain idx len: %" PRIu32 "\n", idx);
#endif

        mc_mem_len = 16384;
        mc_mem = (unsigned char *)malloc(mc_mem_len); /* buffer for compressed reads */

        /* check compressed VC data */
        if (idx > xc->maxhandle)
            idx = xc->maxhandle;
        for (i = 0; i < idx; i++) {
            if (chain_table[i]) {
                int process_idx = i / 8;
                int process_bit = i & 7;

                if (xc->process_mask[process_idx] & (1 << process_bit)) {
                    int rc = Z_OK;
                    uint32_t val;
                    uint32_t skiplen;
                    uint32_t tdelta;

                    fstReaderFseeko(xc, xc->f, vc_start + chain_table[i], SEEK_SET);
                    val = fstReaderVarint32WithSkip(xc->f, &skiplen);
                    if (val) {
                        unsigned char *mu =
                            mem_for_traversal + traversal_mem_offs; /* uncomp: dst */
                        unsigned char *mc; /* comp:   src */
                        unsigned long destlen = val;
                        unsigned long sourcelen = chain_table_lengths[i];

                        if (traversal_mem_offs >= mem_required_for_traversal) {
                            chk_report_abort("TALOS-2023-1785");
                        }

                        if (mc_mem_len < chain_table_lengths[i]) {
                            free(mc_mem);
                            mc_mem = (unsigned char *)malloc(mc_mem_len = chain_table_lengths[i]);
                        }
                        mc = mc_mem;

                        fstFread(mc, chain_table_lengths[i], 1, xc->f);

                        switch (packtype) {
                            case '4':
                                rc = (destlen ==
                                      (unsigned long)LZ4_decompress_safe_partial((char *)mc,
                                                                                 (char *)mu,
                                                                                 sourcelen,
                                                                                 destlen,
                                                                                 destlen))
                                         ? Z_OK
                                         : Z_DATA_ERROR;
                                break;
                            case 'F':
                                fastlz_decompress(mc,
                                                  sourcelen,
                                                  mu,
                                                  destlen); /* rc appears unreliable */
                                break;
                            default:
                                rc = uncompress(mu, &destlen, mc, sourcelen);
                                break;
                        }

                        /* data to process is for(j=0;j<destlen;j++) in mu[j] */
                        headptr[i] = traversal_mem_offs;
                        length_remaining[i] = val;
                        traversal_mem_offs += val;
                    } else {
                        int destlen = chain_table_lengths[i] - skiplen;
                        unsigned char *mu = mem_for_traversal + traversal_mem_offs;

                        if (traversal_mem_offs >= mem_required_for_traversal) {
                            chk_report_abort("TALOS-2023-1785");
                        }

                        fstFread(mu, destlen, 1, xc->f);
                        /* data to process is for(j=0;j<destlen;j++) in mu[j] */
                        headptr[i] = traversal_mem_offs;
                        length_remaining[i] = destlen;
                        traversal_mem_offs += destlen;
                    }

                    if (rc != Z_OK) {
                        fprintf(stderr,
                                FST_APIMESS
                                "fstReaderIterBlocks2(), fac: %d clen: %d (rc=%d), exiting.\n",
                                (int)i,
                                (int)val,
                                rc);
                        exit(255);
                    }

                    if (xc->signal_lens[i] == 1) {
                        uint32_t vli = fstGetVarint32NoSkip(mem_for_traversal + headptr[i]);
                        uint32_t shcnt = 2 << (vli & 1);
                        tdelta = vli >> shcnt;
                    } else {
                        uint32_t vli = fstGetVarint32NoSkip(mem_for_traversal + headptr[i]);
                        tdelta = vli >> 1;
                    }

                    if (tdelta >= tc_head_items) {
                        chk_report_abort("TALOS-2023-1791");
                    }

                    scatterptr[i] = tc_head[tdelta];
                    tc_head[tdelta] = i + 1;
                }
            }
        }

        free(mc_mem); /* there is no usage below for this, no real need to clear out mc_mem or
                         mc_mem_len */

        for (i = 0; i < tsec_nitems; i++) {
            uint32_t tdelta;
            int skiplen, skiplen2;
            uint32_t vli;

            if (fv) {
                char wx_buf[32];
                int wx_len;

                if (time_table[i] != previous_time) {
                    if (xc->limit_range_valid) {
                        if (time_table[i] > xc->limit_range_end) {
                            break;
                        }
                    }

                    if (dumpvars_state == 1) {
                        wx_len = snprintf(wx_buf, 32, "$end\n");
                        fstWritex(xc, wx_buf, wx_len);
                        dumpvars_state = 2;
                    }
                    wx_len = snprintf(wx_buf, 32, "#%" PRIu64 "\n", time_table[i]);
                    fstWritex(xc, wx_buf, wx_len);
                    if (!dumpvars_state) {
                        wx_len = snprintf(wx_buf, 32, "$dumpvars\n");
                        fstWritex(xc, wx_buf, wx_len);
                        dumpvars_state = 1;
                    }

                    if ((xc->num_blackouts) && (cur_blackout != xc->num_blackouts)) {
                        if (time_table[i] == xc->blackout_times[cur_blackout]) {
                            wx_len =
                                snprintf(wx_buf,
                                         32,
                                         "$dump%s $end\n",
                                         (xc->blackout_activity[cur_blackout++]) ? "on" : "off");
                            fstWritex(xc, wx_buf, wx_len);
                        }
                    }
                    previous_time = time_table[i];
                }
            }

            while (tc_head[i]) {
                idx = tc_head[i] - 1;
                vli = fstGetVarint32(mem_for_traversal + headptr[idx], &skiplen);

                if (xc->signal_lens[idx] <= 1) {
                    if (xc->signal_lens[idx] == 1) {
                        unsigned char val;
                        if (!(vli & 1)) {
                            /* tdelta = vli >> 2; */ /* scan-build */
                            val = ((vli >> 1) & 1) | '0';
                        } else {
                            /* tdelta = vli >> 4; */ /* scan-build */
                            val = FST_RCV_STR[((vli >> 1) & 7)];
                        }

                        if (value_change_callback) {
                            xc->temp_signal_value_buf[0] = val;
                            xc->temp_signal_value_buf[1] = 0;
                            value_change_callback(user_callback_data_pointer,
                                                  time_table[i],
                                                  idx + 1,
                                                  xc->temp_signal_value_buf);
                        } else {
                            if (fv) {
                                char vcd_id[16];
                                int vcdid_len = fstVcdIDForFwrite(vcd_id + 1, idx + 1);

                                vcd_id[0] = val;
                                vcd_id[vcdid_len + 1] = '\n';
                                fstWritex(xc, vcd_id, vcdid_len + 2);
                            }
                        }
                        headptr[idx] += skiplen;
                        length_remaining[idx] -= skiplen;

                        tc_head[i] = scatterptr[idx];
                        scatterptr[idx] = 0;

                        if (length_remaining[idx]) {
                            int shamt;
                            vli = fstGetVarint32NoSkip(mem_for_traversal + headptr[idx]);
                            shamt = 2 << (vli & 1);
                            tdelta = vli >> shamt;

                            if ((tdelta + i) >= tc_head_items) {
                                chk_report_abort("TALOS-2023-1791");
                            }

                            scatterptr[idx] = tc_head[i + tdelta];
                            tc_head[i + tdelta] = idx + 1;
                        }
                    } else {
                        unsigned char *vdata;
                        uint32_t len;

                        vli = fstGetVarint32(mem_for_traversal + headptr[idx], &skiplen);
                        len = fstGetVarint32(mem_for_traversal + headptr[idx] + skiplen, &skiplen2);
                        /* tdelta = vli >> 1; */ /* scan-build */
                        skiplen += skiplen2;
                        vdata = mem_for_traversal + headptr[idx] + skiplen;

                        if (!(vli & 1)) {
                            if (value_change_callback_varlen) {
                                value_change_callback_varlen(user_callback_data_pointer,
                                                             time_table[i],
                                                             idx + 1,
                                                             vdata,
                                                             len);
                            } else {
                                if (fv) {
                                    char vcd_id[16];
                                    int vcdid_len;

                                    vcd_id[0] = 's';
                                    fstWritex(xc, vcd_id, 1);

                                    vcdid_len = fstVcdIDForFwrite(vcd_id + 1, idx + 1);
                                    {
                                        if (sizeof(size_t) < sizeof(uint64_t)) {
                                            /* TALOS-2023-1790 for 32b overflow */
                                            uint64_t chk_64 = len * 4 + 1;
                                            size_t chk_32 = len * 4 + 1;
                                            if (chk_64 != chk_32)
                                                chk_report_abort("TALOS-2023-1790");
                                        }

                                        unsigned char *vesc = (unsigned char *)malloc(len * 4 + 1);
                                        int vlen = fstUtilityBinToEsc(vesc, vdata, len);
                                        fstWritex(xc, vesc, vlen);
                                        free(vesc);
                                    }

                                    vcd_id[0] = ' ';
                                    vcd_id[vcdid_len + 1] = '\n';
                                    fstWritex(xc, vcd_id, vcdid_len + 2);
                                }
                            }
                        }

                        skiplen += len;
                        headptr[idx] += skiplen;
                        length_remaining[idx] -= skiplen;

                        tc_head[i] = scatterptr[idx];
                        scatterptr[idx] = 0;

                        if (length_remaining[idx]) {
                            vli = fstGetVarint32NoSkip(mem_for_traversal + headptr[idx]);
                            tdelta = vli >> 1;

                            if ((tdelta + i) >= tc_head_items) {
                                chk_report_abort("TALOS-2023-1791");
                            }

                            scatterptr[idx] = tc_head[i + tdelta];
                            tc_head[i + tdelta] = idx + 1;
                        }
                    }
                } else {
                    uint32_t len = xc->signal_lens[idx];
                    unsigned char *vdata;

                    vli = fstGetVarint32(mem_for_traversal + headptr[idx], &skiplen);
                    /* tdelta = vli >> 1; */ /* scan-build */
                    vdata = mem_for_traversal + headptr[idx] + skiplen;

                    if (xc->signal_typs[idx] != FST_VT_VCD_REAL) {
                        if (len > xc->longest_signal_value_len) {
                            chk_report_abort("TALOS-2023-1797");
                        }

                        if (!(vli & 1)) {
                            int byte = 0;
                            int bit;
                            unsigned int j;

                            for (j = 0; j < len; j++) {
                                unsigned char ch;
                                byte = j / 8;
                                bit = 7 - (j & 7);
                                ch = ((vdata[byte] >> bit) & 1) | '0';
                                xc->temp_signal_value_buf[j] = ch;
                            }
                            xc->temp_signal_value_buf[j] = 0;

                            if (value_change_callback) {
                                value_change_callback(user_callback_data_pointer,
                                                      time_table[i],
                                                      idx + 1,
                                                      xc->temp_signal_value_buf);
                            } else {
                                if (fv) {
                                    unsigned char ch_bp =
                                        (xc->signal_typs[idx] != FST_VT_VCD_PORT) ? 'b' : 'p';

                                    fstWritex(xc, &ch_bp, 1);
                                    fstWritex(xc, xc->temp_signal_value_buf, len);
                                }
                            }

                            len = byte + 1;
                        } else {
                            if (value_change_callback) {
                                memcpy(xc->temp_signal_value_buf, vdata, len);
                                xc->temp_signal_value_buf[len] = 0;
                                value_change_callback(user_callback_data_pointer,
                                                      time_table[i],
                                                      idx + 1,
                                                      xc->temp_signal_value_buf);
                            } else {
                                if (fv) {
                                    unsigned char ch_bp =
                                        (xc->signal_typs[idx] != FST_VT_VCD_PORT) ? 'b' : 'p';
                                    uint64_t mem_required_for_traversal_chk =
                                        vdata - mem_for_traversal + len;

                                    fstWritex(xc, &ch_bp, 1);
                                    if (mem_required_for_traversal_chk >
                                        mem_required_for_traversal) {
                                        chk_report_abort("TALOS-2023-1793");
                                    }
                                    fstWritex(xc, vdata, len);
                                }
                            }
                        }
                    } else {
                        double d;
                        unsigned char *clone_d /*= (unsigned char *)&d */; /* scan-build */
                        unsigned char buf[8];
                        unsigned char *srcdata;

                        if (!(vli & 1)) /* very rare case, but possible */
                        {
                            int bit;
                            int j;

                            for (j = 0; j < 8; j++) {
                                unsigned char ch;
                                bit = 7 - (j & 7);
                                ch = ((vdata[0] >> bit) & 1) | '0';
                                buf[j] = ch;
                            }

                            len = 1;
                            srcdata = buf;
                        } else {
                            srcdata = vdata;
                        }

                        if (value_change_callback) {
                            if (xc->native_doubles_for_cb) {
                                if (xc->double_endian_match) {
                                    clone_d = srcdata;
                                } else {
                                    int j;

                                    clone_d = (unsigned char *)&d;
                                    for (j = 0; j < 8; j++) {
                                        clone_d[j] = srcdata[7 - j];
                                    }
                                }
                                value_change_callback(user_callback_data_pointer,
                                                      time_table[i],
                                                      idx + 1,
                                                      clone_d);
                            } else {
                                clone_d = (unsigned char *)&d;
                                if (xc->double_endian_match) {
                                    memcpy(clone_d, srcdata, 8);
                                } else {
                                    int j;

                                    for (j = 0; j < 8; j++) {
                                        clone_d[j] = srcdata[7 - j];
                                    }
                                }
                                snprintf((char *)xc->temp_signal_value_buf,
                                         xc->longest_signal_value_len + 1,
                                         "%.16g",
                                         d);
                                value_change_callback(user_callback_data_pointer,
                                                      time_table[i],
                                                      idx + 1,
                                                      xc->temp_signal_value_buf);
                            }
                        } else {
                            if (fv) {
                                char wx_buf[32];
                                int wx_len;

                                clone_d = (unsigned char *)&d;
                                if (xc->double_endian_match) {
                                    memcpy(clone_d, srcdata, 8);
                                } else {
                                    int j;

                                    for (j = 0; j < 8; j++) {
                                        clone_d[j] = srcdata[7 - j];
                                    }
                                }

                                wx_len = snprintf(wx_buf, 32, "r%.16g", d);
                                if (wx_len > 32 || wx_len < 0) wx_len = 32;
                                fstWritex(xc, wx_buf, wx_len);
                            }
                        }
                    }

                    if (fv) {
                        char vcd_id[16];
                        int vcdid_len = fstVcdIDForFwrite(vcd_id + 1, idx + 1);
                        vcd_id[0] = ' ';
                        vcd_id[vcdid_len + 1] = '\n';
                        fstWritex(xc, vcd_id, vcdid_len + 2);
                    }

                    skiplen += len;
                    headptr[idx] += skiplen;
                    length_remaining[idx] -= skiplen;

                    tc_head[i] = scatterptr[idx];
                    scatterptr[idx] = 0;

                    if (length_remaining[idx]) {
                        vli = fstGetVarint32NoSkip(mem_for_traversal + headptr[idx]);
                        tdelta = vli >> 1;

                        if ((tdelta + i) >= tc_head_items) {
                            chk_report_abort("TALOS-2023-1791");
                        }

                        scatterptr[idx] = tc_head[i + tdelta];
                        tc_head[i + tdelta] = idx + 1;
                    }
                }
            }
        }

    block_err:
        free(tc_head);
        free(chain_cmem);
        free(mem_for_traversal);
        mem_for_traversal = NULL;

        secnum++;
        if (secnum == xc->vc_section_count)
            break; /* in case file is growing, keep with original block count */
        blkpos += seclen;
    }

    if (mem_for_traversal)
        free(mem_for_traversal); /* scan-build */
    free(length_remaining);
    free(headptr);
    free(scatterptr);

    if (chain_table)
        free(chain_table);
    if (chain_table_lengths)
        free(chain_table_lengths);

    free(time_table);

#ifndef FST_WRITEX_DISABLE
    if (fv) {
        fstWritex(xc, NULL, 0);
    }
#endif

    return (1);
}

/* rvat functions */

static char *fstExtractRvatDataFromFrame(fstReaderContext *xc, fstHandle facidx, char *buf)
{
    if (facidx >= xc->rvat_frame_maxhandle) {
        return (NULL);
    }

    if (xc->signal_lens[facidx] == 1) {
        buf[0] = (char)xc->rvat_frame_data[xc->rvat_sig_offs[facidx]];
        buf[1] = 0;
    } else {
        if (xc->signal_typs[facidx] != FST_VT_VCD_REAL) {
            memcpy(buf, xc->rvat_frame_data + xc->rvat_sig_offs[facidx], xc->signal_lens[facidx]);
            buf[xc->signal_lens[facidx]] = 0;
        } else {
            double d;
            unsigned char *clone_d = (unsigned char *)&d;
            unsigned char *srcdata = xc->rvat_frame_data + xc->rvat_sig_offs[facidx];

            if (xc->double_endian_match) {
                memcpy(clone_d, srcdata, 8);
            } else {
                int j;

                for (j = 0; j < 8; j++) {
                    clone_d[j] = srcdata[7 - j];
                }
            }

            snprintf((char *)buf, 32, "%.16g", d); /* this will write 18 bytes */
        }
    }

    return (buf);
}

char *fstReaderGetValueFromHandleAtTime(fstReaderContext *xc,
                                        uint64_t tim,
                                        fstHandle facidx,
                                        char *buf)
{
    fst_off_t blkpos = 0, prev_blkpos;
    uint64_t beg_tim, end_tim, beg_tim2, end_tim2;
    int sectype;
#ifdef FST_DEBUG
    unsigned int secnum = 0;
#endif
    uint64_t seclen;
    uint64_t tsec_uclen = 0, tsec_clen = 0;
    uint64_t tsec_nitems;
    uint64_t frame_uclen, frame_clen;
#ifdef FST_DEBUG
    uint64_t mem_required_for_traversal;
#endif
    fst_off_t indx_pntr, indx_pos;
    long chain_clen;
    unsigned char *chain_cmem;
    unsigned char *pnt;
    fstHandle idx, pidx = 0, i;
    uint64_t pval;

    if ((!xc) || (!facidx) || (facidx > xc->maxhandle) || (!buf) ||
        (!xc->signal_lens[facidx - 1])) {
        return (NULL);
    }

    if (!xc->rvat_sig_offs) {
        uint32_t cur_offs = 0;

        xc->rvat_sig_offs = (uint32_t *)calloc(xc->maxhandle, sizeof(uint32_t));
        for (i = 0; i < xc->maxhandle; i++) {
            xc->rvat_sig_offs[i] = cur_offs;
            cur_offs += xc->signal_lens[i];
        }
    }

    if (xc->rvat_data_valid) {
        if ((xc->rvat_beg_tim <= tim) && (tim <= xc->rvat_end_tim)) {
            goto process_value;
        }

        fstReaderDeallocateRvatData(xc);
    }

    xc->rvat_chain_pos_valid = 0;

    for (;;) {
        fstReaderFseeko(xc, xc->f, (prev_blkpos = blkpos), SEEK_SET);

        sectype = fgetc(xc->f);
        seclen = fstReaderUint64(xc->f);

        if ((sectype == EOF) || (sectype == FST_BL_SKIP) || (!seclen)) {
            return (NULL); /* if this loop exits on break, it's successful */
        }

        blkpos++;
        if ((sectype != FST_BL_VCDATA) && (sectype != FST_BL_VCDATA_DYN_ALIAS) &&
            (sectype != FST_BL_VCDATA_DYN_ALIAS2)) {
            blkpos += seclen;
            continue;
        }

        beg_tim = fstReaderUint64(xc->f);
        end_tim = fstReaderUint64(xc->f);

        if ((beg_tim <= tim) && (tim <= end_tim)) {
            if ((tim == end_tim) && (tim != xc->end_time)) {
                fst_off_t cached_pos = ftello(xc->f);
                fstReaderFseeko(xc, xc->f, blkpos, SEEK_SET);

                sectype = fgetc(xc->f);
                seclen = fstReaderUint64(xc->f);

                beg_tim2 = fstReaderUint64(xc->f);
                end_tim2 = fstReaderUint64(xc->f);

                if (((sectype != FST_BL_VCDATA) && (sectype != FST_BL_VCDATA_DYN_ALIAS) &&
                     (sectype != FST_BL_VCDATA_DYN_ALIAS2)) ||
                    (!seclen) || (beg_tim2 != tim)) {
                    blkpos = prev_blkpos;
                    break;
                }
                beg_tim = beg_tim2;
                end_tim = end_tim2;
                fstReaderFseeko(xc, xc->f, cached_pos, SEEK_SET);
            }
            break;
        }

        blkpos += seclen;
#ifdef FST_DEBUG
        secnum++;
#endif
    }

    xc->rvat_beg_tim = beg_tim;
    xc->rvat_end_tim = end_tim;

#ifdef FST_DEBUG
    mem_required_for_traversal =
#endif
        fstReaderUint64(xc->f);

#ifdef FST_DEBUG
    fprintf(stderr,
            FST_APIMESS "rvat sec: %u seclen: %d begtim: %d endtim: %d\n",
            secnum,
            (int)seclen,
            (int)beg_tim,
            (int)end_tim);
    fprintf(stderr,
            FST_APIMESS "mem_required_for_traversal: %d\n",
            (int)mem_required_for_traversal);
#endif

    /* process time block */
    {
        unsigned char *ucdata;
        unsigned char *cdata;
        unsigned long destlen /* = tsec_uclen */; /* scan-build */
        unsigned long sourcelen /* = tsec_clen */; /* scan-build */
        int rc;
        unsigned char *tpnt;
        uint64_t tpval;
        unsigned int ti;

        fstReaderFseeko(xc, xc->f, blkpos + seclen - 24, SEEK_SET);
        tsec_uclen = fstReaderUint64(xc->f);
        tsec_clen = fstReaderUint64(xc->f);
        tsec_nitems = fstReaderUint64(xc->f);
#ifdef FST_DEBUG
        fprintf(stderr,
                FST_APIMESS "time section unc: %d, com: %d (%d items)\n",
                (int)tsec_uclen,
                (int)tsec_clen,
                (int)tsec_nitems);
#endif
        ucdata = (unsigned char *)malloc(tsec_uclen);
        destlen = tsec_uclen;
        sourcelen = tsec_clen;

        fstReaderFseeko(xc, xc->f, -24 - ((fst_off_t)tsec_clen), SEEK_CUR);
        if (tsec_uclen != tsec_clen) {
            cdata = (unsigned char *)malloc(tsec_clen);
            fstFread(cdata, tsec_clen, 1, xc->f);

            rc = uncompress(ucdata, &destlen, cdata, sourcelen);

            if (rc != Z_OK) {
                fprintf(stderr,
                        FST_APIMESS
                        "fstReaderGetValueFromHandleAtTime(), tsec uncompress rc = %d, exiting.\n",
                        rc);
                exit(255);
            }

            free(cdata);
        } else {
            fstFread(ucdata, tsec_uclen, 1, xc->f);
        }

        xc->rvat_time_table = (uint64_t *)calloc(tsec_nitems, sizeof(uint64_t));
        tpnt = ucdata;
        tpval = 0;
        for (ti = 0; ti < tsec_nitems; ti++) {
            int skiplen;
            uint64_t val = fstGetVarint64(tpnt, &skiplen);
            tpval = xc->rvat_time_table[ti] = tpval + val;
            tpnt += skiplen;
        }

        free(ucdata);
    }

    fstReaderFseeko(xc, xc->f, blkpos + 32, SEEK_SET);

    frame_uclen = fstReaderVarint64(xc->f);
    frame_clen = fstReaderVarint64(xc->f);
    xc->rvat_frame_maxhandle = fstReaderVarint64(xc->f);
    xc->rvat_frame_data = (unsigned char *)malloc(frame_uclen);

    if (frame_uclen == frame_clen) {
        fstFread(xc->rvat_frame_data, frame_uclen, 1, xc->f);
    } else {
        unsigned char *mc = (unsigned char *)malloc(frame_clen);
        int rc;

        unsigned long destlen = frame_uclen;
        unsigned long sourcelen = frame_clen;

        fstFread(mc, sourcelen, 1, xc->f);
        rc = uncompress(xc->rvat_frame_data, &destlen, mc, sourcelen);
        if (rc != Z_OK) {
            fprintf(stderr,
                    FST_APIMESS
                    "fstReaderGetValueFromHandleAtTime(), frame decompress rc: %d, exiting.\n",
                    rc);
            exit(255);
        }
        free(mc);
    }

    xc->rvat_vc_maxhandle = fstReaderVarint64(xc->f);
    xc->rvat_vc_start = ftello(xc->f); /* points to '!' character */
    xc->rvat_packtype = fgetc(xc->f);

#ifdef FST_DEBUG
    fprintf(stderr,
            FST_APIMESS "frame_uclen: %d, frame_clen: %d, frame_maxhandle: %d\n",
            (int)frame_uclen,
            (int)frame_clen,
            (int)xc->rvat_frame_maxhandle);
    fprintf(stderr, FST_APIMESS "vc_maxhandle: %d\n", (int)xc->rvat_vc_maxhandle);
#endif

    indx_pntr = blkpos + seclen - 24 - tsec_clen - 8;
    fstReaderFseeko(xc, xc->f, indx_pntr, SEEK_SET);
    chain_clen = fstReaderUint64(xc->f);
    indx_pos = indx_pntr - chain_clen;
#ifdef FST_DEBUG
    fprintf(stderr, FST_APIMESS "indx_pos: %d (%d bytes)\n", (int)indx_pos, (int)chain_clen);
#endif
    chain_cmem = (unsigned char *)malloc(chain_clen);
    fstReaderFseeko(xc, xc->f, indx_pos, SEEK_SET);
    fstFread(chain_cmem, chain_clen, 1, xc->f);

    xc->rvat_chain_table = (fst_off_t *)calloc((xc->rvat_vc_maxhandle + 1), sizeof(fst_off_t));
    xc->rvat_chain_table_lengths =
        (uint32_t *)calloc((xc->rvat_vc_maxhandle + 1), sizeof(uint32_t));

    pnt = chain_cmem;
    idx = 0;
    pval = 0;

    if (sectype == FST_BL_VCDATA_DYN_ALIAS2) {
        uint32_t prev_alias = 0;

        do {
            int skiplen;

            if (*pnt & 0x01) {
                int64_t shval = fstGetSVarint64(pnt, &skiplen) >> 1;
                if (shval > 0) {
                    pval = xc->rvat_chain_table[idx] = pval + shval;
                    if (idx) {
                        xc->rvat_chain_table_lengths[pidx] = pval - xc->rvat_chain_table[pidx];
                    }
                    pidx = idx++;
                } else if (shval < 0) {
                    xc->rvat_chain_table[idx] =
                        0; /* need to explicitly zero as calloc above might not run */
                    xc->rvat_chain_table_lengths[idx] = prev_alias =
                        shval; /* because during this loop iter would give stale data! */
                    idx++;
                } else {
                    xc->rvat_chain_table[idx] =
                        0; /* need to explicitly zero as calloc above might not run */
                    xc->rvat_chain_table_lengths[idx] =
                        prev_alias; /* because during this loop iter would give stale data! */
                    idx++;
                }
            } else {
                uint64_t val = fstGetVarint32(pnt, &skiplen);

                fstHandle loopcnt = val >> 1;
                for (i = 0; i < loopcnt; i++) {
                    xc->rvat_chain_table[idx++] = 0;
                }
            }

            pnt += skiplen;
        } while (pnt != (chain_cmem + chain_clen));
    } else {
        do {
            int skiplen;
            uint64_t val = fstGetVarint32(pnt, &skiplen);

            if (!val) {
                pnt += skiplen;
                val = fstGetVarint32(pnt, &skiplen);
                xc->rvat_chain_table[idx] = 0;
                xc->rvat_chain_table_lengths[idx] = -val;
                idx++;
            } else if (val & 1) {
                pval = xc->rvat_chain_table[idx] = pval + (val >> 1);
                if (idx) {
                    xc->rvat_chain_table_lengths[pidx] = pval - xc->rvat_chain_table[pidx];
                }
                pidx = idx++;
            } else {
                fstHandle loopcnt = val >> 1;
                for (i = 0; i < loopcnt; i++) {
                    xc->rvat_chain_table[idx++] = 0;
                }
            }

            pnt += skiplen;
        } while (pnt != (chain_cmem + chain_clen));
    }

    free(chain_cmem);
    xc->rvat_chain_table[idx] = indx_pos - xc->rvat_vc_start;
    xc->rvat_chain_table_lengths[pidx] = xc->rvat_chain_table[idx] - xc->rvat_chain_table[pidx];

    for (i = 0; i < idx; i++) {
        int32_t v32 = xc->rvat_chain_table_lengths[i];
        if ((v32 < 0) && (!xc->rvat_chain_table[i])) {
            v32 = -v32;
            v32--;
            if (((uint32_t)v32) < i) /* sanity check */
            {
                xc->rvat_chain_table[i] = xc->rvat_chain_table[v32];
                xc->rvat_chain_table_lengths[i] = xc->rvat_chain_table_lengths[v32];
            }
        }
    }

#ifdef FST_DEBUG
    fprintf(stderr, FST_APIMESS "decompressed chain idx len: %" PRIu32 "\n", idx);
#endif

    xc->rvat_data_valid = 1;

/* all data at this point is loaded or resident in fst cache, process and return appropriate value
 */
process_value:
    if (facidx > xc->rvat_vc_maxhandle) {
        return (NULL);
    }

    facidx--; /* scale down for array which starts at zero */

    if (((tim == xc->rvat_beg_tim) && (!xc->rvat_chain_table[facidx])) ||
        (!xc->rvat_chain_table[facidx])) {
        return (fstExtractRvatDataFromFrame(xc, facidx, buf));
    }

    if (facidx != xc->rvat_chain_facidx) {
        if (xc->rvat_chain_mem) {
            free(xc->rvat_chain_mem);
            xc->rvat_chain_mem = NULL;

            xc->rvat_chain_pos_valid = 0;
        }
    }

    if (!xc->rvat_chain_mem) {
        uint32_t skiplen;
        fstReaderFseeko(xc, xc->f, xc->rvat_vc_start + xc->rvat_chain_table[facidx], SEEK_SET);
        xc->rvat_chain_len = fstReaderVarint32WithSkip(xc->f, &skiplen);
        if (xc->rvat_chain_len) {
            unsigned char *mu = (unsigned char *)malloc(xc->rvat_chain_len);
            unsigned char *mc = (unsigned char *)malloc(xc->rvat_chain_table_lengths[facidx]);
            unsigned long destlen = xc->rvat_chain_len;
            unsigned long sourcelen = xc->rvat_chain_table_lengths[facidx];
            int rc = Z_OK;

            fstFread(mc, xc->rvat_chain_table_lengths[facidx], 1, xc->f);

            switch (xc->rvat_packtype) {
                case '4':
                    rc = (destlen == (unsigned long)LZ4_decompress_safe_partial((char *)mc,
                                                                                (char *)mu,
                                                                                sourcelen,
                                                                                destlen,
                                                                                destlen))
                             ? Z_OK
                             : Z_DATA_ERROR;
                    break;
                case 'F':
                    fastlz_decompress(mc, sourcelen, mu, destlen); /* rc appears unreliable */
                    break;
                default:
                    rc = uncompress(mu, &destlen, mc, sourcelen);
                    break;
            }

            free(mc);

            if (rc != Z_OK) {
                fprintf(stderr,
                        FST_APIMESS "fstReaderGetValueFromHandleAtTime(), rvat decompress clen: %d "
                                    "(rc=%d), exiting.\n",
                        (int)xc->rvat_chain_len,
                        rc);
                exit(255);
            }

            /* data to process is for(j=0;j<destlen;j++) in mu[j] */
            xc->rvat_chain_mem = mu;
        } else {
            int destlen = xc->rvat_chain_table_lengths[facidx] - skiplen;
            unsigned char *mu = (unsigned char *)malloc(xc->rvat_chain_len = destlen);
            fstFread(mu, destlen, 1, xc->f);
            /* data to process is for(j=0;j<destlen;j++) in mu[j] */
            xc->rvat_chain_mem = mu;
        }

        xc->rvat_chain_facidx = facidx;
    }

    /* process value chain here */

    {
        uint32_t tidx = 0, ptidx = 0;
        uint32_t tdelta;
        int skiplen;
        unsigned int iprev = xc->rvat_chain_len;
        uint32_t pvli = 0;
        int pskip = 0;

        if ((xc->rvat_chain_pos_valid) && (tim >= xc->rvat_chain_pos_time)) {
            i = xc->rvat_chain_pos_idx;
            tidx = xc->rvat_chain_pos_tidx;
        } else {
            i = 0;
            tidx = 0;
            xc->rvat_chain_pos_time = xc->rvat_beg_tim;
        }

        if (xc->signal_lens[facidx] == 1) {
            while (i < xc->rvat_chain_len) {
                uint32_t vli = fstGetVarint32(xc->rvat_chain_mem + i, &skiplen);
                uint32_t shcnt = 2 << (vli & 1);
                tdelta = vli >> shcnt;

                if (xc->rvat_time_table[tidx + tdelta] <= tim) {
                    iprev = i;
                    pvli = vli;
                    ptidx = tidx;
                    /* pskip = skiplen; */ /* scan-build */

                    tidx += tdelta;
                    i += skiplen;
                } else {
                    break;
                }
            }
            if (iprev != xc->rvat_chain_len) {
                xc->rvat_chain_pos_tidx = ptidx;
                xc->rvat_chain_pos_idx = iprev;
                xc->rvat_chain_pos_time = tim;
                xc->rvat_chain_pos_valid = 1;

                if (!(pvli & 1)) {
                    buf[0] = ((pvli >> 1) & 1) | '0';
                } else {
                    buf[0] = FST_RCV_STR[((pvli >> 1) & 7)];
                }
                buf[1] = 0;
                return (buf);
            } else {
                return (fstExtractRvatDataFromFrame(xc, facidx, buf));
            }
        } else {
            while (i < xc->rvat_chain_len) {
                uint32_t vli = fstGetVarint32(xc->rvat_chain_mem + i, &skiplen);
                tdelta = vli >> 1;

                if (xc->rvat_time_table[tidx + tdelta] <= tim) {
                    iprev = i;
                    pvli = vli;
                    ptidx = tidx;
                    pskip = skiplen;

                    tidx += tdelta;
                    i += skiplen;

                    if (!(pvli & 1)) {
                        i += ((xc->signal_lens[facidx] + 7) / 8);
                    } else {
                        i += xc->signal_lens[facidx];
                    }
                } else {
                    break;
                }
            }

            if (iprev != xc->rvat_chain_len) {
                unsigned char *vdata = xc->rvat_chain_mem + iprev + pskip;

                xc->rvat_chain_pos_tidx = ptidx;
                xc->rvat_chain_pos_idx = iprev;
                xc->rvat_chain_pos_time = tim;
                xc->rvat_chain_pos_valid = 1;

                if (xc->signal_typs[facidx] != FST_VT_VCD_REAL) {
                    if (!(pvli & 1)) {
                        int byte = 0;
                        int bit;
                        unsigned int j;

                        for (j = 0; j < xc->signal_lens[facidx]; j++) {
                            unsigned char ch;
                            byte = j / 8;
                            bit = 7 - (j & 7);
                            ch = ((vdata[byte] >> bit) & 1) | '0';
                            buf[j] = ch;
                        }
                        buf[j] = 0;

                        return (buf);
                    } else {
                        memcpy(buf, vdata, xc->signal_lens[facidx]);
                        buf[xc->signal_lens[facidx]] = 0;
                        return (buf);
                    }
                } else {
                    double d;
                    unsigned char *clone_d = (unsigned char *)&d;
                    unsigned char bufd[8];
                    unsigned char *srcdata;

                    if (!(pvli & 1)) /* very rare case, but possible */
                    {
                        int bit;
                        int j;

                        for (j = 0; j < 8; j++) {
                            unsigned char ch;
                            bit = 7 - (j & 7);
                            ch = ((vdata[0] >> bit) & 1) | '0';
                            bufd[j] = ch;
                        }

                        srcdata = bufd;
                    } else {
                        srcdata = vdata;
                    }

                    if (xc->double_endian_match) {
                        memcpy(clone_d, srcdata, 8);
                    } else {
                        int j;

                        for (j = 0; j < 8; j++) {
                            clone_d[j] = srcdata[7 - j];
                        }
                    }

                    snprintf(buf, 32, "r%.16g", d); /* this will write 19 bytes */
                    return (buf);
                }
            } else {
                return (fstExtractRvatDataFromFrame(xc, facidx, buf));
            }
        }
    }

    /* return(NULL); */
}

/***********************/
/***                 ***/
/***  jenkins hash   ***/
/***                 ***/
/***********************/

/*
--------------------------------------------------------------------
mix -- mix 3 32-bit values reversibly.
For every delta with one or two bits set, and the deltas of all three
  high bits or all three low bits, whether the original value of a,b,c
  is almost all zero or is uniformly distributed,
* If mix() is run forward or backward, at least 32 bits in a,b,c
  have at least 1/4 probability of changing.
* If mix() is run forward, every bit of c will change between 1/3 and
  2/3 of the time.  (Well, 22/100 and 78/100 for some 2-bit deltas.)
mix() was built out of 36 single-cycle latency instructions in a
  structure that could supported 2x parallelism, like so:
      a -= b;
      a -= c; x = (c>>13);
      b -= c; a ^= x;
      b -= a; x = (a<<8);
      c -= a; b ^= x;
      c -= b; x = (b>>13);
      ...
  Unfortunately, superscalar Pentiums and Sparcs can't take advantage
  of that parallelism.  They've also turned some of those single-cycle
  latency instructions into multi-cycle latency instructions.  Still,
  this is the fastest good hash I could find.  There were about 2^^68
  to choose from.  I only looked at a billion or so.
--------------------------------------------------------------------
*/
#define mix(a, b, c) \
    { \
        a -= b; \
        a -= c; \
        a ^= (c >> 13); \
        b -= c; \
        b -= a; \
        b ^= (a << 8); \
        c -= a; \
        c -= b; \
        c ^= (b >> 13); \
        a -= b; \
        a -= c; \
        a ^= (c >> 12); \
        b -= c; \
        b -= a; \
        b ^= (a << 16); \
        c -= a; \
        c -= b; \
        c ^= (b >> 5); \
        a -= b; \
        a -= c; \
        a ^= (c >> 3); \
        b -= c; \
        b -= a; \
        b ^= (a << 10); \
        c -= a; \
        c -= b; \
        c ^= (b >> 15); \
    }

/*
--------------------------------------------------------------------
j_hash() -- hash a variable-length key into a 32-bit value
  k       : the key (the unaligned variable-length array of bytes)
  len     : the length of the key, counting by bytes
  initval : can be any 4-byte value
Returns a 32-bit value.  Every bit of the key affects every bit of
the return value.  Every 1-bit and 2-bit delta achieves avalanche.
About 6*len+35 instructions.

The best hash table sizes are powers of 2.  There is no need to do
mod a prime (mod is sooo slow!).  If you need less than 32 bits,
use a bitmask.  For example, if you need only 10 bits, do
  h = (h & hashmask(10));
In which case, the hash table should have hashsize(10) elements.

If you are hashing n strings (uint8_t **)k, do it like this:
  for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h);

By Bob Jenkins, 1996.  bob_jenkins@burtleburtle.net.  You may use this
code any way you wish, private, educational, or commercial.  It's free.

See http://burtleburtle.net/bob/hash/evahash.html
Use for hash table lookup, or anything where one collision in 2^^32 is
acceptable.  Do NOT use for cryptographic purposes.
--------------------------------------------------------------------
*/

static uint32_t j_hash(const uint8_t *k, uint32_t length, uint32_t initval)
{
    uint32_t a, b, c, len;

    /* Set up the internal state */
    len = length;
    a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
    c = initval; /* the previous hash value */

    /*---------------------------------------- handle most of the key */
    while (len >= 12) {
        a += (k[0] + ((uint32_t)k[1] << 8) + ((uint32_t)k[2] << 16) + ((uint32_t)k[3] << 24));
        b += (k[4] + ((uint32_t)k[5] << 8) + ((uint32_t)k[6] << 16) + ((uint32_t)k[7] << 24));
        c += (k[8] + ((uint32_t)k[9] << 8) + ((uint32_t)k[10] << 16) + ((uint32_t)k[11] << 24));
        mix(a, b, c);
        k += 12;
        len -= 12;
    }

    /*------------------------------------- handle the last 11 bytes */
    c += length;
    switch (len) /* all the case statements fall through */
    {
        case 11:
            c += ((uint32_t)k[10] << 24); /* fallthrough */
        case 10:
            c += ((uint32_t)k[9] << 16); /* fallthrough */
        case 9:
            c += ((uint32_t)k[8] << 8); /* fallthrough */
            /* the first byte of c is reserved for the length */
        case 8:
            b += ((uint32_t)k[7] << 24); /* fallthrough */
        case 7:
            b += ((uint32_t)k[6] << 16); /* fallthrough */
        case 6:
            b += ((uint32_t)k[5] << 8); /* fallthrough */
        case 5:
            b += k[4]; /* fallthrough */
        case 4:
            a += ((uint32_t)k[3] << 24); /* fallthrough */
        case 3:
            a += ((uint32_t)k[2] << 16); /* fallthrough */
        case 2:
            a += ((uint32_t)k[1] << 8); /* fallthrough */
        case 1:
            a += k[0];
            /* case 0: nothing left to add */
    }
    mix(a, b, c);
    /*-------------------------------------------- report the result */
    return (c);
}

/********************************************************************/

/***************************/
/***                     ***/
/***  judy HS emulation  ***/
/***                     ***/
/***************************/

struct collchain_t
{
    struct collchain_t *next;
    void *payload;
    uint32_t fullhash, length;
    unsigned char mem[1];
};

void **JenkinsIns(void *base_i, const unsigned char *mem, uint32_t length, uint32_t hashmask)
{
    struct collchain_t ***base = (struct collchain_t ***)base_i;
    uint32_t hf, h;
    struct collchain_t **ar;
    struct collchain_t *chain, *pchain;

    if (!*base) {
        *base = (struct collchain_t **)calloc(1, (hashmask + 1) * sizeof(void *));
    }
    ar = *base;

    h = (hf = j_hash(mem, length, length)) & hashmask;
    pchain = chain = ar[h];
    while (chain) {
        if ((chain->fullhash == hf) && (chain->length == length) &&
            !memcmp(chain->mem, mem, length)) {
            if (pchain != chain) /* move hit to front */
            {
                pchain->next = chain->next;
                chain->next = ar[h];
                ar[h] = chain;
            }
            return (&(chain->payload));
        }

        pchain = chain;
        chain = chain->next;
    }

    chain = (struct collchain_t *)calloc(1, sizeof(struct collchain_t) + length - 1);
    memcpy(chain->mem, mem, length);
    chain->fullhash = hf;
    chain->length = length;
    chain->next = ar[h];
    ar[h] = chain;
    return (&(chain->payload));
}

void JenkinsFree(void *base_i, uint32_t hashmask)
{
    struct collchain_t ***base = (struct collchain_t ***)base_i;
    uint32_t h;
    struct collchain_t **ar;
    struct collchain_t *chain, *chain_next;

    if (base && *base) {
        ar = *base;
        for (h = 0; h <= hashmask; h++) {
            chain = ar[h];
            while (chain) {
                chain_next = chain->next;
                free(chain);
                chain = chain_next;
            }
        }

        free(*base);
        *base = NULL;
    }
}

/**********************************************************************/

/************************/
/***                  ***/
/*** utility function ***/
/***                  ***/
/************************/

int fstUtilityBinToEscConvertedLen(const unsigned char *s, int len)
{
    const unsigned char *src = s;
    int dlen = 0;
    int i;

    for (i = 0; i < len; i++) {
        switch (src[i]) {
            case '\a': /* fallthrough */
            case '\b': /* fallthrough */
            case '\f': /* fallthrough */
            case '\n': /* fallthrough */
            case '\r': /* fallthrough */
            case '\t': /* fallthrough */
            case '\v': /* fallthrough */
            case '\'': /* fallthrough */
            case '\"': /* fallthrough */
            case '\\': /* fallthrough */
            case '\?':
                dlen += 2;
                break;
            default:
                if ((src[i] > ' ') && (src[i] <= '~')) /* no white spaces in output */
                {
                    dlen++;
                } else {
                    dlen += 4;
                }
                break;
        }
    }

    return (dlen);
}

int fstUtilityBinToEsc(unsigned char *d, const unsigned char *s, int len)
{
    const unsigned char *src = s;
    unsigned char *dst = d;
    unsigned char val;
    int i;

    for (i = 0; i < len; i++) {
        switch (src[i]) {
            case '\a':
                *(dst++) = '\\';
                *(dst++) = 'a';
                break;
            case '\b':
                *(dst++) = '\\';
                *(dst++) = 'b';
                break;
            case '\f':
                *(dst++) = '\\';
                *(dst++) = 'f';
                break;
            case '\n':
                *(dst++) = '\\';
                *(dst++) = 'n';
                break;
            case '\r':
                *(dst++) = '\\';
                *(dst++) = 'r';
                break;
            case '\t':
                *(dst++) = '\\';
                *(dst++) = 't';
                break;
            case '\v':
                *(dst++) = '\\';
                *(dst++) = 'v';
                break;
            case '\'':
                *(dst++) = '\\';
                *(dst++) = '\'';
                break;
            case '\"':
                *(dst++) = '\\';
                *(dst++) = '\"';
                break;
            case '\\':
                *(dst++) = '\\';
                *(dst++) = '\\';
                break;
            case '\?':
                *(dst++) = '\\';
                *(dst++) = '\?';
                break;
            default:
                if ((src[i] > ' ') && (src[i] <= '~')) /* no white spaces in output */
                {
                    *(dst++) = src[i];
                } else {
                    val = src[i];
                    *(dst++) = '\\';
                    *(dst++) = (val / 64) + '0';
                    val = val & 63;
                    *(dst++) = (val / 8) + '0';
                    val = val & 7;
                    *(dst++) = (val) + '0';
                }
                break;
        }
    }

    return (dst - d);
}

/*
 * this overwrites the original string if the destination pointer is NULL
 */
int fstUtilityEscToBin(unsigned char *d, unsigned char *s, int len)
{
    unsigned char *src = s;
    unsigned char *dst = (!d) ? s : (s = d);
    unsigned char val[3];
    int i;

    for (i = 0; i < len; i++) {
        if (src[i] != '\\') {
            *(dst++) = src[i];
        } else {
            switch (src[++i]) {
                case 'a':
                    *(dst++) = '\a';
                    break;
                case 'b':
                    *(dst++) = '\b';
                    break;
                case 'f':
                    *(dst++) = '\f';
                    break;
                case 'n':
                    *(dst++) = '\n';
                    break;
                case 'r':
                    *(dst++) = '\r';
                    break;
                case 't':
                    *(dst++) = '\t';
                    break;
                case 'v':
                    *(dst++) = '\v';
                    break;
                case '\'':
                    *(dst++) = '\'';
                    break;
                case '\"':
                    *(dst++) = '\"';
                    break;
                case '\\':
                    *(dst++) = '\\';
                    break;
                case '\?':
                    *(dst++) = '\?';
                    break;

                case 'x':
                    val[0] = toupper(src[++i]);
                    val[1] = toupper(src[++i]);
                    val[0] =
                        ((val[0] >= 'A') && (val[0] <= 'F')) ? (val[0] - 'A' + 10) : (val[0] - '0');
                    val[1] =
                        ((val[1] >= 'A') && (val[1] <= 'F')) ? (val[1] - 'A' + 10) : (val[1] - '0');
                    *(dst++) = val[0] * 16 + val[1];
                    break;

                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                    val[0] = src[i] - '0';
                    val[1] = src[++i] - '0';
                    val[2] = src[++i] - '0';
                    *(dst++) = val[0] * 64 + val[1] * 8 + val[2];
                    break;

                default:
                    *(dst++) = src[i];
                    break;
            }
        }
    }

    return (dst - s);
}

struct fstETab *fstUtilityExtractEnumTableFromString(const char *s)
{
    struct fstETab *et = NULL;
    int num_spaces = 0;
    int i;
    int newlen;

    if (s) {
        const char *csp = strchr(s, ' ');
        int cnt = atoi(csp + 1);

        for (;;) {
            csp = strchr(csp + 1, ' ');
            if (csp) {
                num_spaces++;
            } else {
                break;
            }
        }

        if (num_spaces == (2 * cnt)) {
            char *sp, *sp2;

            et = (struct fstETab *)calloc(1, sizeof(struct fstETab));
            et->elem_count = cnt;
            et->name = strdup(s);
            et->literal_arr = (char **)calloc(cnt, sizeof(char *));
            et->val_arr = (char **)calloc(cnt, sizeof(char *));

            sp = strchr(et->name, ' ');
            *sp = 0;

            sp = strchr(sp + 1, ' ');

            for (i = 0; i < cnt; i++) {
                sp2 = strchr(sp + 1, ' ');
                *(char *)sp2 = 0;
                et->literal_arr[i] = sp + 1;
                sp = sp2;

                newlen = fstUtilityEscToBin(NULL,
                                            (unsigned char *)et->literal_arr[i],
                                            strlen(et->literal_arr[i]));
                et->literal_arr[i][newlen] = 0;
            }

            for (i = 0; i < cnt; i++) {
                sp2 = strchr(sp + 1, ' ');
                if (sp2) {
                    *sp2 = 0;
                }
                et->val_arr[i] = sp + 1;
                sp = sp2;

                newlen = fstUtilityEscToBin(NULL,
                                            (unsigned char *)et->val_arr[i],
                                            strlen(et->val_arr[i]));
                et->val_arr[i][newlen] = 0;
            }
        }
    }

    return (et);
}

void fstUtilityFreeEnumTable(struct fstETab *etab)
{
    if (etab) {
        free(etab->literal_arr);
        free(etab->val_arr);
        free(etab->name);
        free(etab);
    }
}
