Files
opennurbs/opennurbs_compress.cpp
2024-02-15 08:00:36 -08:00

719 lines
19 KiB
C++

//
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#include "opennurbs.h"
#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif
#include "opennurbs_zlib.h"
struct ON_ZlibImplementation
{
z_stream m_strm;
unsigned char m_zlib_out_buffer[16384];
};
ON_CompressStream::ON_CompressStream()
: m_out_callback_function(0)
, m_out_callback_context(0)
, m_in_size(0)
, m_out_size(0)
, m_in_crc(0)
, m_out_crc(0)
, m_implementation(0)
, m_reserved(0)
{}
ON_CompressStream::~ON_CompressStream()
{
if ( 0 != m_implementation )
{
onfree(m_implementation);
m_implementation = 0;
}
}
void ON_CompressStream::ErrorHandler()
{
// place holder for error handing
ON_ERROR("ON_CompressStream error");
}
bool ON_CompressStream::Begin()
{
if ( 0 != m_implementation )
{
onfree(m_implementation);
m_implementation = 0;
}
// zero these because the same instance of an
// ON_CompressStream class may be used multiple times.
m_in_size = 0;
m_out_size = 0;
m_in_crc = 0;
m_out_crc = 0;
struct ON_ZlibImplementation* imp = (struct ON_ZlibImplementation*)onmalloc(sizeof(*imp));
memset(&imp->m_strm,0,sizeof(imp->m_strm));
if ( Z_OK != deflateInit( &imp->m_strm, Z_BEST_COMPRESSION ) )
{
onfree(imp);
return false;
}
m_implementation = imp;
return true;
}
bool ON_CompressStream::In( ON__UINT64 size, const void* uncompressed_buffer )
{
if ( size <= 0 )
return true;
if ( 0 == m_implementation )
{
ErrorHandler();
return false;
}
if ( 0 == uncompressed_buffer )
{
ErrorHandler();
return false;
}
struct ON_ZlibImplementation* imp = (struct ON_ZlibImplementation*)m_implementation;
z_stream& strm = imp->m_strm;
if ( 0 != strm.avail_in || 0 != strm.next_in )
{
// strm.avail_in is always zero when we leave an ON_CompressStream function.
ErrorHandler();
return false;
}
const ON__UINT32 sizeof_out_buffer = (ON__UINT32)(sizeof(imp->m_zlib_out_buffer));
void* out_buffer = imp->m_zlib_out_buffer;
int zrc = Z_OK;
const ON__UINT64 max_sz = 0x7FFFFFF0;
bool rc = false;
ON__UINT32 deflate_output_count;
// counter prevents infinte loops if there is a bug in zlib return codes.
for( int counter = 512; counter > 0; counter-- )
{
// Call zlib's deflate function. It can either process
// more input from m_zlib.strm.next_in[], create more
// compressed output in m_zlib.strm.next_out[], or do both.
// provide storage for compressed stream output
strm.next_out = (z_Bytef*)out_buffer;
strm.avail_out = sizeof_out_buffer;
if ( strm.avail_in <= 0 )
{
if ( size <= 0 )
{
// finshed with uncompressed input
break;
}
// submit a portion of uncompressed_buffer to zlib
ON__UINT64 sz = (size > max_sz) ? max_sz : size;
m_in_size += sz;
m_in_crc = ON_CRC32(m_in_crc,(size_t)sz,uncompressed_buffer); // (size_t) cast is safe because sz <= max_sz = 0x7FFFFFF0
strm.next_in = (z_Bytef*)uncompressed_buffer;
strm.avail_in = (ON__UINT32)sz;
uncompressed_buffer = ((const unsigned char*)uncompressed_buffer) + sz;
size -= sz;
counter = 512; // added input - reset the counter that detects stalls
}
// calculate compression
ON__UINT32 avail_in0 = strm.avail_in;
ON__UINT32 avail_out0 = strm.avail_out;
zrc = z_deflate( &strm, Z_NO_FLUSH );
if ( zrc < 0 )
{
// Something went haywire - bail out.
ErrorHandler();
rc = false;
break;
}
if ( strm.avail_in < avail_in0 || strm.avail_out > avail_out0 )
{
// zlib did something
rc = true;
}
deflate_output_count = sizeof_out_buffer - strm.avail_out;
if ( deflate_output_count > 0 )
{
// The last call to deflate created compressed output.
// Send the output to compressed stream handler.
// Calculate the updated crc and size before we call
// the output handler because someday sombody will
// decide it's a good idea to modify the values
// in the buffer argument.
ON__UINT32 out_crc1 = ON_CRC32( m_out_crc, deflate_output_count, out_buffer);
ON__UINT64 out_size1 = m_out_size + deflate_output_count;
rc = (0 != m_out_callback_function)
? m_out_callback_function( m_out_callback_context, deflate_output_count, out_buffer )
: Out( m_out_callback_context, deflate_output_count, out_buffer );
if ( !rc )
break;
// Update compressed stream crc and size
m_out_crc = out_crc1;
m_out_size = out_size1;
counter = 512; // created output - reset counter that detects stalls
}
if ( size <= 0 && strm.avail_in <= 0 )
{
// no input left
break;
}
}
strm.avail_in = 0;
strm.next_in = 0;
strm.next_out = 0;
strm.avail_out = 0;
return rc;
}
bool ON_CompressStream::End()
{
if ( 0 == m_implementation )
{
ErrorHandler();
return false;
}
struct ON_ZlibImplementation* imp = (struct ON_ZlibImplementation*)m_implementation;
z_stream& strm = imp->m_strm;
if ( 0 != strm.avail_in || 0 != strm.next_in )
{
// strm.avail_in is always zero when we leave an ON_CompressStream function.
ErrorHandler();
return false;
}
const ON__UINT32 sizeof_out_buffer = (ON__UINT32)(sizeof(imp->m_zlib_out_buffer));
void* out_buffer = imp->m_zlib_out_buffer;
int zrc = Z_OK;
bool rc = false;
ON__UINT32 deflate_output_count;
// counter prevents infinte loops if there is a bug in zlib return codes.
for( int counter = 512; counter > 0; counter-- )
{
// provide storage for compressed stream output
strm.avail_in = 0;
strm.next_in = 0;
strm.next_out = (z_Bytef*)out_buffer;
strm.avail_out = sizeof_out_buffer;
// finish compression calculation
zrc = z_deflate( &strm, Z_FINISH );
if ( zrc < 0 )
{
// Something went haywire - bail out.
ErrorHandler();
rc = false;
break;
}
deflate_output_count = sizeof_out_buffer - strm.avail_out;
if ( deflate_output_count > 0 )
{
// The last call to deflate created compressed output.
// Send the output to compressed stream handler.
// Calculate the updated crc and size before we call
// the output handler because someday sombody will
// decide it's a good idea to modify the values
// in the buffer argument.
ON__UINT32 compressed_crc1 = ON_CRC32( m_out_crc, deflate_output_count, out_buffer);
ON__UINT64 compressed_size1 = m_out_size + ((ON__UINT64)deflate_output_count);
rc = (0 != m_out_callback_function)
? m_out_callback_function( m_out_callback_context, deflate_output_count, out_buffer )
: Out( m_out_callback_context, deflate_output_count, out_buffer );
if ( !rc )
break;
// Update compressed stream crc and size
m_out_crc = compressed_crc1;
m_out_size = compressed_size1;
counter = 512; // created output - reset counter that detects stalls
}
if ( Z_STREAM_END == zrc )
{
// no input left, all pending compressing is finished,
// and all compressed output has been returned.
rc = true;
break;
}
}
strm.avail_in = 0;
strm.next_in = 0;
strm.next_out = 0;
strm.avail_out = 0;
deflateEnd(&strm);
onfree(m_implementation);
m_implementation = 0;
return rc;
}
bool ON_CompressStream::Out( void*, ON__UINT32, const void* )
{
// default compressed stream handler does nothing.
return true;
}
bool ON_CompressStream::SetCallback(
ON_StreamCallbackFunction out_callback_function,
void* out_callback_context
)
{
m_out_callback_function = out_callback_function;
m_out_callback_context = out_callback_context;
return true;
}
ON_StreamCallbackFunction ON_CompressStream::CallbackFunction() const
{
return m_out_callback_function;
}
void* ON_CompressStream::CallbackContext() const
{
return m_out_callback_context;
}
ON__UINT64 ON_CompressStream::InSize() const
{
return m_in_size;
}
ON__UINT64 ON_CompressStream::OutSize() const
{
return m_out_size;
}
ON__UINT32 ON_CompressStream::InCRC() const
{
return m_in_crc;
}
ON__UINT32 ON_CompressStream::OutCRC() const
{
return m_out_crc;
}
///////////////////////////////////////////////////////////////////////////////////////////
ON_UncompressStream::ON_UncompressStream()
: m_out_callback_function(0)
, m_out_callback_context(0)
, m_in_size(0)
, m_out_size(0)
, m_in_crc(0)
, m_out_crc(0)
, m_implementation(0)
, m_reserved(0)
{}
ON_UncompressStream::~ON_UncompressStream()
{
if ( 0 != m_implementation )
{
onfree(m_implementation);
m_implementation = 0;
}
}
void ON_UncompressStream::ErrorHandler()
{
// place holder for error handing
ON_ERROR("ON_UncompressStream error");
}
bool ON_UncompressStream::Begin()
{
if ( 0 != m_implementation )
{
onfree(m_implementation);
m_implementation = 0;
}
// zero these because the same instance of an
// ON_UncompressStream class may be used multiple times.
m_in_size = 0;
m_out_size = 0;
m_in_crc = 0;
m_out_crc = 0;
struct ON_ZlibImplementation* imp = (struct ON_ZlibImplementation*)onmalloc(sizeof(*imp));
memset(&imp->m_strm,0,sizeof(imp->m_strm));
if ( Z_OK != inflateInit( &imp->m_strm ) )
{
onfree(imp);
return false;
}
m_implementation = imp;
return true;
}
bool ON_UncompressStream::In( ON__UINT64 size, const void* compressed_buffer )
{
if ( size <= 0 )
return true;
if ( 0 == m_implementation )
{
ErrorHandler();
return false;
}
if ( 0 == compressed_buffer )
{
ErrorHandler();
return false;
}
struct ON_ZlibImplementation* imp = (struct ON_ZlibImplementation*)m_implementation;
z_stream& strm = imp->m_strm;
if ( 0 != strm.avail_in || 0 != strm.next_in )
{
// strm.avail_in is always zero when we leave an ON_UncompressStream function.
ErrorHandler();
return false;
}
const ON__UINT32 sizeof_out_buffer = (ON__UINT32)(sizeof(imp->m_zlib_out_buffer));
void* out_buffer = imp->m_zlib_out_buffer;
int zrc = Z_OK;
const ON__UINT64 max_sz = 0x7FFFFFF0;
bool rc = false;
ON__UINT32 inflate_output_count;
// counter prevents infinte loops if there is a bug in zlib return codes.
for( int counter = 512; counter > 0; counter-- )
{
// Call zlib's inflate function. It can process
// more compressed input from strm.next_in[], create more
// uncompressed output in strm.next_out[], or do both.
// provide storage for uncompressed stream output
strm.next_out = (z_Bytef*)out_buffer;
strm.avail_out = sizeof_out_buffer;
if ( strm.avail_in <= 0 )
{
if ( size <= 0 )
{
// finshed with compressed input
break;
}
// submit a portion of compressed_buffer to zlib
ON__UINT64 sz = (size > max_sz) ? max_sz : size;
m_in_size += sz;
m_in_crc = ON_CRC32(m_in_crc,(size_t)sz,compressed_buffer); // (size_t) cast is safe because sz <= max_sz = 0x7FFFFFF0
strm.next_in = (z_Bytef*)compressed_buffer;
strm.avail_in = (ON__UINT32)sz;
compressed_buffer = ((const unsigned char*)compressed_buffer) + sz;
size -= sz;
counter = 512; // added input - reset the counter that detects stalls
}
// calculate compression
ON__UINT32 avail_in0 = strm.avail_in;
ON__UINT32 avail_out0 = strm.avail_out;
zrc = z_inflate( &strm, Z_NO_FLUSH );
if ( zrc < 0 )
{
// Something went haywire - bail out.
ErrorHandler();
rc = false;
break;
}
if ( strm.avail_in < avail_in0 || strm.avail_out > avail_out0 )
{
// zlib did something
rc = true;
}
inflate_output_count = sizeof_out_buffer - strm.avail_out;
if ( inflate_output_count > 0 )
{
// The last call to inflate created uncompressed output.
// Send the output to the uncompressed stream handler.
// Calculate the updated crc and size before we call
// the output handler because someday sombody will
// decide it's a good idea to modify the values
// in the buffer argument.
ON__UINT32 out_crc1 = ON_CRC32( m_out_crc, inflate_output_count, out_buffer);
ON__UINT64 out_size1 = m_out_size + inflate_output_count;
rc = (0 != m_out_callback_function)
? m_out_callback_function( m_out_callback_context, inflate_output_count, out_buffer )
: Out( m_out_callback_context, inflate_output_count, out_buffer );
if ( !rc )
break;
// Update compressed stream crc and size
m_out_crc = out_crc1;
m_out_size = out_size1;
counter = 512; // created output - reset counter that detects stalls
}
if ( size <= 0 && strm.avail_in <= 0 )
{
// no input left
break;
}
}
strm.avail_in = 0;
strm.next_in = 0;
strm.next_out = 0;
strm.avail_out = 0;
return rc;
}
bool ON_UncompressStream::End()
{
if ( 0 == m_implementation )
{
ErrorHandler();
return false;
}
struct ON_ZlibImplementation* imp = (struct ON_ZlibImplementation*)m_implementation;
z_stream& strm = imp->m_strm;
if ( 0 != strm.avail_in || 0 != strm.next_in )
{
// strm.avail_in is always zero when we leave an ON_UncompressStream function.
ErrorHandler();
return false;
}
const ON__UINT32 sizeof_out_buffer = (ON__UINT32)(sizeof(imp->m_zlib_out_buffer));
void* out_buffer = imp->m_zlib_out_buffer;
int zrc = Z_OK;
bool rc = false;
ON__UINT32 inflate_output_count;
// counter prevents infinte loops if there is a bug in zlib return codes.
for( int counter = 512; counter > 0; counter-- )
{
// provide storage for compressed stream output
strm.avail_in = 0;
strm.next_in = 0;
strm.next_out = (z_Bytef*)out_buffer;
strm.avail_out = sizeof_out_buffer;
// finish compression calculation
zrc = z_inflate( &strm, Z_FINISH );
if ( zrc < 0 )
{
// Something went haywire - bail out.
ErrorHandler();
rc = false;
break;
}
inflate_output_count = sizeof_out_buffer - strm.avail_out;
if ( inflate_output_count > 0 )
{
// The last call to inflate created uncompressed output.
// Send the output to the uncompressed stream handler.
// Calculate the updated crc and size before we call
// the output handler because someday sombody will
// decide it's a good idea to modify the values
// in the buffer argument.
ON__UINT32 out_crc1 = ON_CRC32( m_out_crc, inflate_output_count, out_buffer);
ON__UINT64 out_size1 = m_out_size + inflate_output_count;
rc = (0 != m_out_callback_function)
? m_out_callback_function( m_out_callback_context, inflate_output_count, out_buffer )
: Out( m_out_callback_context, inflate_output_count, out_buffer );
if ( !rc )
break;
// Update compressed stream crc and size
m_out_crc = out_crc1;
m_out_size = out_size1;
counter = 512; // created output - reset counter that detects stalls
}
if ( Z_STREAM_END == zrc )
{
// no input left, all pending compressing is finished,
// and all compressed output has been returned.
rc = true;
break;
}
}
strm.avail_in = 0;
strm.next_in = 0;
strm.next_out = 0;
strm.avail_out = 0;
inflateEnd(&strm);
onfree(m_implementation);
m_implementation = 0;
return rc;
}
bool ON_UncompressStream::Out( void*, ON__UINT32, const void* )
{
// default uncompressed stream handler does nothing.
return true;
}
bool ON_UncompressStream::SetCallback(
ON_StreamCallbackFunction out_callback_function,
void* out_callback_context
)
{
m_out_callback_function = out_callback_function;
m_out_callback_context = out_callback_context;
return true;
}
ON_StreamCallbackFunction ON_UncompressStream::CallbackFunction() const
{
return m_out_callback_function;
}
void* ON_UncompressStream::CallbackContext() const
{
return m_out_callback_context;
}
ON__UINT64 ON_UncompressStream::InSize() const
{
return m_in_size;
}
ON__UINT64 ON_UncompressStream::OutSize() const
{
return m_out_size;
}
ON__UINT32 ON_UncompressStream::InCRC() const
{
return m_in_crc;
}
ON__UINT32 ON_UncompressStream::OutCRC() const
{
return m_out_crc;
}
class ON_UncompressBuffer_Context
{
public:
ON_UncompressBuffer_Context(
size_t sizeof_uncompressed_buffer,
const void* uncompressed_buffer
)
: m_dst((sizeof_uncompressed_buffer > 0) ? ((unsigned char*)uncompressed_buffer) : nullptr)
, m_dst_end((nullptr != m_dst) ? (m_dst + sizeof_uncompressed_buffer) : nullptr)
{}
unsigned char* m_dst = nullptr;
unsigned char* m_dst_end = nullptr;
static bool Callback(void* context, ON__UINT32 size, const void* buffer)
{
ON_UncompressBuffer_Context* p = (ON_UncompressBuffer_Context*)context;
unsigned char* dst = p->m_dst;
unsigned char* dst_end = p->m_dst_end;
const unsigned char* src = (size > 0) ? ((const unsigned char*)buffer) : nullptr;
const unsigned char* src_end = (nullptr != src) ? (src + size) : nullptr;
while (dst < dst_end && src < src_end)
{
*dst++ = *src++;
}
p->m_dst = dst;
return (src == src_end);
}
private:
ON_UncompressBuffer_Context() = delete;
ON_UncompressBuffer_Context(const ON_UncompressBuffer_Context&) = delete;
ON_UncompressBuffer_Context& operator=(const ON_UncompressBuffer_Context&) = delete;
};
size_t ON_UncompressBuffer(
size_t sizeof_compressed_buffer,
const void* compressed_buffer,
size_t sizeof_uncompressed_buffer,
void* uncompressed_buffer
)
{
unsigned char* dst((sizeof_uncompressed_buffer > 0) ? ((unsigned char*)uncompressed_buffer) : nullptr);
ON_UncompressBuffer_Context context(sizeof_uncompressed_buffer, dst);
ON_UncompressStream unzipper;
bool rc = unzipper.SetCallback(ON_UncompressBuffer_Context::Callback, &context);
if (rc)
{
rc = unzipper.Begin();
if (rc)
{
rc = unzipper.In(sizeof_compressed_buffer, compressed_buffer);
if (!unzipper.End())
rc = false;
}
}
return rc ? (context.m_dst - dst) : 0;
}