You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
7.4 KiB
C

// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file cpr/flate.c
*
* Interfaces with `zlib` and implements INFLATE/DEFLATE.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "cpr/flate.h"
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
typedef struct ZlibStmObj ZlibStmObj;
struct ZlibStmObj {
z_stream zs;
void *streamp;
const StmOps *ops;
unsigned bufsiz;
Boolean8 deflating;
Uint8 buf[FLEX_ARRAY]; // `bufsiz` bytes
};
#define ZSTM_BUFSIZ (32 * 1024)
#define ZSTM_EIO -1LL
#define ZSTM_EBADSTREAM -2LL
static Sint64 Zlib_FlushData(ZlibStmHn hn)
{
size_t nbytes = hn->bufsiz - hn->zs.avail_out;
Sint64 n = hn->ops->Write(hn->streamp, hn->buf, nbytes);
if (n == -1)
return -1;
size_t left = nbytes - n;
memmove(hn->buf, hn->buf + n, left);
hn->zs.next_out = hn->buf + left;
hn->zs.avail_out = hn->bufsiz - left;
return n;
}
static Sint64 Zlib_StmRead(void *streamp, void *buf, size_t nbytes)
{
return Zlib_Read((ZlibStmHn) streamp, buf, nbytes);
}
static Sint64 Zlib_StmWrite(void *streamp, const void *buf, size_t nbytes)
{
return Zlib_Write((ZlibStmHn) streamp, buf, nbytes);
}
static Sint64 Zlib_StmTell(void *streamp)
{
ZlibStmHn hn = (ZlibStmHn) streamp;
return hn->deflating ? hn->zs.total_out : hn->zs.total_in;
}
static Judgement Zlib_StmFinish(void *streamp)
{
return Zlib_Finish((ZlibStmHn) streamp);
}
static void Zlib_StmClose(void *streamp)
{
Zlib_Close((ZlibStmHn) streamp);
}
static const StmOps zlib_stmOps = {
Zlib_StmRead,
Zlib_StmWrite,
NULL,
Zlib_StmTell,
Zlib_StmFinish,
Zlib_StmClose
};
static const StmOps zlib_ncStmOps = {
Zlib_StmRead,
Zlib_StmWrite,
NULL,
Zlib_StmTell,
Zlib_StmFinish,
NULL
};
const StmOps *const Zlib_StmOps = &zlib_stmOps;
const StmOps *const Zlib_NcStmOps = &zlib_ncStmOps;
static THREAD_LOCAL ZlibRet zlib_errStat = 0;
static void Zlib_SetErrStat(ZlibRet ret)
{
if (ret == Z_ERRNO) {
Sint64 err = errno;
ret |= (Sint32) (err << 32);
}
zlib_errStat = ret;
}
ZlibRet Zlib_GetErrStat(void)
{
return zlib_errStat;
}
const char *Zlib_ErrorString(ZlibRet ret)
{
if (ret == ZSTM_EIO)
return "I/O error";
if (ret == ZSTM_EBADSTREAM)
return "Bad stream operation";
Sint32 zerrno = (Sint32) (ret & 0xffffffffu);
Sint32 err = (Sint32) (ret >> 32);
switch (zerrno) {
case Z_OK: return "Success";
case Z_ERRNO: return strerror(err);
case Z_STREAM_ERROR: return "Stream error";
case Z_DATA_ERROR: return "Data error";
case Z_MEM_ERROR: return "Memory allocation failure";
case Z_BUF_ERROR: return "Buffer error";
case Z_VERSION_ERROR: return "Zlib version error";
default: return "Unknown Zlib error";
}
}
ZlibStmHn Zlib_InflateOpen(void *streamp,
const StmOps *ops,
const InflateOpts *opts)
{
const InflateOpts default_opts = {
15,
ZFMT_RFC1952,
ZSTM_BUFSIZ
};
if (!ops->Write) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return NULL;
}
if (!opts)
opts = &default_opts;
int wbits = CLAMP(opts->win_bits, 8, 15);
// Mangle window bits according to the required RFC
switch (opts->format) {
case ZFMT_RFC1951:
wbits = -wbits;
break;
case ZFMT_RFC1950:
break;
case ZFMT_RFC1952:
default:
wbits += 16;
break;
}
size_t bufsiz = MIN(opts->bufsiz, INT_MAX); // safety to avoid short reads
if (bufsiz == 0)
bufsiz = ZSTM_BUFSIZ;
ZlibStmObj *hn = (ZlibStmObj *) malloc(offsetof(ZlibStmObj, buf[bufsiz]));
if (!hn) {
Zlib_SetErrStat(Z_MEM_ERROR);
return NULL;
}
memset(&hn->zs, 0, sizeof(hn->zs));
int err = inflateInit2(&hn->zs, wbits);
if (err != Z_OK) {
Zlib_SetErrStat(err);
free(hn);
return NULL;
}
hn->streamp = streamp;
hn->ops = ops;
hn->bufsiz = bufsiz;
hn->deflating = FALSE;
Zlib_SetErrStat(Z_OK);
return hn;
}
ZlibStmHn Zlib_DeflateOpen(void *streamp,
const StmOps *ops,
const DeflateOpts *opts)
{
const DeflateOpts default_opts = {
Z_DEFAULT_COMPRESSION,
15,
ZFMT_RFC1952,
ZSTM_BUFSIZ
};
if (!ops->Read) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return NULL;
}
if (!opts)
opts = &default_opts;
// Setup open options
int compression = opts->compression;
if (compression != Z_DEFAULT_COMPRESSION)
compression = CLAMP(compression, 0, 9);
int wbits = CLAMP(opts->win_bits, 8, 15);
// Mangle window bits according to the required RFC
switch (opts->format) {
case ZFMT_RFC1951:
wbits = -wbits;
break;
case ZFMT_RFC1950:
break;
case ZFMT_RFC1952:
default:
wbits += 16;
break;
}
size_t bufsiz = MIN(opts->bufsiz, INT_MAX); // safety to avoid short reads
if (bufsiz == 0)
bufsiz = ZSTM_BUFSIZ;
ZlibStmObj *hn = (ZlibStmObj *) malloc(offsetof(ZlibStmObj, buf[bufsiz]));
if (!hn) {
Zlib_SetErrStat(Z_MEM_ERROR);
return NULL;
}
memset(&hn->zs, 0, sizeof(hn->zs));
hn->streamp = streamp;
hn->ops = ops;
hn->bufsiz = bufsiz;
hn->deflating = TRUE;
int err = deflateInit2(
&hn->zs,
compression,
Z_DEFLATED,
wbits,
8,
Z_DEFAULT_STRATEGY
);
if (err != Z_OK) {
Zlib_SetErrStat(err);
free(hn);
return NULL;
}
hn->zs.next_out = hn->buf;
hn->zs.avail_out = hn->bufsiz;
Zlib_SetErrStat(Z_OK);
return hn;
}
Sint64 Zlib_Read(ZlibStmHn hn, void *buf, size_t nbytes)
{
if (hn->deflating) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return -1;
}
ZlibRet ret = Z_OK; // unless found otherwise
hn->zs.next_out = (Uint8 *) buf;
hn->zs.avail_out = nbytes;
while (hn->zs.avail_out > 0) {
if (hn->zs.avail_in == 0) {
// Fill buffer
Sint64 n = hn->ops->Read(hn->streamp, hn->buf, hn->bufsiz);
if (n <= 0) {
if (n < 0) ret = ZSTM_EIO;
break;
}
hn->zs.next_in = hn->buf;
hn->zs.avail_in = n;
}
int err = inflate(&hn->zs, Z_NO_FLUSH);
if (err == Z_NEED_DICT)
err = Z_DATA_ERROR;
if (err != Z_OK && err != Z_STREAM_END) {
ret = err;
break;
}
}
Zlib_SetErrStat(ret);
return nbytes - hn->zs.avail_out;
}
Sint64 Zlib_Write(ZlibStmHn hn, const void *buf, size_t nbytes)
{
if (!hn->deflating) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return -1;
}
ZlibRet ret = Z_OK;
hn->zs.next_in = (Uint8 *) buf;
hn->zs.avail_in = nbytes;
while (hn->zs.avail_in > 0) {
if (hn->zs.avail_out == 0) {
Sint64 n = Zlib_FlushData(hn);
if (n <= 0) {
if (n < 0) ret = ZSTM_EIO;
break; // short-write
}
}
int err = deflate(&hn->zs, Z_NO_FLUSH);
if (err == Z_NEED_DICT)
err = Z_DATA_ERROR;
if (err != Z_OK) {
ret = err;
break;
}
}
Zlib_SetErrStat(ret);
return nbytes - hn->zs.avail_in;
}
Judgement Zlib_Finish(ZlibStmHn hn)
{
if (!hn->deflating) {
Zlib_SetErrStat(ZSTM_EBADSTREAM);
return NG;
}
int err;
do {
err = deflate(&hn->zs, Z_FINISH);
if (err != Z_STREAM_END && err != Z_BUF_ERROR && err != Z_OK) {
Zlib_SetErrStat(err);
return NG;
}
if (Zlib_FlushData(hn) == -1) {
Zlib_SetErrStat(ZSTM_EIO);
return NG;
}
} while (err != Z_STREAM_END);
if (hn->zs.avail_out != hn->bufsiz) {
Zlib_SetErrStat(Z_BUF_ERROR);
return NG;
}
Zlib_SetErrStat(Z_OK);
return OK;
}
void Zlib_Close(ZlibStmHn hn)
{
// Close stream
if (hn->ops->Close)
hn->ops->Close(hn->streamp);
// Finalize Z_stream
if (hn->deflating)
deflateEnd(&hn->zs);
else
inflateEnd(&hn->zs);
free(hn); // Free memory
}