Skip to content

File syn_lz4.c

File List > src > syntropic > util > syn_lz4.c

Go to the documentation of this file

#include "syn_lz4.h"

#include <string.h>

#define LZ4_MIN_MATCH 4U  
#define LZ4_HASH_BITS 12U 
static inline uint32_t lz4_hash4(uint32_t val)
{
    return (val * 2654435761U) >> (32U - LZ4_HASH_BITS);
}

static inline uint32_t lz4_read32(const uint8_t *ptr)
{
    return ((uint32_t)ptr[0]) | (((uint32_t)ptr[1]) << 8) | (((uint32_t)ptr[2]) << 16) |
           (((uint32_t)ptr[3]) << 24);
}

SYN_Status syn_lz4_init(SYN_Lz4 *ctx)
{
    if (ctx == NULL) {
        return SYN_INVALID_PARAM;
    }
    memset(ctx->hash_table, 0, sizeof(ctx->hash_table));
    return SYN_OK;
}

size_t syn_lz4_compress_bound(size_t input_size)
{
    if (input_size > (SIZE_MAX - 16U - (input_size / 255U))) {
        return 0U; /* LCOV_EXCL_LINE: Defensive size_t overflow guard */
    }
    return input_size + (input_size / 255U) + 16U;
}

size_t syn_lz4_compress(SYN_Lz4 *ctx, const void *src, size_t src_size, void *dst,
                        size_t dst_capacity)
{
    if ((ctx == NULL) || (src == NULL) || (dst == NULL) || (src_size == 0U)) {
        return 0U;
    }

    const uint8_t *ip = (const uint8_t *)src;
    const uint8_t *in_end = ip + src_size;
    const uint8_t *anchor = ip;

    uint8_t *op = (uint8_t *)dst;
    uint8_t *out_end = op + dst_capacity;

    if (src_size < LZ4_MIN_MATCH + 1U) {
        /* Input too small for matching: store as single literal block */
        size_t lit_len = src_size;
        size_t needed = 1U + (lit_len >= 15U ? ((lit_len - 15U) / 255U + 1U) : 0U) + lit_len;
        if (needed > dst_capacity) {
            return 0U; /* LCOV_EXCL_LINE: Defensive bounds guard */
        }

        uint8_t token = (uint8_t)((lit_len >= 15U ? 15U : lit_len) << 4);
        *op++ = token;

        /* LCOV_EXCL_START */
        if (lit_len >= 15U) {
            size_t rem = lit_len - 15U;
            while (rem >= 255U) {
                *op++ = 255U;
                rem -= 255U;
            }
            *op++ = (uint8_t)rem;
        }
        /* LCOV_EXCL_STOP */

        memcpy(op, ip, lit_len);
        op += lit_len;
        return (size_t)(op - (uint8_t *)dst);
    }

    syn_lz4_init(ctx);

    const uint8_t *match_limit = in_end - 5U;
    while (ip < match_limit) {
        uint32_t val = lz4_read32(ip);
        uint32_t hash = lz4_hash4(val);
        const uint8_t *match = (const uint8_t *)src + ctx->hash_table[hash];

        ctx->hash_table[hash] = (uint16_t)(ip - (const uint8_t *)src);

        if ((match >= (const uint8_t *)src) && (match < ip) && ((size_t)(ip - match) <= 65535U) &&
            (lz4_read32(match) == val)) {
            /* Found a match! Calculate match length */
            size_t lit_len = (size_t)(ip - anchor);
            const uint8_t *ref = match + 4;
            const uint8_t *p = ip + 4;

            while ((p < in_end) && (*p == *ref)) {
                p++;
                ref++;
            }

            size_t match_len = (size_t)(p - ip) - LZ4_MIN_MATCH;
            size_t offset = (size_t)(ip - match);

            /* Output token */
            if (op >= out_end)
                return 0U; /* LCOV_EXCL_LINE: Defensive bounds guard */

            uint8_t token = (uint8_t)(((lit_len >= 15U ? 15U : lit_len) << 4) |
                                      (match_len >= 15U ? 15U : match_len));
            *op++ = token;

            /* Output literal extra length */
            if (lit_len >= 15U) {
                size_t rem = lit_len - 15U;
                /* LCOV_EXCL_START */
                while (rem >= 255U) {
                    if (op >= out_end)
                        return 0U;
                    *op++ = 255U;
                    rem -= 255U;
                }
                /* LCOV_EXCL_STOP */
                if (op >= out_end)
                    return 0U; /* LCOV_EXCL_LINE: Defensive bounds guard */
                *op++ = (uint8_t)rem;
            }

            /* Copy literals */
            if ((size_t)(out_end - op) < lit_len)
                return 0U; /* LCOV_EXCL_LINE: Defensive bounds guard */
            memcpy(op, anchor, lit_len);
            op += lit_len;

            /* Output offset (16-bit little-endian) */
            if ((size_t)(out_end - op) < 2U)
                return 0U; /* LCOV_EXCL_LINE: Defensive bounds guard */
            *op++ = (uint8_t)(offset & 0xFFU);
            *op++ = (uint8_t)((offset >> 8) & 0xFFU);

            /* Output match extra length */
            if (match_len >= 15U) {
                size_t rem = match_len - 15U;
                /* LCOV_EXCL_START */
                while (rem >= 255U) {
                    if (op >= out_end)
                        return 0U;
                    *op++ = 255U;
                    rem -= 255U;
                }
                /* LCOV_EXCL_STOP */
                if (op >= out_end)
                    return 0U; /* LCOV_EXCL_LINE: Defensive bounds guard */
                *op++ = (uint8_t)rem;
            }

            ip = p;
            anchor = ip;
        } else {
            ip++;
        }
    }

    /* Output remaining literals */
    size_t last_lit = (size_t)(in_end - anchor);
    if (last_lit > 0U) {
        if (op >= out_end)
            return 0U; /* LCOV_EXCL_LINE: Defensive bounds guard */
        uint8_t token = (uint8_t)((last_lit >= 15U ? 15U : last_lit) << 4);
        *op++ = token;

        /* LCOV_EXCL_START */
        if (last_lit >= 15U) {
            size_t rem = last_lit - 15U;
            while (rem >= 255U) {
                if (op >= out_end)
                    return 0U;
                *op++ = 255U;
                rem -= 255U;
            }
            if (op >= out_end)
                return 0U;
            *op++ = (uint8_t)rem;
        }
        /* LCOV_EXCL_STOP */

        if ((size_t)(out_end - op) < last_lit)
            return 0U; /* LCOV_EXCL_LINE: Defensive bounds guard */
        memcpy(op, anchor, last_lit);
        op += last_lit;
    }

    return (size_t)(op - (uint8_t *)dst);
}

size_t syn_lz4_decompress(const void *src, size_t src_size, void *dst, size_t dst_capacity)
{
    if ((src == NULL) || (dst == NULL) || (src_size == 0U) || (dst_capacity == 0U)) {
        return 0U;
    }

    const uint8_t *ip = (const uint8_t *)src;
    const uint8_t *in_end = ip + src_size;

    uint8_t *op = (uint8_t *)dst;
    uint8_t *out_end = op + dst_capacity;

    while (ip < in_end) {
        uint8_t token = *ip++;

        size_t lit_len = (size_t)(token >> 4);
        if (lit_len == 15U) {
            uint8_t s;
            /* LCOV_EXCL_START */
            do {
                if (ip >= in_end)
                    return 0U;
                s = *ip++;
                lit_len += s;
            } while (s == 255U);
            /* LCOV_EXCL_STOP */
        }

        if ((size_t)(in_end - ip) < lit_len)
            return 0U;
        if ((size_t)(out_end - op) < lit_len)
            return 0U;

        memcpy(op, ip, lit_len);
        ip += lit_len;
        op += lit_len;

        if (ip >= in_end) {
            /* Final literal sequence complete */
            break;
        }

        if ((size_t)(in_end - ip) < 2U)
            return 0U;
        size_t offset = (size_t)ip[0] | (((size_t)ip[1]) << 8);
        ip += 2;

        if ((offset == 0U) || (offset > (size_t)(op - (uint8_t *)dst))) {
            return 0U; /* Invalid offset */
        }

        size_t match_len = (size_t)(token & 0x0FU);
        if (match_len == 15U) {
            uint8_t s;
            /* LCOV_EXCL_START */
            do {
                if (ip >= in_end)
                    return 0U;
                s = *ip++;
                match_len += s;
            } while (s == 255U);
            /* LCOV_EXCL_STOP */
        }
        match_len += LZ4_MIN_MATCH;

        if ((size_t)(out_end - op) < match_len)
            return 0U; /* LCOV_EXCL_LINE: Defensive match length destination guard */

        const uint8_t *ref = op - offset;
        for (size_t i = 0U; i < match_len; i++) {
            *op++ = *ref++;
        }
    }

    return (size_t)(op - (uint8_t *)dst);
}