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