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