diff --git a/CHANGES.md b/CHANGES.md index 351fb7985..a5c824c1a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,11 @@ ##### Breaking Changes :mega: - `LoadedRasterOverlayImage` now has a single `errorList` property instead of separate `errors` and `warnings` properties. +- Renamed `CesiumUtility/Gunzip.h` to `CesiumUtility/Gzip.h`. + +##### Additions :tada: + +- Added `CesiumUtility::gzip`. ##### Fixes :wrench: diff --git a/CesiumAsync/src/GunzipAssetAccessor.cpp b/CesiumAsync/src/GunzipAssetAccessor.cpp index 472de869f..6a8a28454 100644 --- a/CesiumAsync/src/GunzipAssetAccessor.cpp +++ b/CesiumAsync/src/GunzipAssetAccessor.cpp @@ -12,7 +12,7 @@ class GunzippedAssetResponse : public IAssetResponse { public: GunzippedAssetResponse(const IAssetResponse* pOther) noexcept : _pAssetResponse{pOther} { - this->_dataValid = CesiumUtility::Gzip::gunzip( + this->_dataValid = CesiumUtility::gunzip( this->_pAssetResponse->data(), this->_gunzippedData); } @@ -70,7 +70,7 @@ Future> gunzipIfNeeded( const AsyncSystem& asyncSystem, std::shared_ptr&& pCompletedRequest) { const IAssetResponse* pResponse = pCompletedRequest->response(); - if (pResponse && CesiumUtility::Gzip::isGzip(pResponse->data())) { + if (pResponse && CesiumUtility::isGzip(pResponse->data())) { return asyncSystem.runInWorkerThread( [pCompletedRequest = std::move( pCompletedRequest)]() mutable -> std::shared_ptr { diff --git a/CesiumUtility/CMakeLists.txt b/CesiumUtility/CMakeLists.txt index a03cc60c9..3baea8e1d 100644 --- a/CesiumUtility/CMakeLists.txt +++ b/CesiumUtility/CMakeLists.txt @@ -12,6 +12,7 @@ set_target_properties(CesiumUtility PROPERTIES TEST_SOURCES "${CESIUM_UTILITY_TEST_SOURCES}" TEST_HEADERS "${CESIUM_UTILITY_TEST_HEADERS}" + TEST_DATA_DIR ${CMAKE_CURRENT_LIST_DIR}/test/data ) set_target_properties(CesiumUtility diff --git a/CesiumUtility/include/CesiumUtility/Gzip.h b/CesiumUtility/include/CesiumUtility/Gzip.h index 1f39f5297..cd911578b 100644 --- a/CesiumUtility/include/CesiumUtility/Gzip.h +++ b/CesiumUtility/include/CesiumUtility/Gzip.h @@ -5,43 +5,41 @@ namespace CesiumUtility { -struct Gzip { - /** - * @brief Checks whether the data is gzipped. - * - * @param data The data. - * - * @returns Whether the data is gzipped - */ - static bool isGzip(const gsl::span& data); +/** + * @brief Checks whether the data is gzipped. + * + * @param data The data. + * + * @returns Whether the data is gzipped + */ +bool isGzip(const gsl::span& data); - /** - * @brief Gzips data. - * - * If successful, it will return true and the result will be in the - * provided vector. - * - * @param data The data to gzip. - * @param out The gzipped data. - * - * @returns True if successful, false otherwise. - */ - static bool - gzip(const gsl::span& data, std::vector& out); +/** + * @brief Gzips data. + * + * If successful, it will return true and the result will be in the + * provided vector. + * + * @param data The data to gzip. + * @param out The gzipped data. + * + * @returns True if successful, false otherwise. + */ +bool gzip(const gsl::span& data, std::vector& out); - /** - * @brief Gunzips data. - * - * If successful, it will return true and the result will be in the - * provided vector. - * - * @param data The data to gunzip. - * @param out The gunzipped data. - * - * @returns True if successful, false otherwise. - */ - static bool - gunzip(const gsl::span& data, std::vector& out); -}; +/** + * @brief Gunzips data. + * + * If successful, it will return true and the result will be in the + * provided vector. + * + * @param data The data to gunzip. + * @param out The gunzipped data. + * + * @returns True if successful, false otherwise. + */ +bool gunzip( + const gsl::span& data, + std::vector& out); } // namespace CesiumUtility diff --git a/CesiumUtility/src/Gzip.cpp b/CesiumUtility/src/Gzip.cpp index ed128a7b2..269ff76c9 100644 --- a/CesiumUtility/src/Gzip.cpp +++ b/CesiumUtility/src/Gzip.cpp @@ -1,6 +1,8 @@ #include "CesiumUtility/Gzip.h" -#define ZLIB_CONST -#include "zlib-ng.h" + +#include "CesiumUtility/Assert.h" + +#include #include @@ -10,16 +12,14 @@ namespace { constexpr unsigned int ChunkSize = 65536; } -/*static*/ bool Gzip::isGzip(const gsl::span& data) { +bool isGzip(const gsl::span& data) { if (data.size() < 3) { return false; } return data[0] == std::byte{31} && data[1] == std::byte{139}; } -/*static*/ bool Gzip::gzip( - const gsl::span& data, - std::vector& out) { +bool gzip(const gsl::span& data, std::vector& out) { int ret; unsigned int index = 0; zng_stream strm; @@ -44,13 +44,16 @@ constexpr unsigned int ChunkSize = 65536; strm.next_out = reinterpret_cast(&out[index]); strm.avail_out = ChunkSize; ret = zng_deflate(&strm, Z_NO_FLUSH); - switch (ret) { - case Z_NEED_DICT: - case Z_DATA_ERROR: - case Z_MEM_ERROR: - zng_deflateEnd(&strm); - return false; - } + CESIUM_ASSERT(ret != Z_STREAM_ERROR); + index += ChunkSize - strm.avail_out; + } while (strm.avail_in != 0); + + do { + out.resize(index + ChunkSize); + strm.next_out = reinterpret_cast(&out[index]); + strm.avail_out = ChunkSize; + ret = zng_deflate(&strm, Z_FINISH); + CESIUM_ASSERT(ret != Z_STREAM_ERROR); index += ChunkSize - strm.avail_out; } while (ret != Z_STREAM_END); @@ -59,7 +62,7 @@ constexpr unsigned int ChunkSize = 65536; return true; } -/*static*/ bool Gzip::gunzip( +bool gunzip( const gsl::span& data, std::vector& out) { int ret; @@ -80,9 +83,11 @@ constexpr unsigned int ChunkSize = 65536; strm.next_out = reinterpret_cast(&out[index]); strm.avail_out = ChunkSize; ret = zng_inflate(&strm, Z_NO_FLUSH); + CESIUM_ASSERT(ret != Z_STREAM_ERROR); switch (ret) { case Z_NEED_DICT: case Z_DATA_ERROR: + case Z_BUF_ERROR: case Z_MEM_ERROR: zng_inflateEnd(&strm); return false; diff --git a/CesiumUtility/test/TestGzip.cpp b/CesiumUtility/test/TestGzip.cpp new file mode 100644 index 000000000..7b27ea895 --- /dev/null +++ b/CesiumUtility/test/TestGzip.cpp @@ -0,0 +1,79 @@ +#include +#include + +#include + +#include + +using namespace CesiumUtility; + +namespace { +std::filesystem::path testDataDir = CesiumUtility_TEST_DATA_DIR; +std::filesystem::path compressedDataPath = + testDataDir / "Gzip" / "CesiumMilkTruck.png.gz"; +std::filesystem::path uncompressedDataPath = + testDataDir / "Gzip" / "CesiumMilkTruck.png"; +std::filesystem::path invalidCompressedDataPath = + testDataDir / "Gzip" / "CesiumMilkTruck.png.gz.invalid"; + +} // namespace + +TEST_CASE("isGzip") { + SECTION("returns true if data is gzipped") { + std::vector compressedData = readFile(compressedDataPath); + CHECK(isGzip(compressedData)); + } + + SECTION("returns false if data is not gzipped") { + std::vector uncompressedData = readFile(uncompressedDataPath); + CHECK(!isGzip(uncompressedData)); + } +} + +TEST_CASE("gzip") { + SECTION("gzips data") { + std::vector uncompressedData = readFile(uncompressedDataPath); + std::vector compressedData; + bool result = gzip(uncompressedData, compressedData); + REQUIRE(result); + CHECK(compressedData.size() < uncompressedData.size()); + CHECK(isGzip(compressedData)); + + std::vector decompressedData; + result = gunzip(compressedData, decompressedData); + REQUIRE(result); + CHECK(decompressedData == uncompressedData); + } +} + +TEST_CASE("gunzip") { + SECTION("gunzips data") { + std::vector compressedData = readFile(compressedDataPath); + std::vector uncompressedData = readFile(uncompressedDataPath); + + std::vector decompressedData; + bool result = gunzip(compressedData, decompressedData); + REQUIRE(result); + CHECK(decompressedData == uncompressedData); + } + + SECTION("returns false for invalid header") { + std::vector invalidCompressedData = + readFile(uncompressedDataPath); + + std::vector decompressedData; + bool result = gunzip(invalidCompressedData, decompressedData); + + CHECK(!result); + } + + SECTION("returns false for truncated data") { + std::vector invalidCompressedData = + readFile(invalidCompressedDataPath); + + std::vector decompressedData; + bool result = gunzip(invalidCompressedData, decompressedData); + + CHECK(!result); + } +} diff --git a/CesiumUtility/test/data/Gzip/CesiumMilkTruck.png b/CesiumUtility/test/data/Gzip/CesiumMilkTruck.png new file mode 100644 index 000000000..ff6d78c35 Binary files /dev/null and b/CesiumUtility/test/data/Gzip/CesiumMilkTruck.png differ diff --git a/CesiumUtility/test/data/Gzip/CesiumMilkTruck.png.gz b/CesiumUtility/test/data/Gzip/CesiumMilkTruck.png.gz new file mode 100644 index 000000000..c426b90fc Binary files /dev/null and b/CesiumUtility/test/data/Gzip/CesiumMilkTruck.png.gz differ diff --git a/CesiumUtility/test/data/Gzip/CesiumMilkTruck.png.gz.invalid b/CesiumUtility/test/data/Gzip/CesiumMilkTruck.png.gz.invalid new file mode 100644 index 000000000..65c8933c8 Binary files /dev/null and b/CesiumUtility/test/data/Gzip/CesiumMilkTruck.png.gz.invalid differ