Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ZLibSupport.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
8#include "Stroika/Foundation/Execution/FeatureNotSupportedException.h"
9
11
12#if qStroika_HasComponent_zlib
13// SEE http://www.zlib.net/zlib_how.html
14#include <zlib.h>
15#endif
16
17using std::byte;
18
19using namespace Stroika::Foundation;
23using namespace Stroika::Foundation::Debug;
24using namespace Stroika::Foundation::Streams;
25
26#if qStroika_HasComponent_zlib
27namespace Stroika::Foundation::DataExchange::Compression::Private_ {
28 inline void ThrowIfZLibErr_ (int err)
29 {
30 // VERY ROUGH DRAFT - probably need a more specific exception object type
31 if (err != Z_OK) [[unlikely]] {
32 switch (err) {
33 case Z_VERSION_ERROR: {
34 static const Execution::RuntimeErrorException kException_{"ZLIB Z_VERSION_ERROR"sv};
35 Execution::Throw (kException_);
36 }
37 case Z_DATA_ERROR: {
38 static const Execution::RuntimeErrorException kException_{"ZLIB Z_DATA_ERROR"sv};
39 Execution::Throw (kException_);
40 }
41 case Z_STREAM_ERROR: {
42 static const Execution::RuntimeErrorException kException_{"ZLIB Z_STREAM_ERROR"sv};
43 Execution::Throw (kException_);
44 }
45 case Z_ERRNO:
46 Execution::Throw (Execution::RuntimeErrorException{"ZLIB Z_ERRNO (errno={})"_f(errno)});
47 default:
49 }
50 }
51 }
52
53 struct BaseRep_ : public InputStream::IRep<byte> {
54 private:
55 static constexpr size_t CHUNK_ = 16384;
56
57 public:
58 Streams::InputStream::Ptr<byte> fInStream_; // consider wrapping in StreamReader for efficiency sake - maybe unhelpful due to CHUNK logic below
59 z_stream fZStream_{};
60 byte fInBuf_[CHUNK_]; // uninitialized cuz written before read
61 SeekOffsetType _fSeekOffset{};
62 optional<byte> _fNextOutputByte_; // 'cached' next output byte - if not nullopt - magic needed to make AvailableToRead
63 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fThisAssertExternallySynchronized_;
64
65 BaseRep_ (const Streams::InputStream::Ptr<byte>& in)
66 : fInStream_{in}
67 {
68 }
69 virtual ~BaseRep_ () = default;
70 virtual bool IsSeekable () const override
71 {
72 return false; // SHOULD allow seekable IFF src is seekable, but tricky because of internal state in compress/decompress library - not sure how to update/manage
73 }
74 virtual void CloseRead () override
75 {
76 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
77 if (fInStream_ != nullptr) {
78 fInStream_.Close ();
79 }
80 Assert (fInStream_ == nullptr);
81 Ensure (not IsOpenRead ());
82 }
83 virtual bool IsOpenRead () const override
84 {
85 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
86 return fInStream_ != nullptr;
87 }
88 virtual SeekOffsetType GetReadOffset () const override
89 {
90 Require (IsOpenRead ());
91 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
92 return _fSeekOffset;
93 }
94 // return number of bytes definitely copied into intoBuffer, else nullopt on EWOULDBLOCK
95 template <invocable<bool> PROCESS>
96 optional<size_t> PullEnufForDeflate1Byte_ (NoDataAvailableHandling blockFlag, span<byte> intoBuffer, PROCESS processInputZLibFunction)
97 {
98 Assert (_fNextOutputByte_ == nullopt); // already handled
99 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
100 Again:
101 if (blockFlag == NoDataAvailableHandling::eDontBlock and fZStream_.avail_in == 0 and fInStream_.AvailableToRead () == nullopt) {
102 // if non-blocking call, no data pre-available in zstream, and nothing in upstream, NoDataAvailable!
103 // note MAY not be enuf in zbuf to read a full byte of output, but OK - will come back here
104 return nullopt;
105 }
106 if (fZStream_.avail_in == 0) {
107 Assert (Memory::NEltsOf (fInBuf_) < numeric_limits<uInt>::max ());
108 fZStream_.avail_in = static_cast<uInt> (fInStream_.ReadBlocking (span{fInBuf_}).size ()); // blocking read always OK by the time we get here
109 fZStream_.next_in = reinterpret_cast<Bytef*> (begin (fInBuf_));
110 }
111 bool isAtSrcEOF = fZStream_.avail_in == 0;
112
113 ptrdiff_t outBufSize = intoBuffer.size ();
114
115 fZStream_.avail_out = static_cast<uInt> (outBufSize);
116 fZStream_.next_out = reinterpret_cast<Bytef*> (intoBuffer.data ());
117 int ret;
118 switch (ret = processInputZLibFunction (isAtSrcEOF)) {
119 case Z_OK:
120 break;
121 case Z_STREAM_END:
122 break;
123 default:
124 ThrowIfZLibErr_ (ret);
125 }
126 ptrdiff_t pulledOut = outBufSize - fZStream_.avail_out;
127 Assert (pulledOut <= outBufSize);
128 if (pulledOut == 0 and not isAtSrcEOF) {
129 goto Again;
130 }
131 return pulledOut;
132 }
133 template <invocable<bool> PROCESS>
134 optional<size_t> _Available2Read (PROCESS processInputZLibFunction)
135 {
136 Require (IsOpenRead ());
137 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
138 if (_fNextOutputByte_) {
139 return 1;
140 }
141 byte tmp;
142 auto pulledButMustCache = PullEnufForDeflate1Byte_ (NoDataAvailableHandling::eDontBlock, span{&tmp, 1}, processInputZLibFunction);
143 if (pulledButMustCache) {
144 if (*pulledButMustCache == 0) {
145 return 0;
146 }
147 else {
148 Assert (*pulledButMustCache == 1);
149 _fNextOutputByte_ = tmp;
150 return 1;
151 }
152 }
153 else {
154 return nullopt;
155 }
156 }
157 template <invocable<bool> PROCESS>
158 optional<span<byte>> _Read (span<byte> intoBuffer, NoDataAvailableHandling blockFlag, PROCESS processInputZLibFunction)
159 {
160 Require (not intoBuffer.empty ()); // API rule for streams
161 Require (IsOpenRead ());
162 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
163 if (_fNextOutputByte_) {
164 intoBuffer[0] = *_fNextOutputByte_;
165 _fNextOutputByte_.reset ();
166 // OK to just return now, but see if we can be more efficient, and grab a bit more
167 if (intoBuffer.size () > 1) {
168 if (auto o = PullEnufForDeflate1Byte_ (NoDataAvailableHandling::eDontBlock, intoBuffer.subspan (1), processInputZLibFunction)) {
169 size_t pulledOut = *o + 1;
170 _fSeekOffset += pulledOut;
171 return intoBuffer.subspan (0, pulledOut);
172 }
173 }
174 _fSeekOffset++;
175 return intoBuffer.subspan (0, 1);
176 }
177 if (auto o = PullEnufForDeflate1Byte_ (blockFlag, intoBuffer, processInputZLibFunction)) {
178 size_t pulledOut = *o;
179 _fSeekOffset += pulledOut;
180 return intoBuffer.subspan (0, pulledOut);
181 }
182 else {
183 return nullopt;
184 }
185 }
186 };
187 struct DeflateRep_ : BaseRep_ {
188 Compress::Options fOptions_;
189 DeflateRep_ (const Streams::InputStream::Ptr<byte>& in, Compress::Options o, bool useGZip)
190 : BaseRep_{in}
191 , fOptions_{o}
192 {
193 Require (not o.fCompressionLevel.has_value () or (0 <= o.fCompressionLevel and o.fCompressionLevel <= 1));
194 int level = o.fCompressionLevel ? Z_BEST_SPEED + Math::Round<int> (*o.fCompressionLevel * (Z_BEST_COMPRESSION - Z_BEST_SPEED))
195 : Z_DEFAULT_COMPRESSION;
196 Assert (level == Z_DEFAULT_COMPRESSION or (Z_BEST_SPEED <= level and level <= Z_BEST_COMPRESSION));
197 if (useGZip) {
198 constexpr int kWindowBits = 15 + 16; // 16 => gzip - https://zlib.net/manual.html#Advanced
199 constexpr int kMemLevel = 8; // default
200 ThrowIfZLibErr_ (::deflateInit2 (&fZStream_, level, Z_DEFLATED, kWindowBits, kMemLevel, Z_DEFAULT_STRATEGY));
201 }
202 else {
203 ThrowIfZLibErr_ (::deflateInit (&fZStream_, level));
204 }
205 }
206 virtual ~DeflateRep_ ()
207 {
208 Verify (::deflateEnd (&fZStream_) == Z_OK);
209 }
210 virtual optional<size_t> AvailableToRead () override
211 {
212 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
213 return _Available2Read ([this] (bool isEOF) { return DoProcess_ (isEOF); });
214 }
215 virtual optional<SeekOffsetType> RemainingLength () override
216 {
217 return nullopt; // generally cannot tell without side-effects on input stream
218 }
219 virtual optional<span<byte>> Read (span<byte> intoBuffer, NoDataAvailableHandling blockFlag) override
220 {
221 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
222 return _Read (intoBuffer, blockFlag, [this] (bool isEOF) { return DoProcess_ (isEOF); });
223 }
224 int DoProcess_ (bool isEOF)
225 {
226 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
227 return ::deflate (&fZStream_, isEOF ? Z_FINISH : Z_NO_FLUSH);
228 }
229 };
230 struct InflateRep_ : BaseRep_ {
231 InflateRep_ (const Streams::InputStream::Ptr<byte>& in, bool gzip)
232 : BaseRep_{in}
233 {
234 // see http://zlib.net/manual.html for meaning of params and http://www.lemoda.net/c/zlib-open-read/ for example
235 constexpr int windowBits = 15;
236 constexpr int ENABLE_ZLIB_GZIP = 32;
237 ThrowIfZLibErr_ (::inflateInit2 (&fZStream_, windowBits | (gzip ? ENABLE_ZLIB_GZIP : 0)));
238 }
239 virtual ~InflateRep_ ()
240 {
241 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
242 Verify (::inflateEnd (&fZStream_) == Z_OK);
243 }
244 virtual optional<size_t> AvailableToRead () override
245 {
246 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
247 return _Available2Read ([this] (bool isEOF) { return DoProcess_ (isEOF); });
248 }
249 virtual optional<SeekOffsetType> RemainingLength () override
250 {
251 return nullopt; // generally cannot tell without side-effects on input stream
252 }
253 virtual optional<span<byte>> Read (span<byte> intoBuffer, NoDataAvailableHandling blockFlag) override
254 {
255 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
256 return _Read (intoBuffer, blockFlag, [this] (bool isEOF) { return DoProcess_ (isEOF); });
257 }
258 int DoProcess_ ([[maybe_unused]] bool isEOF)
259 {
260 return ::inflate (&fZStream_, Z_NO_FLUSH);
261 }
262 };
263}
264#endif
#define Verify(c)
Definition Assertions.h:419
NoDataAvailableHandling
If eDontBlock passed to most Stream APIs, then when the code would do a blocking read,...
Definition Stream.h:90
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual optional< ElementType > ReadBlocking() const
ReadBlocking () reads either a single element, or fills in argument intoBuffer - but never blocks (no...
nonvirtual optional< size_t > AvailableToRead() const
returns nullopt if nothing known available, zero if known EOF, and any other number of elements (typi...
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43