chore: Remove miniz and modularise inflation logic (#1073)
## Summary * Remove miniz and move completely to uzlib * Move uzlib interfacing to InflateReader to better modularise inflation code --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? Yes, Claude helped with the extraction and refactor
This commit is contained in:
@@ -1,35 +1,38 @@
|
||||
#include "ZipFile.h"
|
||||
|
||||
#include <HalStorage.h>
|
||||
#include <InflateReader.h>
|
||||
#include <Logging.h>
|
||||
#include <miniz.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
static bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf,
|
||||
const size_t inflatedSize) {
|
||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||
if (!inflator) {
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
|
||||
return false;
|
||||
}
|
||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
||||
tinfl_init(inflator);
|
||||
struct ZipInflateCtx {
|
||||
InflateReader reader; // Must be first — callback casts uzlib_uncomp* to ZipInflateCtx*
|
||||
FsFile* file = nullptr;
|
||||
size_t fileRemaining = 0;
|
||||
uint8_t* readBuf = nullptr;
|
||||
size_t readBufSize = 0;
|
||||
};
|
||||
|
||||
size_t inBytes = deflatedSize;
|
||||
size_t outBytes = inflatedSize;
|
||||
const tinfl_status status = tinfl_decompress(inflator, inputBuf, &inBytes, nullptr, outputBuf, &outBytes,
|
||||
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
|
||||
namespace {
|
||||
constexpr uint16_t ZIP_METHOD_STORED = 0;
|
||||
constexpr uint16_t ZIP_METHOD_DEFLATED = 8;
|
||||
|
||||
free(inflator);
|
||||
int zipReadCallback(uzlib_uncomp* uncomp) {
|
||||
auto* ctx = reinterpret_cast<ZipInflateCtx*>(uncomp);
|
||||
if (ctx->fileRemaining == 0) return -1;
|
||||
|
||||
if (status != TINFL_STATUS_DONE) {
|
||||
LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status);
|
||||
return false;
|
||||
}
|
||||
const size_t toRead = ctx->fileRemaining < ctx->readBufSize ? ctx->fileRemaining : ctx->readBufSize;
|
||||
const size_t bytesRead = ctx->file->read(ctx->readBuf, toRead);
|
||||
ctx->fileRemaining -= bytesRead;
|
||||
|
||||
return true;
|
||||
if (bytesRead == 0) return -1;
|
||||
|
||||
uncomp->source = ctx->readBuf + 1;
|
||||
uncomp->source_limit = ctx->readBuf + bytesRead;
|
||||
return ctx->readBuf[0];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool ZipFile::loadAllFileStatSlims() {
|
||||
const bool wasOpen = isOpen();
|
||||
@@ -111,7 +114,6 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) {
|
||||
|
||||
// Phase 1: Try scanning from cursor position first
|
||||
uint32_t startPos = lastCentralDirPosValid ? lastCentralDirPos : zipDetails.centralDirOffset;
|
||||
uint32_t wrapPos = zipDetails.centralDirOffset;
|
||||
bool wrapped = false;
|
||||
bool found = false;
|
||||
|
||||
@@ -201,7 +203,7 @@ long ZipFile::getDataOffset(const FileStatSlim& fileStat) {
|
||||
}
|
||||
|
||||
if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) !=
|
||||
0x04034b50 /* MZ_ZIP_LOCAL_DIR_HEADER_SIG */) {
|
||||
0x04034b50 /* ZIP local file header signature */) {
|
||||
LOG_ERR("ZIP", "Not a valid zip file header");
|
||||
return -1;
|
||||
}
|
||||
@@ -415,7 +417,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fileStat.method == MZ_NO_COMPRESSION) {
|
||||
if (fileStat.method == ZIP_METHOD_STORED) {
|
||||
// no deflation, just read content
|
||||
const size_t dataRead = file.read(data, inflatedDataSize);
|
||||
if (!wasOpen) {
|
||||
@@ -429,7 +431,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
||||
}
|
||||
|
||||
// Continue out of block with data set
|
||||
} else if (fileStat.method == MZ_DEFLATED) {
|
||||
} else if (fileStat.method == ZIP_METHOD_DEFLATED) {
|
||||
// Read out deflated content from file
|
||||
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
|
||||
if (deflatedData == nullptr) {
|
||||
@@ -452,7 +454,13 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool success = inflateOneShot(deflatedData, deflatedDataSize, data, inflatedDataSize);
|
||||
bool success = false;
|
||||
{
|
||||
InflateReader r;
|
||||
r.init(false);
|
||||
r.setSource(deflatedData, deflatedDataSize);
|
||||
success = r.read(data, inflatedDataSize);
|
||||
}
|
||||
free(deflatedData);
|
||||
|
||||
if (!success) {
|
||||
@@ -495,7 +503,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
||||
const auto deflatedDataSize = fileStat.compressedSize;
|
||||
const auto inflatedDataSize = fileStat.uncompressedSize;
|
||||
|
||||
if (fileStat.method == MZ_NO_COMPRESSION) {
|
||||
if (fileStat.method == ZIP_METHOD_STORED) {
|
||||
// no deflation, just read content
|
||||
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (!buffer) {
|
||||
@@ -529,127 +537,88 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fileStat.method == MZ_DEFLATED) {
|
||||
auto* inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||
if (!inflator) {
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
||||
tinfl_init(inflator);
|
||||
|
||||
// Setup file read buffer
|
||||
const auto fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (fileStat.method == ZIP_METHOD_DEFLATED) {
|
||||
auto* fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (!fileReadBuffer) {
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for zip file read buffer");
|
||||
free(inflator);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto outputBuffer = static_cast<uint8_t*>(malloc(TINFL_LZ_DICT_SIZE));
|
||||
auto* outputBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (!outputBuffer) {
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for dictionary");
|
||||
free(inflator);
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for output buffer");
|
||||
free(fileReadBuffer);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
memset(outputBuffer, 0, TINFL_LZ_DICT_SIZE);
|
||||
|
||||
size_t fileRemainingBytes = deflatedDataSize;
|
||||
size_t processedOutputBytes = 0;
|
||||
size_t fileReadBufferFilledBytes = 0;
|
||||
size_t fileReadBufferCursor = 0;
|
||||
size_t outputCursor = 0; // Current offset in the circular dictionary
|
||||
ZipInflateCtx ctx;
|
||||
ctx.file = &file;
|
||||
ctx.fileRemaining = deflatedDataSize;
|
||||
ctx.readBuf = fileReadBuffer;
|
||||
ctx.readBufSize = chunkSize;
|
||||
|
||||
if (!ctx.reader.init(true)) {
|
||||
LOG_ERR("ZIP", "Failed to init inflate reader");
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ctx.reader.setReadCallback(zipReadCallback);
|
||||
|
||||
bool success = false;
|
||||
size_t totalProduced = 0;
|
||||
|
||||
while (true) {
|
||||
// Load more compressed bytes when needed
|
||||
if (fileReadBufferCursor >= fileReadBufferFilledBytes) {
|
||||
if (fileRemainingBytes == 0) {
|
||||
// Should not be hit, but a safe protection
|
||||
break; // EOF
|
||||
}
|
||||
size_t produced;
|
||||
const InflateStatus status = ctx.reader.readAtMost(outputBuffer, chunkSize, &produced);
|
||||
|
||||
fileReadBufferFilledBytes =
|
||||
file.read(fileReadBuffer, fileRemainingBytes < chunkSize ? fileRemainingBytes : chunkSize);
|
||||
fileRemainingBytes -= fileReadBufferFilledBytes;
|
||||
fileReadBufferCursor = 0;
|
||||
|
||||
if (fileReadBufferFilledBytes == 0) {
|
||||
// Bad read
|
||||
break; // EOF
|
||||
}
|
||||
totalProduced += produced;
|
||||
if (totalProduced > static_cast<size_t>(inflatedDataSize)) {
|
||||
LOG_ERR("ZIP", "Decompressed size exceeds expected (%zu > %zu)", totalProduced,
|
||||
static_cast<size_t>(inflatedDataSize));
|
||||
break;
|
||||
}
|
||||
|
||||
// Available bytes in fileReadBuffer to process
|
||||
size_t inBytes = fileReadBufferFilledBytes - fileReadBufferCursor;
|
||||
// Space remaining in outputBuffer
|
||||
size_t outBytes = TINFL_LZ_DICT_SIZE - outputCursor;
|
||||
|
||||
const tinfl_status status = tinfl_decompress(inflator, fileReadBuffer + fileReadBufferCursor, &inBytes,
|
||||
outputBuffer, outputBuffer + outputCursor, &outBytes,
|
||||
fileRemainingBytes > 0 ? TINFL_FLAG_HAS_MORE_INPUT : 0);
|
||||
|
||||
// Update input position
|
||||
fileReadBufferCursor += inBytes;
|
||||
|
||||
// Write output chunk
|
||||
if (outBytes > 0) {
|
||||
processedOutputBytes += outBytes;
|
||||
if (out.write(outputBuffer + outputCursor, outBytes) != outBytes) {
|
||||
if (produced > 0) {
|
||||
if (out.write(outputBuffer, produced) != produced) {
|
||||
LOG_ERR("ZIP", "Failed to write all output bytes to stream");
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
free(inflator);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
// Update output position in buffer (with wraparound)
|
||||
outputCursor = (outputCursor + outBytes) & (TINFL_LZ_DICT_SIZE - 1);
|
||||
}
|
||||
|
||||
if (status < 0) {
|
||||
LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
if (status == InflateStatus::Done) {
|
||||
if (totalProduced != static_cast<size_t>(inflatedDataSize)) {
|
||||
LOG_ERR("ZIP", "Decompressed size mismatch (expected %zu, got %zu)", static_cast<size_t>(inflatedDataSize),
|
||||
totalProduced);
|
||||
break;
|
||||
}
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
free(inflator);
|
||||
return false;
|
||||
LOG_DBG("ZIP", "Decompressed %d bytes into %d bytes", deflatedDataSize, inflatedDataSize);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status == TINFL_STATUS_DONE) {
|
||||
LOG_ERR("ZIP", "Decompressed %d bytes into %d bytes", deflatedDataSize, inflatedDataSize);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
free(inflator);
|
||||
free(fileReadBuffer);
|
||||
free(outputBuffer);
|
||||
return true;
|
||||
if (status == InflateStatus::Error) {
|
||||
LOG_ERR("ZIP", "Decompression failed");
|
||||
break;
|
||||
}
|
||||
// InflateStatus::Ok: output buffer full, continue
|
||||
}
|
||||
|
||||
// If we get here, EOF reached without TINFL_STATUS_DONE
|
||||
LOG_ERR("ZIP", "Unexpected EOF");
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
free(inflator);
|
||||
return false;
|
||||
return success; // ctx.reader destructor frees the ring buffer
|
||||
}
|
||||
|
||||
if (!wasOpen) {
|
||||
|
||||
Reference in New Issue
Block a user