Stroika Library 3.0d23x
 
Loading...
Searching...
No Matches
MessageStartTextInputStreamBinaryAdapter.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
8#include "Stroika/Foundation/Execution/Common.h"
9#include "Stroika/Foundation/Execution/OperationNotSupportedException.h"
11
13
14using namespace Stroika::Foundation;
16using namespace Stroika::Foundation::Execution;
17using namespace Stroika::Foundation::IO;
20using namespace Stroika::Foundation::Streams;
21
22using Memory::MakeSharedPtr;
23
24// Comment this in to turn on aggressive noisy DbgTrace in this module
25// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
26
27namespace {
28 constexpr size_t kDefaultBufSize_ = 2 * 1024; // guess enough for http headers (typically around .8K but little cost in reserving a bit more)
29}
30
31//@todo - NOTE - NOT RIGHT - RE_READ RFP - maybe need to do mime decoding???
32// http://stackoverflow.com/questions/4400678/http-header-should-use-what-character-encoding
33// but for now this seems and adequate hack
34
35class MessageStartTextInputStreamBinaryAdapter::Rep_ final : public InputStream::IRep<Character> {
36 using inherited = InputStream::IRep<Character>;
37
38public:
39 Rep_ (const InputStream::Ptr<byte>& src)
40 : fSource_{src}
41 , fAllDataReadBuf_{kDefaultBufSize_}
42 , fOffset_{0}
43 , fBufferFilledUpValidBytes_{0}
44 {
45 }
46
47public:
48 bool AssureHeaderSectionAvailable ()
49 {
50 // @todo fix - inefficient implementation - LGP 2023-12-30
51#if USE_NOISY_TRACE_IN_THIS_MODULE_
52 Debug::TraceContextBumper ctx{"MessageStartTextInputStreamBinaryAdapter::AssureHeaderSectionAvailable"};
53#endif
54 this->SeekRead (eFromStart, 0);
55 Character c;
56 enum state {
57 gotCR,
58 gotCRLF,
59 gotCRLFCR,
60 gotNOTHING,
61 };
62 state s = gotNOTHING;
63 while (optional<span<Character>> o = Read (span{&c, &c + 1}, NoDataAvailableHandling::eDontBlock)) {
64 if (o->size () == 0) {
65 return true; // tricky corner case - EOF in header - treat as available so we process whole header
66 }
67 Assert (o->size () == 1);
68 switch (c.GetCharacterCode ()) {
69 case '\r': {
70 switch (s) {
71 case gotNOTHING: {
72 s = gotCR;
73 } break;
74 case gotCRLF: {
75 s = gotCRLFCR;
76 } break;
77 default: {
78 DbgTrace ("Looks like bad HTTP header (\\r)"_f);
79 s = gotNOTHING;
80 } break;
81 }
82 } break;
83 case '\n': {
84 switch (s) {
85 case gotCR: {
86 s = gotCRLF;
87 } break;
88 case gotCRLFCR: {
89 this->SeekRead (eFromStart, 0);
90 return true;
91 } break;
92 default: {
93 DbgTrace ("Looks like bad HTTP header (\\n)"_f);
94 s = gotNOTHING;
95 } break;
96 }
97 } break;
98 default: {
99 s = gotNOTHING;
100 } break;
101 }
102 }
103 return false;
104 }
105
106public:
107 nonvirtual Characters::String ToString (ToStringFormat format) const
108 {
109 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
110 StringBuilder sb;
111 sb << "{"sv;
112 sb << "Offset: "sv << fOffset_;
113 sb << ", HighWaterMark: "sv << fBufferFilledUpValidBytes_;
114 sb << ", TEXT: "sv;
115 switch (format) {
116 case ToStringFormat::eAsBytes: {
117 for (size_t i = 0; i < fBufferFilledUpValidBytes_; ++i) {
118 sb << "x{:x}, "_f(fAllDataReadBuf_[i]);
119 }
120 } break;
121 case ToStringFormat::eAsString: {
122 sb << "'"sv;
123 for (Character c : String::FromLatin1 (span{reinterpret_cast<const char*> (begin (fAllDataReadBuf_)), fBufferFilledUpValidBytes_})) {
124 switch (c.GetCharacterCode ()) {
125 case '\r':
126 sb << "\\r"sv;
127 break;
128 case '\n':
129 sb << "\\n"sv;
130 break;
131 default:
132 sb << c.GetCharacterCode ();
133 break;
134 }
135 }
136 sb << "'"sv;
137 } break;
138 }
139 sb << "}"sv;
140 return sb;
141 }
142
143protected:
144 virtual bool IsSeekable () const override
145 {
146 return true;
147 }
148 virtual void CloseRead () override
149 {
150 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
151 if (fSource_ != nullptr) {
152 fSource_.Close ();
153 }
154 Assert (fSource_ == nullptr);
155 }
156 virtual bool IsOpenRead () const override
157 {
158 return fSource_ != nullptr;
159 }
160 virtual optional<size_t> AvailableToRead () override
161 {
162 Require (IsOpenRead ());
163 if (fOffset_ < fBufferFilledUpValidBytes_) {
164 return fBufferFilledUpValidBytes_ - fOffset_;
165 }
166 // default impl handles this case since we are seekable
168 }
169 virtual optional<SeekOffsetType> RemainingLength () override
170 {
171 Require (IsOpenRead ());
172 return nullopt; // could do a bit better, but not important here - generally can do no better
173 }
174 virtual optional<span<Character>> Read (span<Character> intoBuffer, NoDataAvailableHandling blockFlag) override
175 {
176 Require (not intoBuffer.empty ());
177 Require (IsOpenRead ());
178 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
179 Assert (fBufferFilledUpValidBytes_ >= fOffset_); // limitation/feature of current implemetnation
180 if (fBufferFilledUpValidBytes_ == fOffset_) {
181 size_t roomLeftInBuf = fAllDataReadBuf_.GetSize () - fBufferFilledUpValidBytes_;
182 if (roomLeftInBuf == 0) {
183 // should be quite rare
184 fAllDataReadBuf_.GrowToSize_uninitialized (fBufferFilledUpValidBytes_ + kDefaultBufSize_);
185 roomLeftInBuf = fAllDataReadBuf_.GetSize () - fBufferFilledUpValidBytes_;
186 }
187 Assert (roomLeftInBuf > 0);
188
189 //tmphack
190 {
191 // this code is crap and needs to be thrown out/rewritten - but this kludge may get us limping along
192 size_t nBytesNeeded = intoBuffer.size ();
193 if (roomLeftInBuf > nBytesNeeded) {
194 roomLeftInBuf = nBytesNeeded;
195 }
196 }
197
198 byte* startReadAt = fAllDataReadBuf_.begin () + fBufferFilledUpValidBytes_;
199 size_t n = fSource_.ReadOrThrow (span{startReadAt, roomLeftInBuf}, blockFlag).size ();
200 Assert (n <= roomLeftInBuf);
201 // if n == 0, OK, just means EOF
202 fBufferFilledUpValidBytes_ += n;
203 }
204
205 // At this point - see if we can fullfill the request. If not - its cuz we got EOF
206 size_t outN = 0;
207 for (auto outChar = intoBuffer.begin (); outChar != intoBuffer.end (); ++outChar) {
208 if (fOffset_ < fBufferFilledUpValidBytes_) {
209 // SEE http://stroika-bugs.sophists.com/browse/STK-969 - treat incoming chars as ascii for now
210 *outChar = Characters::Character{(char32_t)*(fAllDataReadBuf_.begin () + fOffset_)};
211 fOffset_++;
212 outN++;
213 }
214 }
215 Ensure (outN <= intoBuffer.size ());
216 return intoBuffer.subspan (0, outN);
217 }
218 virtual SeekOffsetType GetReadOffset () const override
219 {
220 Debug::AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
221 Require (IsOpenRead ());
222 return fOffset_;
223 }
224 virtual SeekOffsetType SeekRead (Whence whence, SignedSeekOffsetType offset) override
225 {
226 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
227 Require (IsOpenRead ());
228 static const auto kException_ = range_error{"seek"};
229 switch (whence) {
230 case eFromStart: {
231 if (offset < 0) [[unlikely]] {
232 Execution::Throw (kException_);
233 }
234 SeekOffsetType uOffset = static_cast<SeekOffsetType> (offset);
235 if (uOffset > fBufferFilledUpValidBytes_) [[unlikely]] {
236 Execution::Throw (kException_);
237 }
238 // Note - warning here legit - our caching strategy wtih string is bogus and wont work with large streams
239 fOffset_ = static_cast<size_t> (offset);
240 } break;
241 case eFromCurrent: {
242 Streams::SeekOffsetType curOffset = fOffset_;
243 Streams::SignedSeekOffsetType newOffset = curOffset + offset;
244 if (newOffset < 0) [[unlikely]] {
245 Execution::Throw (kException_);
246 }
247 SeekOffsetType uNewOffset = static_cast<SeekOffsetType> (newOffset);
248 if (uNewOffset > fBufferFilledUpValidBytes_) [[unlikely]] {
249 Execution::Throw (kException_);
250 }
251 // Note - warning here legit - our caching strategy wtih string is bogus and wont work wtih large streams
252 fOffset_ = static_cast<size_t> (newOffset);
253 } break;
254 case eFromEnd: {
255 Streams::SignedSeekOffsetType newOffset = fBufferFilledUpValidBytes_ + offset;
256 if (newOffset < 0) [[unlikely]] {
257 Execution::Throw (kException_);
258 }
259 SeekOffsetType uNewOffset = static_cast<SeekOffsetType> (newOffset);
260 if (uNewOffset > fBufferFilledUpValidBytes_) [[unlikely]] {
261 Execution::Throw (kException_);
262 }
263 // Note - warning here legit - our caching strategy wtih string is bogus and wont work wtih large streams
264 fOffset_ = static_cast<size_t> (newOffset);
265 } break;
266 }
267 Ensure ((0 <= fOffset_) and (fOffset_ <= fBufferFilledUpValidBytes_));
268 return GetReadOffset ();
269 }
270
271private:
272 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fThisAssertExternallySynchronized_;
273 InputStream::Ptr<byte> fSource_;
274 Memory::InlineBuffer<byte> fAllDataReadBuf_; // OK cuz typically this will be very small (1k) and not really grow...but it can if we must
275 size_t fOffset_; // text stream offset
276 size_t fBufferFilledUpValidBytes_; // nbytes of valid text in fAllDataReadBuf_
277};
278
279/*
280 ********************************************************************************
281 ********* IO::Network::HTTP::MessageStartTextInputStreamBinaryAdapter **********
282 ********************************************************************************
283 */
284MessageStartTextInputStreamBinaryAdapter::Ptr MessageStartTextInputStreamBinaryAdapter::New (const InputStream::Ptr<byte>& src)
285{
286 return Ptr{MakeSharedPtr<Rep_> (src)};
287}
288
289/*
290 ********************************************************************************
291 ******** IO::Network::HTTP::MessageStartTextInputStreamBinaryAdapter::Ptr ******
292 ********************************************************************************
293 */
294MessageStartTextInputStreamBinaryAdapter::Ptr::Ptr (const shared_ptr<InputStream::IRep<Character>>& from)
295 : inherited{from}
296{
297}
298
299bool MessageStartTextInputStreamBinaryAdapter::Ptr::AssureHeaderSectionAvailable ()
300{
301 return Debug::UncheckedDynamicCast<Rep_&> (GetRepRWRef ()).AssureHeaderSectionAvailable ();
302}
303
305{
306 return Debug::UncheckedDynamicCast<const Rep_&> (GetRepConstRef ()).ToString (format);
307}
NoDataAvailableHandling
If eDontBlock passed to most Stream APIs, then when the code would do a blocking read,...
Definition Stream.h:90
#define DbgTrace
Definition Trace.h:309
constexpr char32_t GetCharacterCode() const noexcept
Return the char32_t UNICODE code-point associated with this character.
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
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...
nonvirtual Characters::String ToString(ToStringFormat format=ToStringFormat::eDEFAULT) const
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
virtual optional< size_t > AvailableToRead()
returns nullopt if nothing known available, zero if known EOF, and any other number of elements (typi...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual IRep< Characters::Character > & GetRepRWRef() const
A Streams::Ptr<ELEMENT_TYPE> is a smart-pointer to a stream of elements of type T.
Definition Stream.h:170
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43