From b556be871a429f35b21c2b405c259532eeba2891 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Fri, 21 Aug 2026 12:18:42 -0700 Subject: [PATCH 1/7] Add inflatelib_version --- src/include/inflatelib.h | 28 ++++++++++++++++++++++------ src/lib/huffman_tree.h | 2 +- src/lib/inflate.c | 35 ++++++++++++++++++++++++++++------- src/lib/window.h | 2 +- test/cpp/InflateTests.cpp | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src/include/inflatelib.h b/src/include/inflatelib.h index 4cd7eea..561b78b 100644 --- a/src/include/inflatelib.h +++ b/src/include/inflatelib.h @@ -140,17 +140,33 @@ extern "C" * will remain unchanged. A positive return value indicates an "interesting" change in state that is not considered a * failure, while a return value of zero indicates generic success. */ -#define INFLATELIB_OK 0 /* No error occurred */ -#define INFLATELIB_EOF 1 /* No error occurred; reached the end of the stream */ -#define INFLATELIB_ERROR_ARG -1 /* Invalid argument */ -#define INFLATELIB_ERROR_DATA -2 /* Error in the input data */ -#define INFLATELIB_ERROR_OOM -3 /* Failed to allocate data */ +#define INFLATELIB_OK 0 /* No error occurred */ +#define INFLATELIB_EOF 1 /* No error occurred; reached the end of the stream */ +#define INFLATELIB_ERROR_ARG -1 /* Invalid argument */ +#define INFLATELIB_ERROR_DATA -2 /* Error in the input data */ +#define INFLATELIB_ERROR_OOM -3 /* Failed to allocate data */ +#define INFLATELIB_ERROR_VERSION -4 /* The major version of this header and the compiled library differ */ + + /* + * Returns the version of the compiled library as a string in the form 'X.Y.Z'. The implementation just returns + * 'INFLATELIB_VERSION_STRING', however this may differ when linking, statically or dynamically, against a pre-built + * library. The major version of 'INFLATELIB_VERSION_STRING' must match the major version returned by this function, + * otherwise the two are incompatible (struct size, arrangement, etc. may differ). The 'inflatelib_init' macro does + * this for you by passing 'INFLATELIB_VERSION_STRING' to 'inflatelib_init_'. + */ + INFLATELIB_EXPORT const char* INFLATELIB_CALLCONV inflatelib_version(); /* * Initializes the stream. The 'user_data', 'alloc', and 'free' members MUST be set prior to the init call and MUST * NOT be changed after the init call completes. This function returns one of the status values specified above. */ - INFLATELIB_EXPORT int INFLATELIB_CALLCONV inflatelib_init(inflatelib_stream* stream); + #define inflatelib_init(stream) inflatelib_init_(stream, INFLATELIB_VERSION_STRING) + + /* + * Backing implementation of 'inflatelib_init' that verififes the major version of this header matches the major + * version the library was compiled with. + */ + INFLATELIB_EXPORT int INFLATELIB_CALLCONV inflatelib_init_(inflatelib_stream* stream, const char* version); /* * Resets the stream's state back to its initialized state. This allows the stream to be reused for multiple inflate diff --git a/src/lib/huffman_tree.h b/src/lib/huffman_tree.h index 9b6001a..f4d4137 100644 --- a/src/lib/huffman_tree.h +++ b/src/lib/huffman_tree.h @@ -11,7 +11,7 @@ #include -// NOTE: We can't include 'internal.h' since it includes us, so forward declare what we need +/* NOTE: We can't include 'internal.h' since it includes us, so forward declare what we need */ struct inflatelib_stream; /* Code length codes are encoded using 3 bits (length of 0-7 bits), and the code length alphabet, which defines how the diff --git a/src/lib/inflate.c b/src/lib/inflate.c index 469f594..bf23051 100644 --- a/src/lib/inflate.c +++ b/src/lib/inflate.c @@ -14,6 +14,11 @@ #include "internal.h" +const char* INFLATELIB_CALLCONV inflatelib_version() +{ + return INFLATELIB_VERSION_STRING; +} + static void* inflatelib_default_alloc(void* unusedUserData, size_t bytes, size_t alignment) { void* result; @@ -35,10 +40,26 @@ static void inflatelib_default_free(void* unusedUserData, void* ptr, size_t byte free(ptr); } -int inflatelib_init(inflatelib_stream* stream) +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) +#define INFLATELIB_VERSION_MAJOR_STRING STRINGIFY(INFLATELIB_VERSION_MAJOR) + +int INFLATELIB_CALLCONV inflatelib_init_(inflatelib_stream* stream, const char* version) { int result; inflatelib_state* state; + const char* verPeriod; + const char expectedMajorVersion[] = INFLATELIB_VERSION_MAJOR_STRING; + + /* If the major versions don't match, make no assumptions about the layout of 'inflatelib_stream' as it may not be + safe to write to. This failure is communicated ONLY through the return value. */ + verPeriod = strchr(version, '.'); + if (!verPeriod || ((verPeriod - version) != (sizeof(expectedMajorVersion) - 1)) || + strncmp(version, expectedMajorVersion, verPeriod - version) != 0) + { + errno = EINVAL; + return INFLATELIB_ERROR_VERSION; + } /* Start with no error message, in case it was set before (or contains uninitialized memory) */ stream->error_msg = NULL; @@ -93,7 +114,7 @@ int inflatelib_init(inflatelib_stream* stream) return INFLATELIB_OK; } -int inflatelib_reset(inflatelib_stream* stream) +int INFLATELIB_CALLCONV inflatelib_reset(inflatelib_stream* stream) { inflatelib_state* state = stream->internal; @@ -107,15 +128,15 @@ int inflatelib_reset(inflatelib_stream* stream) bitstream_reset(&state->bitstream); window_reset(&state->window); - // NOTE: The Huffman trees do not need to be reset as they are reset on demand as needed. If we've made it this far, - // all of their internal state has been allocated, and that's the best that we can ask for + /* NOTE: The Huffman trees do not need to be reset as they are reset on demand as needed. If we've made it this far, + all of their internal state has been allocated, and that's the best that we can ask for */ state->ifstate = ifstate_init; return INFLATELIB_OK; } -int inflatelib_destroy(inflatelib_stream* stream) +int INFLATELIB_CALLCONV inflatelib_destroy(inflatelib_stream* stream) { inflatelib_state* state = stream->internal; @@ -238,7 +259,7 @@ static int do_inflate(inflatelib_stream* stream) return result; } -int inflatelib_inflate(inflatelib_stream* stream) +int INFLATELIB_CALLCONV inflatelib_inflate(inflatelib_stream* stream) { inflatelib_state* state = stream->internal; @@ -273,7 +294,7 @@ int inflatelib_inflate(inflatelib_stream* stream) return do_inflate(stream); } -int inflatelib_inflate64(inflatelib_stream* stream) +int INFLATELIB_CALLCONV inflatelib_inflate64(inflatelib_stream* stream) { inflatelib_state* state = stream->internal; diff --git a/src/lib/window.h b/src/lib/window.h index d401630..984603d 100644 --- a/src/lib/window.h +++ b/src/lib/window.h @@ -73,4 +73,4 @@ extern "C" } #endif -#endif // INFLATELIB_WINDOW_H +#endif diff --git a/test/cpp/InflateTests.cpp b/test/cpp/InflateTests.cpp index 3299ffb..6d1ba92 100644 --- a/test/cpp/InflateTests.cpp +++ b/test/cpp/InflateTests.cpp @@ -272,6 +272,11 @@ static void inflate64_error_test(const char* inputFileName, const char* errFragm do_inflate_test<&inflatelib::stream::try_inflate64>(input, {}, errFragment); } +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) +#define MAJOR_VERSION_STRING STRINGIFY(INFLATELIB_VERSION_MAJOR) +#define MAKE_VERSION_STRING(x, y, z) STRINGIFY(x) "." STRINGIFY(y) "." STRINGIFY(z) + TEST_CASE("InflateErrors", "[inflate]") { inflate_error_test("error.invalid-block-type.in.bin", "Unexpected block type '3'"); @@ -279,6 +284,20 @@ TEST_CASE("InflateErrors", "[inflate]") // Error if we call 'inflatelib_inflate' before calling 'inflate64_init' inflatelib_stream stream = {}; REQUIRE(inflatelib_inflate(&stream) == INFLATELIB_ERROR_ARG); + + // Error if we have a mis-matched major version + auto checkMajorVersionMismatch = [&](const char* version) { + REQUIRE(inflatelib_init_(&stream, version) == INFLATELIB_ERROR_VERSION); + }; + + checkMajorVersionMismatch("." INFLATELIB_VERSION_STRING); // Leading dot + checkMajorVersionMismatch(" " INFLATELIB_VERSION_STRING); // Leading space + checkMajorVersionMismatch("0" INFLATELIB_VERSION_STRING); // Leading zero + checkMajorVersionMismatch(MAJOR_VERSION_STRING "0.0.0"); // Zero after the correct major version + checkMajorVersionMismatch(MAJOR_VERSION_STRING); // Only major version; not a supported form + // Different major version - library is older + checkMajorVersionMismatch(MAKE_VERSION_STRING("9", INFLATELIB_VERSION_MINOR, INFLATELIB_VERSION_PATCH)); + // TODO: Once we reach a 1.0 release, we can test when the library is newer } TEST_CASE("Inflate64Errors", "[inflate64]") @@ -290,6 +309,19 @@ TEST_CASE("Inflate64Errors", "[inflate64]") REQUIRE(inflatelib_inflate64(&stream) == INFLATELIB_ERROR_ARG); } +TEST_CASE("InflateInitDifferentMinorVersion", "[inflate]") +{ + auto checkInit = [](const char* version) { + inflatelib_stream stream = {}; + REQUIRE(inflatelib_init_(&stream, version) == INFLATELIB_OK); + inflatelib_destroy(&stream); + }; + + checkInit(INFLATELIB_VERSION_STRING); // Same version, obvious success + checkInit(MAKE_VERSION_STRING(INFLATELIB_VERSION_MAJOR, 0, 0)); // Library is (likely) newer + checkInit(MAKE_VERSION_STRING(INFLATELIB_VERSION_MAJOR, 999, 999)); // Library is (likely) older +} + TEST_CASE("InflateUncompressed", "[inflate]") { inflate_test("uncompressed.empty.in.bin", "uncompressed.empty.out.bin"); From 284444853788505186d502f832429d41aa28b741 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Fri, 21 Aug 2026 12:25:23 -0700 Subject: [PATCH 2/7] Formatting --- src/include/inflatelib.h | 10 +++++----- test/cpp/InflateTests.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/include/inflatelib.h b/src/include/inflatelib.h index 561b78b..3b416a2 100644 --- a/src/include/inflatelib.h +++ b/src/include/inflatelib.h @@ -156,11 +156,11 @@ extern "C" */ INFLATELIB_EXPORT const char* INFLATELIB_CALLCONV inflatelib_version(); - /* - * Initializes the stream. The 'user_data', 'alloc', and 'free' members MUST be set prior to the init call and MUST - * NOT be changed after the init call completes. This function returns one of the status values specified above. - */ - #define inflatelib_init(stream) inflatelib_init_(stream, INFLATELIB_VERSION_STRING) +/* + * Initializes the stream. The 'user_data', 'alloc', and 'free' members MUST be set prior to the init call and MUST NOT + * be changed after the init call completes. This function returns one of the status values specified above. + */ +#define inflatelib_init(stream) inflatelib_init_(stream, INFLATELIB_VERSION_STRING) /* * Backing implementation of 'inflatelib_init' that verififes the major version of this header matches the major diff --git a/test/cpp/InflateTests.cpp b/test/cpp/InflateTests.cpp index 6d1ba92..9fcc10f 100644 --- a/test/cpp/InflateTests.cpp +++ b/test/cpp/InflateTests.cpp @@ -293,8 +293,8 @@ TEST_CASE("InflateErrors", "[inflate]") checkMajorVersionMismatch("." INFLATELIB_VERSION_STRING); // Leading dot checkMajorVersionMismatch(" " INFLATELIB_VERSION_STRING); // Leading space checkMajorVersionMismatch("0" INFLATELIB_VERSION_STRING); // Leading zero - checkMajorVersionMismatch(MAJOR_VERSION_STRING "0.0.0"); // Zero after the correct major version - checkMajorVersionMismatch(MAJOR_VERSION_STRING); // Only major version; not a supported form + checkMajorVersionMismatch(MAJOR_VERSION_STRING "0.0.0"); // Zero after the correct major version + checkMajorVersionMismatch(MAJOR_VERSION_STRING); // Only major version; not a supported form // Different major version - library is older checkMajorVersionMismatch(MAKE_VERSION_STRING("9", INFLATELIB_VERSION_MINOR, INFLATELIB_VERSION_PATCH)); // TODO: Once we reach a 1.0 release, we can test when the library is newer @@ -317,8 +317,8 @@ TEST_CASE("InflateInitDifferentMinorVersion", "[inflate]") inflatelib_destroy(&stream); }; - checkInit(INFLATELIB_VERSION_STRING); // Same version, obvious success - checkInit(MAKE_VERSION_STRING(INFLATELIB_VERSION_MAJOR, 0, 0)); // Library is (likely) newer + checkInit(INFLATELIB_VERSION_STRING); // Same version, obvious success + checkInit(MAKE_VERSION_STRING(INFLATELIB_VERSION_MAJOR, 0, 0)); // Library is (likely) newer checkInit(MAKE_VERSION_STRING(INFLATELIB_VERSION_MAJOR, 999, 999)); // Library is (likely) older } From 591fefba32773a8561af76e42d8e4e08b20b05e9 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Fri, 21 Aug 2026 12:26:36 -0700 Subject: [PATCH 3/7] Version bump --- src/include/inflatelib.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/inflatelib.h b/src/include/inflatelib.h index 3b416a2..49b3dfe 100644 --- a/src/include/inflatelib.h +++ b/src/include/inflatelib.h @@ -48,9 +48,9 @@ extern "C" { #endif -#define INFLATELIB_VERSION_STRING "0.2.0" +#define INFLATELIB_VERSION_STRING "0.3.0" #define INFLATELIB_VERSION_MAJOR 0 -#define INFLATELIB_VERSION_MINOR 2 +#define INFLATELIB_VERSION_MINOR 3 #define INFLATELIB_VERSION_PATCH 0 typedef void* (*inflatelib_alloc)(void* userData, size_t bytes, size_t alignment); From 2b7ec7ae6522526afc84c4c337eb5290a336a4e6 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Fri, 21 Aug 2026 12:47:06 -0700 Subject: [PATCH 4/7] Silly C rules --- src/include/inflatelib.h | 2 +- src/lib/inflate.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/inflatelib.h b/src/include/inflatelib.h index 49b3dfe..037c95d 100644 --- a/src/include/inflatelib.h +++ b/src/include/inflatelib.h @@ -154,7 +154,7 @@ extern "C" * otherwise the two are incompatible (struct size, arrangement, etc. may differ). The 'inflatelib_init' macro does * this for you by passing 'INFLATELIB_VERSION_STRING' to 'inflatelib_init_'. */ - INFLATELIB_EXPORT const char* INFLATELIB_CALLCONV inflatelib_version(); + INFLATELIB_EXPORT const char* INFLATELIB_CALLCONV inflatelib_version(void); /* * Initializes the stream. The 'user_data', 'alloc', and 'free' members MUST be set prior to the init call and MUST NOT diff --git a/src/lib/inflate.c b/src/lib/inflate.c index bf23051..effb0f4 100644 --- a/src/lib/inflate.c +++ b/src/lib/inflate.c @@ -14,7 +14,7 @@ #include "internal.h" -const char* INFLATELIB_CALLCONV inflatelib_version() +const char* INFLATELIB_CALLCONV inflatelib_version(void) { return INFLATELIB_VERSION_STRING; } From 166e0f223deae9746d3ed99934b8f0da8393b54d Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Fri, 21 Aug 2026 15:17:57 -0700 Subject: [PATCH 5/7] Switch to custom loop --- src/lib/inflate.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lib/inflate.c b/src/lib/inflate.c index effb0f4..1b5e32c 100644 --- a/src/lib/inflate.c +++ b/src/lib/inflate.c @@ -48,17 +48,22 @@ int INFLATELIB_CALLCONV inflatelib_init_(inflatelib_stream* stream, const char* { int result; inflatelib_state* state; - const char* verPeriod; - const char expectedMajorVersion[] = INFLATELIB_VERSION_MAJOR_STRING; + const char* localVer = INFLATELIB_VERSION_MAJOR_STRING; /* If the major versions don't match, make no assumptions about the layout of 'inflatelib_stream' as it may not be safe to write to. This failure is communicated ONLY through the return value. */ - verPeriod = strchr(version, '.'); - if (!verPeriod || ((verPeriod - version) != (sizeof(expectedMajorVersion) - 1)) || - strncmp(version, expectedMajorVersion, verPeriod - version) != 0) + for (size_t i = 0; ; ++i) { - errno = EINVAL; - return INFLATELIB_ERROR_VERSION; + if (version[i] != localVer[i]) + { + errno = EINVAL; + return INFLATELIB_ERROR_VERSION; + } + + if (version[i] == '.') + { + break; + } } /* Start with no error message, in case it was set before (or contains uninitialized memory) */ From e16c6478df126c522897670ac7de0ac508ea32f4 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Fri, 21 Aug 2026 15:19:16 -0700 Subject: [PATCH 6/7] Woops, committed the wrong thing --- src/lib/inflate.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib/inflate.c b/src/lib/inflate.c index 1b5e32c..051f5c2 100644 --- a/src/lib/inflate.c +++ b/src/lib/inflate.c @@ -40,15 +40,11 @@ static void inflatelib_default_free(void* unusedUserData, void* ptr, size_t byte free(ptr); } -#define STRINGIFY_(x) #x -#define STRINGIFY(x) STRINGIFY_(x) -#define INFLATELIB_VERSION_MAJOR_STRING STRINGIFY(INFLATELIB_VERSION_MAJOR) - int INFLATELIB_CALLCONV inflatelib_init_(inflatelib_stream* stream, const char* version) { int result; inflatelib_state* state; - const char* localVer = INFLATELIB_VERSION_MAJOR_STRING; + const char* localVer = INFLATELIB_VERSION_STRING; /* If the major versions don't match, make no assumptions about the layout of 'inflatelib_stream' as it may not be safe to write to. This failure is communicated ONLY through the return value. */ From c9300981d62e313486a354c485f93794e29878b9 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Fri, 21 Aug 2026 15:23:54 -0700 Subject: [PATCH 7/7] Formatting --- src/lib/inflate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/inflate.c b/src/lib/inflate.c index 051f5c2..8efff84 100644 --- a/src/lib/inflate.c +++ b/src/lib/inflate.c @@ -48,7 +48,7 @@ int INFLATELIB_CALLCONV inflatelib_init_(inflatelib_stream* stream, const char* /* If the major versions don't match, make no assumptions about the layout of 'inflatelib_stream' as it may not be safe to write to. This failure is communicated ONLY through the return value. */ - for (size_t i = 0; ; ++i) + for (size_t i = 0;; ++i) { if (version[i] != localVer[i]) {