How to Handle Errors and Detect Corrupted Data When Using Kanzi: Complete Validation Guide
Kanzi detects corrupted data through three validation stages—header integrity, block checksums, and output size verification—raising IOException objects with specific error codes defined in src/Error.hpp or returning numeric codes through high-level APIs like BlockDecompressor.
The flanglet/kanzi-cpp compression library treats every failure as a well-defined error condition. Whether you are streaming data through CompressedInputStream or using the convenient BlockDecompressor wrapper, understanding how to handle errors and detect corrupted data ensures your application can gracefully manage truncated files, bit-flips, or version mismatches.
Three-Layer Validation Architecture
Kanzi validates bit-stream integrity at three distinct checkpoints. Each stage maps to a specific error code in src/Error.hpp and a corresponding location in the source code.
Header validation occurs in CompressedInputStream::readHeader(). The library verifies the magic word identifying the stream as BITSTREAM_TYPE, checks version compatibility, and validates a header-level checksum. Failures here raise IOException with Error::ERR_INVALID_FILE for magic word mismatches, Error::ERR_STREAM_VERSION for unsupported versions, or Error::ERR_CRC_CHECK for header corruption.
Block checksum verification happens inside CompressedInputStream::run() immediately after inverse transformation. If the stream was created with a hasher, Kanzi recomputes the XXHash (32- or 64-bit) of the decompressed block and compares it against the checksum stored in the block footer. A mismatch triggers Error::ERR_CRC_CHECK.
Output size verification is performed by BlockDecompressor::run() after the entire stream is consumed. The total bytes written are compared against the original size stored in the header. A discrepancy results in Error::ERR_INVALID_FILE.
Error Codes and Exception Types
All error conditions are enumerated in src/Error.hpp. The library uses integer constants consistently across C++ and language bindings:
// src/Error.hpp
enum ErrorCode {
ERR_MISSING_PARAM = 1,
ERR_BLOCK_SIZE = 2,
ERR_INVALID_CODEC = 3,
// ...
ERR_CRC_CHECK = 19,
ERR_UNKNOWN = 127
};
The IOException class defined in src/io/IOException.hpp extends std::runtime_error to couple human-readable messages with programmatic error codes:
// src/io/IOException.hpp
class IOException : public std::runtime_error {
private:
int _code;
public:
IOException(const std::string& msg, int error)
: std::runtime_error(msg + ". Error code: " + TOSTR(error)), _code(error) {}
int error() const { return _code; }
};
Constructor validation in CompressedInputStream uses std::invalid_argument for illegal parameters such as invalid block sizes or checksum configurations, distinguishing programming errors from runtime corruption.
Detecting Corruption in CompressedInputStream
When using the streaming API directly, error handling follows a lazy evaluation pattern. The constructor validates arguments immediately, but header parsing is deferred until the first read() call invokes readHeader().
During block processing in CompressedInputStream::run(), each entropy-decoded block undergoes inverse transformation before its checksum is recomputed. This check is atomic per block, allowing early detection of localized corruption without processing the entire file.
High-Level Error Handling with BlockDecompressor
The BlockDecompressor API in src/app/BlockDecompressor.cpp encapsulates the streaming logic and converts exceptions into numeric return codes. This approach is essential for C-style interfaces or when integrating with languages that prefer error codes over exceptions.
The decompressor catches IOException internally and returns the associated error code from Error.hpp. Additionally, it performs the final output size verification mentioned above, ensuring the decompressed payload matches the header's recorded original size exactly.
Practical Code Examples
Exception-Based Error Handling with CompressedInputStream
Use this pattern when you prefer C++ exception handling and need granular control over streaming:
#include "kanzi.hpp"
#include "io/CompressedInputStream.hpp"
#include "io/IOException.hpp"
int main() {
try {
std::ifstream ifs("sample.knz", std::ios::binary);
if (!ifs) throw std::runtime_error("cannot open file");
kanzi::CompressedInputStream cis(ifs, 0 /*default context*/);
const std::size_t bufSize = 1<<20; // 1 MiB buffer
std::vector<kanzi::byte> buf(bufSize);
while (!cis.eof()) {
cis.read(reinterpret_cast<char*>(buf.data()), bufSize);
std::size_t got = cis.gcount();
// …process `got` bytes…
}
}
catch (const kanzi::IOException& ex) {
std::cerr << "Decompression failed: " << ex.what()
<< " (code " << ex.error() << ")\n";
// react to specific codes if needed
if (ex.error() == kanzi::Error::ERR_CRC_CHECK) {
std::cerr << " → corrupted block detected!\n";
}
}
}
The constructor may throw std::invalid_argument for parameter errors, while readHeader() and block processing throw IOException for corruption.
Return Code-Based Error Handling with BlockDecompressor
Use this pattern for robust applications that avoid exceptions or need to integrate with C-style error handling:
#include "app/BlockDecompressor.hpp"
int main() {
kanzi::Context ctx;
ctx.putString("inputName", "corrupted.knz");
ctx.putString("outputName", "out.dat");
ctx.putInt("verbosity", 2);
kanzi::BlockDecompressor dec(ctx);
uint64 totalRead = 0;
int rc = dec.decompress(totalRead);
if (rc != 0) {
std::cerr << "Decompression error code: " << rc << "\n";
// Translate using Error.hpp constants
if (rc == kanzi::Error::ERR_CRC_CHECK) {
std::cerr << "Data corruption detected\n";
}
} else {
std::cout << "Successfully decompressed " << totalRead << " bytes.\n";
}
}
Python Wrapper Error Detection
The Python wrapper exposes the same integer error codes for language-agnostic handling:
import kanzi_c_api as knz
def decompress(src, dst):
rc, decoded = knz.decompress(src, dst)
if rc != 0:
print(f"Error {rc}: {knz.ErrorMessage(rc)}")
if rc == knz.ERR_CRC_CHECK:
print(" → corrupted data")
else:
print(f"Decompressed {decoded} bytes")
Summary
- Kanzi validates compressed streams at three stages: header integrity, per-block XXHash checksums, and final output size verification.
- Error codes are centralized in
src/Error.hppand propagated viaIOExceptionobjects or numeric return values fromBlockDecompressor. - Header corruption triggers
ERR_INVALID_FILEorERR_STREAM_VERSIONduring the lazy initialization ofCompressedInputStream. - Data corruption within blocks is detected via checksum mismatch in
CompressedInputStream::run(), raisingERR_CRC_CHECK. - Use
ex.error()on caughtIOExceptioninstances to programmatically distinguish between corruption, version mismatches, and parameter errors.
Frequently Asked Questions
What error code does Kanzi return for a corrupted file?
Kanzi returns Error::ERR_CRC_CHECK (integer value 19) when a block checksum fails validation during decompression. This indicates that the decompressed data does not match the XXHash stored in the block footer, signaling bit-rot or transmission errors.
How can I distinguish between a version mismatch and actual data corruption?
Header version mismatches raise Error::ERR_STREAM_VERSION in CompressedInputStream::readHeader(), while data corruption inside valid blocks raises Error::ERR_CRC_CHECK. Both use IOException, but you can inspect ex.error() to differentiate: version errors occur before any block processing begins, whereas checksum errors occur during the streaming loop.
Is it safe to continue reading a stream after catching an IOException?
No. Once CompressedInputStream throws an IOException due to failed header validation or checksum mismatch, the stream enters an error state that invalidates further operations. You must destroy the CompressedInputStream object and reopen the source to attempt recovery, preferably after verifying the input file's integrity externally.
Where does Kanzi compute the checksums during compression?
The symmetric counterpart in src/io/CompressedOutputStream.cpp computes XXHash checksums for each compressed block during the write process. These values are embedded in the block footer and later extracted by CompressedInputStream::run() for comparison, ensuring end-to-end integrity across the compression-decompression lifecycle.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →