@@ -293,6 +293,7 @@ static void eZlibCompressDecompressHelper(
293293 &bufferFactory,
294294 algorithm,
295295 compressed,
296+ 0 , // no output cap
296297 &error,
297298 bmqtst::TestHelperUtil::allocator ());
298299 *decompressionTime = bsls::TimeUtil::getTimer () - startTime;
@@ -336,6 +337,7 @@ eZlibCompressDecompressHelper(bsls::Types::Int64* compressionTime,
336337 &decompressed,
337338 &bufferFactory,
338339 compressed,
340+ 0 , // no output cap
339341 &error,
340342 bmqtst::TestHelperUtil::allocator ());
341343 *decompressionTime = bsls::TimeUtil::getTimer () - startTime;
@@ -377,6 +379,7 @@ static void eZlibCompressionRatioHelper(bsls::Types::Int64* inputSize,
377379 &decompressed,
378380 &bufferFactory,
379381 compressed,
382+ 0 , // no output cap
380383 &error,
381384 bmqtst::TestHelperUtil::allocator ());
382385 BMQTST_ASSERT_EQ (rc, 0 );
@@ -544,6 +547,7 @@ static void test1_breathingTest()
544547 &decompressed,
545548 &bufferFactory,
546549 compressed,
550+ 0 , // no output cap
547551 &error,
548552 bmqtst::TestHelperUtil::allocator ());
549553 BMQTST_ASSERT_EQ (rc, 0 );
@@ -613,6 +617,7 @@ static void test1_breathingTest()
613617 &decompressed,
614618 &bufferFactory,
615619 compressed,
620+ 0 , // no output cap
616621 &error,
617622 bmqtst::TestHelperUtil::allocator ());
618623 BMQTST_ASSERT_EQ (rc, 0 );
@@ -692,6 +697,7 @@ static void test2_compression_cluster_message()
692697 &decompressed,
693698 &bufferFactory,
694699 compressed,
700+ 0 , // no output cap
695701 &error,
696702 bmqtst::TestHelperUtil::allocator ());
697703 BMQTST_ASSERT_EQ (rc, 0 );
@@ -752,6 +758,116 @@ static void test3_compression_decompression_none()
752758 }
753759}
754760
761+ static void test4_decompressionSizeLimit ()
762+ // ------------------------------------------------------------------------
763+ // DECOMPRESSION OUTPUT SIZE LIMIT
764+ //
765+ // Concerns:
766+ // A highly-compressible input must not be allowed to expand without
767+ // bound. When a maximum output size is supplied, decompression must fail
768+ // closed once the accumulated output would exceed the cap, rather than
769+ // continuing to allocate memory.
770+ //
771+ // Plan:
772+ // - Compress a large run of zero bytes (which zlib shrinks dramatically).
773+ // - Decompress with no cap and confirm the full round-trip (control).
774+ // - Decompress with a cap below the true output size and confirm it fails
775+ // with a non-zero code while producing output bounded near the cap (not
776+ // the full expansion).
777+ // - Decompress with a cap above the true output size and confirm success.
778+ //
779+ // Testing:
780+ // bmqp::Compression::decompress with a non-zero maxOutputSize
781+ // ------------------------------------------------------------------------
782+ {
783+ bmqtst::TestHelper::printTestName (" DECOMPRESSION SIZE LIMIT TEST" );
784+
785+ // Use small blob buffers so the enforced cap is tight (the overrun beyond
786+ // the cap is bounded by a single buffer).
787+ bdlbb::PooledBlobBufferFactory bufferFactory (
788+ 1024 ,
789+ bmqtst::TestHelperUtil::allocator ());
790+
791+ bmqu::MemOutStream error (bmqtst::TestHelperUtil::allocator ());
792+
793+ // Build a large, highly-compressible input (all zero bytes) that expands
794+ // far beyond the cap we will impose.
795+ const int k_INPUT_SIZE = 8 * 1024 * 1024 ; // 8 MB
796+ bsl::string zeros (k_INPUT_SIZE, ' \0 ' , bmqtst::TestHelperUtil::allocator ());
797+
798+ bdlbb::Blob input (&bufferFactory, bmqtst::TestHelperUtil::allocator ());
799+ bdlbb::BlobUtil::append (&input, zeros.data (), k_INPUT_SIZE);
800+
801+ bdlbb::Blob compressed (&bufferFactory,
802+ bmqtst::TestHelperUtil::allocator ());
803+ int rc = bmqp::Compression::compress (
804+ &compressed,
805+ &bufferFactory,
806+ bmqt::CompressionAlgorithmType::e_ZLIB,
807+ input,
808+ &error,
809+ bmqtst::TestHelperUtil::allocator ());
810+ BMQTST_ASSERT_EQ (rc, 0 );
811+ // Highly-compressible: compressed form is a tiny fraction of the input.
812+ BMQTST_ASSERT_LT (compressed.length (), k_INPUT_SIZE);
813+
814+ {
815+ PVV (" Control: no cap decompresses fully" );
816+ bdlbb::Blob decompressed (&bufferFactory,
817+ bmqtst::TestHelperUtil::allocator ());
818+ rc = bmqp::Compression::decompress (
819+ &decompressed,
820+ &bufferFactory,
821+ bmqt::CompressionAlgorithmType::e_ZLIB,
822+ compressed,
823+ 0 , // no cap
824+ &error,
825+ bmqtst::TestHelperUtil::allocator ());
826+ BMQTST_ASSERT_EQ (rc, 0 );
827+ BMQTST_ASSERT_EQ (decompressed.length (), k_INPUT_SIZE);
828+ }
829+
830+ {
831+ PVV (" Cap below decompressed size fails closed" );
832+ const bsls::Types::Uint64 k_CAP = 1024 * 1024 ; // 1 MB < 8 MB
833+ bdlbb::Blob decompressed (&bufferFactory,
834+ bmqtst::TestHelperUtil::allocator ());
835+ rc = bmqp::Compression::decompress (
836+ &decompressed,
837+ &bufferFactory,
838+ bmqt::CompressionAlgorithmType::e_ZLIB,
839+ compressed,
840+ k_CAP,
841+ &error,
842+ bmqtst::TestHelperUtil::allocator ());
843+ // Fails with a non-zero code ...
844+ BMQTST_ASSERT_NE (rc, 0 );
845+ // ... and stops well short of fully expanding the input, bounded
846+ // near the cap rather than the full 8 MB.
847+ BMQTST_ASSERT_LT (decompressed.length (), k_INPUT_SIZE);
848+ BMQTST_ASSERT_LE (
849+ static_cast <bsls::Types::Uint64>(decompressed.length ()),
850+ k_CAP + 1024 );
851+ }
852+
853+ {
854+ PVV (" Cap above decompressed size succeeds" );
855+ const bsls::Types::Uint64 k_CAP = 64 * 1024 * 1024 ; // 64 MB > 8 MB
856+ bdlbb::Blob decompressed (&bufferFactory,
857+ bmqtst::TestHelperUtil::allocator ());
858+ rc = bmqp::Compression::decompress (
859+ &decompressed,
860+ &bufferFactory,
861+ bmqt::CompressionAlgorithmType::e_ZLIB,
862+ compressed,
863+ k_CAP,
864+ &error,
865+ bmqtst::TestHelperUtil::allocator ());
866+ BMQTST_ASSERT_EQ (rc, 0 );
867+ BMQTST_ASSERT_EQ (decompressed.length (), k_INPUT_SIZE);
868+ }
869+ }
870+
755871// ============================================================================
756872// PERFORMANCE TESTS
757873// ----------------------------------------------------------------------------
@@ -1113,6 +1229,7 @@ int main(int argc, char* argv[])
11131229 case 1 : test1_breathingTest (); break ;
11141230 case 2 : test2_compression_cluster_message (); break ;
11151231 case 3 : test3_compression_decompression_none (); break ;
1232+ case 4 : test4_decompressionSizeLimit (); break ;
11161233 case -1 :
11171234 BMQTST_BENCHMARK_WITH_ARGS (
11181235 testN1_performanceCompressionDecompressionDefault,
0 commit comments