Stroika Library 3.0d22
 
Loading...
Searching...
No Matches
Server.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
11#include "Stroika/Foundation/IO/Network/Listener.h"
21
22#include "Server.h"
23
24using std::byte;
25
26using namespace Stroika::Foundation;
29using namespace Stroika::Foundation::Debug;
30using namespace Stroika::Foundation::Execution;
31using namespace Stroika::Foundation::Memory;
32using namespace Stroika::Foundation::IO;
34using namespace Stroika::Foundation::Streams;
35using namespace Stroika::Foundation::Traversal;
36
37using Memory::MakeSharedPtr;
38
39using namespace Stroika::Frameworks;
40using namespace Stroika::Frameworks::Modbus;
41
42// Comment this in to turn on aggressive noisy DbgTrace in this module
43//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
44
45/*
46 * From http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf page 11
47 */
48namespace {
49 enum FunctionCodeType_ : uint8_t {
50 kReadCoils_ = 1,
51 kReadDiscreteInputs_ = 2,
52 kReadHoldingResisters_ = 3,
53 kReadInputRegister_ = 4,
54 kWriteSingleCoil_ = 5,
55 };
56}
57
58namespace {
59
60 /*
61 * Roughly from http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf - but they describe
62 * only first 7 bytes as header. Trouble is - hard to do in C++ - only 7 type struct. Subclass combing
63 * longer.
64 *
65 * Plus always seems used with following function code. So go with the flow, and add the word ish to the name.
66 */
67 struct MBAPHeaderIsh_ {
68 alignas (2) uint16_t fTransactionID;
69 alignas (2) uint16_t fProtocolID;
70 alignas (2) uint16_t fLength;
71 alignas (1) uint8_t fUnitID;
72 alignas (1) FunctionCodeType_ fFunctionCode;
73
74 static constexpr size_t kExtraLengthFromThisHeaderAccountedInPayloadLength{2};
75
76 size_t GetPayloadLength () const
77 {
78 // Funky - but from http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf -
79 // The length field is a byte count of the following fields, including the Unit Identifier and data fields.
80 Require (fLength >= kExtraLengthFromThisHeaderAccountedInPayloadLength);
81 return fLength - kExtraLengthFromThisHeaderAccountedInPayloadLength;
82 }
83 };
84
85 static_assert (sizeof (MBAPHeaderIsh_) == 8, "");
86 static_assert (offsetof (MBAPHeaderIsh_, fTransactionID) == 0, "");
87 static_assert (offsetof (MBAPHeaderIsh_, fProtocolID) == 2, "");
88 static_assert (offsetof (MBAPHeaderIsh_, fLength) == 4, "");
89 static_assert (offsetof (MBAPHeaderIsh_, fUnitID) == 6, "");
90 static_assert (offsetof (MBAPHeaderIsh_, fFunctionCode) == 7, "");
91}
92
93namespace {
94 /*
95 * All transmissions in big endian (network byte order)
96 */
97 uint16_t FromNetwork_ (uint16_t v)
98 {
99 return Common::EndianConverter (v, Common::Endian::eBig, Common::GetEndianness ());
100 }
101 MBAPHeaderIsh_ FromNetwork_ (const MBAPHeaderIsh_& v)
102 {
103 return MBAPHeaderIsh_{FromNetwork_ (v.fTransactionID), FromNetwork_ (v.fProtocolID), FromNetwork_ (v.fLength), v.fUnitID, v.fFunctionCode};
104 }
105 uint16_t ToNetwork_ (uint16_t v)
106 {
107 return Common::EndianConverter (v, Common::GetEndianness (), Common::Endian::eBig);
108 }
109 MBAPHeaderIsh_ ToNetwork_ (const MBAPHeaderIsh_& v)
110 {
111 return MBAPHeaderIsh_{ToNetwork_ (v.fTransactionID), ToNetwork_ (v.fProtocolID), ToNetwork_ (v.fLength), v.fUnitID, v.fFunctionCode};
112 }
113}
114
115// For internal debugging
117 template <>
118 String ToString (const FunctionCodeType_& f)
119 {
120 switch (f) {
121 case FunctionCodeType_::kReadCoils_:
122 return "Read-Coils"_k;
123 case FunctionCodeType_::kReadDiscreteInputs_:
124 return "Read-Discrete-Inputs"_k;
125 case FunctionCodeType_::kReadHoldingResisters_:
126 return "Read-Holding-Resisters_"_k;
127 case FunctionCodeType_::kReadInputRegister_:
128 return "Read-Input-Register_"_k;
129 case FunctionCodeType_::kWriteSingleCoil_:
130 return "WriteSingleCoil"_k;
131 }
132 return Format ("{}"_f, f);
133 }
134 template <>
135 String ToString (const MBAPHeaderIsh_& mh)
136 {
137 StringBuilder sb;
138 sb << "{"sv;
139 sb << "TransactionID: "sv << mh.fTransactionID;
140 sb << ", ProtocolID: "sv << mh.fProtocolID;
141 sb << ", Length: "sv << mh.fLength;
142 sb << ", UnitID: "sv << mh.fUnitID;
143 sb << ", FunctionCode: "sv << mh.fFunctionCode;
144 sb << "}"sv;
145 return sb;
146 }
147}
148
149namespace {
150 void ConnectionHandler_ (const ConnectionOrientedStreamSocket::Ptr& connectionSocket, shared_ptr<IModbusService> serviceHandler,
151 const ServerOptions& options)
152 {
153 TraceContextBumper ctx{"Modbus-Connection"};
154#if qStroika_Foundation_Debug_DefaultTracingOn
155 static atomic<uint32_t> sConnectionNumber_;
156 uint32_t thisModbusConnectionNumber = ++sConnectionNumber_;
157 DbgTrace ("Starting Modbus connection {}"_f, thisModbusConnectionNumber);
158 [[maybe_unused]] auto&& cleanup =
159 Finally ([thisModbusConnectionNumber] () { DbgTrace ("Finishing Modbus connection {}"_f, thisModbusConnectionNumber); });
160#endif
162 if (auto p = connectionSocket.GetPeerAddress ()) {
163 DbgTrace ("Starting connection from peer: {}"_f, *p);
164 }
165 }
166
167 SocketStream::Ptr socketStream = SocketStream::New (connectionSocket);
168 InputStream::Ptr<byte> in = BufferedInputStream::New<byte> (socketStream); // not important, but a good idea, to avoid excessive kernel calls
169 OutputStream::Ptr<byte> out = BufferedOutputStream::New<byte> (socketStream); // critical so we don't write multiple packets - at least some apps assume whole thing comes in one packet
170
171 auto checkedReadHelperPayload2Shorts = [] (const Memory::BLOB& requestPayload, uint16_t minSecondValue,
172 uint16_t maxSecondValue) -> pair<uint16_t, uint16_t> {
173 /*
174 * From http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf - page 16 (etc)
175 */
176 if (requestPayload.size () != 4) {
177 DbgTrace ("requestPayload={}"_f, requestPayload);
178 Throw (Execution::Exception{"Invalid payload length (got {}, expected 4)"_f(requestPayload.size ())});
179 }
180 uint16_t startingAddress = FromNetwork_ (*reinterpret_cast<const uint16_t*> (requestPayload.begin () + 0));
181 uint16_t quantity = FromNetwork_ (*reinterpret_cast<const uint16_t*> (requestPayload.begin () + 2)); // allowed 1..maxSecondValue
182 if (not(minSecondValue <= quantity and quantity <= maxSecondValue)) {
183 Throw (Execution::Exception{"Invalid quantity parameter ({}): expected value from {}..{}"_f(quantity, minSecondValue, maxSecondValue)});
184 }
185 return pair<uint16_t, uint16_t>{startingAddress, quantity};
186 };
187
188 try {
189 /*
190 * I believe (must re-read docs more carefully) - we can receive multiple requests on a single connection, and just close the connection
191 * on our first bad packet.
192 */
193 while (true) {
194 MBAPHeaderIsh_ requestHeader; // intentionally don't initialize since either all read, or we throw
195 size_t n = in.ReadAll (as_writable_bytes (span{&requestHeader, 1})).size ();
196 if (n != sizeof (requestHeader)) {
197 if (n == 0) {
198 break; // just EOF - so quietly end/close connection
199 }
200 else {
201 Throw (Execution::Exception{"Incomplete MBAP header"sv}); // Bad packet - incomplete header - so closing connection
202 }
203 }
204 requestHeader = FromNetwork_ (requestHeader);
205
206 /*
207 * Perform minimal validation and - for now - abandon conneciton - but soon bettter error handling (see above)
208 */
209 if (requestHeader.fProtocolID != 0) {
210 Throw (Execution::Exception{"bad protocol"sv});
211 }
212 if (requestHeader.fLength < 2) {
213 // Error cuz each full header I know of requires 2 bytes at least
214 Throw (Execution::Exception{"Illegal short MBAP request length"sv});
215 }
216
217 Memory::BLOB requestPayload = in.ReadAll (requestHeader.GetPayloadLength ());
218 auto zeroToOneBased = [] (uint16_t i) -> uint16_t { return i + 1; };
219 auto oneBasedToZeroBased = [] (uint16_t i) -> uint16_t { return i - 1; };
220 switch (requestHeader.fFunctionCode) {
221 case FunctionCodeType_::kReadCoils_: {
222 /*
223 * From http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf - page 12
224 */
225 uint16_t startingAddress = checkedReadHelperPayload2Shorts (requestPayload, 1, 0x7d0).first;
226 uint16_t quantity = checkedReadHelperPayload2Shorts (requestPayload, 1, 0x7d0).second;
227 Assert (quantity >= 1);
228 uint16_t endInclusiveAddress = startingAddress + quantity - 1u;
229 size_t quantityBytes = (quantity + 7) / 8;
230 StackBuffer<uint8_t> results{quantityBytes};
231 (void)::memset (results.begin (), 0, quantityBytes); // for now - fill zeros for values not returned by backend
232#if USE_NOISY_TRACE_IN_THIS_MODULE_
233 DbgTrace ("Processing kReadCoils_ (starting0Address: {}, quantity: {}) message with request-header={}",
234 startingAddress, quantity, requestHeader);
235#endif
236 for (const auto& i :
237 serviceHandler->ReadCoils (DiscreteRange<uint16_t>{zeroToOneBased (startingAddress), zeroToOneBased (endInclusiveAddress)}
238 .Elements ()
240 if (startingAddress <= oneBasedToZeroBased (i.fKey) and oneBasedToZeroBased (i.fKey) < endInclusiveAddress and i.fValue) {
241 results[(oneBasedToZeroBased (i.fKey) - startingAddress) / 8] |=
242 Memory::Bit ((oneBasedToZeroBased (i.fKey) - startingAddress) % 8);
243 }
244 }
245#if USE_NOISY_TRACE_IN_THIS_MODULE_
246 DbgTrace ("results bitmask bytes={}", Memory::BLOB{reinterpret_cast<const byte*> (results.begin ()),
247 reinterpret_cast<const byte*> (results.end ())});
248#endif
249 {
250 // Response ready - format, toNetwork, and write
251 uint8_t responseLen = static_cast<uint8_t> (quantityBytes); // OK cuz validated in checkedReadHelperPayload2Shorts (and converted to bytes)
252 MBAPHeaderIsh_ responseHeader =
253 MBAPHeaderIsh_{requestHeader.fTransactionID, requestHeader.fProtocolID,
254 static_cast<uint16_t> (MBAPHeaderIsh_::kExtraLengthFromThisHeaderAccountedInPayloadLength +
255 sizeof (responseLen) + responseLen),
256 requestHeader.fUnitID, requestHeader.fFunctionCode};
257 out.Write (Memory::AsBytes (ToNetwork_ (responseHeader)));
258 out.Write (Memory::AsBytes (responseLen));
259 out.Write (span{results.begin (), responseLen});
260#if USE_NOISY_TRACE_IN_THIS_MODULE_
261 DbgTrace ("Sent response: header={}, responseLen={}, responsePayload={}"_f, responseHeader, responseLen,
262 Memory::BLOB{results.begin (), results.begin () + responseLen});
263#endif
264 }
265 } break;
266 case FunctionCodeType_::kReadDiscreteInputs_: {
267 /*
268 * From http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf - page 13
269 */
270 uint16_t startingAddress = checkedReadHelperPayload2Shorts (requestPayload, 1, 0x7d0).first;
271 uint16_t quantity = checkedReadHelperPayload2Shorts (requestPayload, 1, 0x7d0).second;
272 Assert (quantity >= 1);
273 uint16_t endInclusiveAddress = startingAddress + quantity - 1u;
274 size_t quantityBytes = (quantity + 7) / 8;
275 StackBuffer<uint8_t> results{quantityBytes}; // for now - fill zeros for values not returned by backend
276#if USE_NOISY_TRACE_IN_THIS_MODULE_
277 DbgTrace ("Processing kReadDiscreteInputs_ (starting0Address: {}, quantity: {}) message with request-header={}"_f,
278 startingAddress, quantity, requestHeader);
279#endif
280 for (const auto& i : serviceHandler->ReadDiscreteInput (
281 DiscreteRange<uint16_t>{zeroToOneBased (startingAddress), zeroToOneBased (endInclusiveAddress)}
282 .Elements ()
284 if (startingAddress <= oneBasedToZeroBased (i.fKey) and oneBasedToZeroBased (i.fKey) <= endInclusiveAddress) {
285 if (i.fValue) {
286 results[(oneBasedToZeroBased (i.fKey) - startingAddress) / 8] |=
287 Memory::Bit ((oneBasedToZeroBased (i.fKey) - startingAddress) % 8);
288 }
289 }
290 }
291 {
292 // Response ready - format, toNetwork, and write
293 uint8_t responseLen = static_cast<uint8_t> (quantityBytes); // OK cuz validated in checkedReadHelperPayload2Shorts (and converted to bytes)
294 MBAPHeaderIsh_ responseHeader =
295 MBAPHeaderIsh_{requestHeader.fTransactionID, requestHeader.fProtocolID,
296 static_cast<uint16_t> (MBAPHeaderIsh_::kExtraLengthFromThisHeaderAccountedInPayloadLength +
297 sizeof (responseLen) + responseLen),
298 requestHeader.fUnitID, requestHeader.fFunctionCode};
299 out.Write (Memory::AsBytes (ToNetwork_ (responseHeader)));
300 out.Write (Memory::AsBytes (responseLen));
301 out.Write (span{results.begin (), responseLen});
302#if USE_NOISY_TRACE_IN_THIS_MODULE_
303 DbgTrace ("Sent response: header={}, responseLen={}"_f, responseHeader, responseLen);
304#endif
305 }
306 } break;
307 case FunctionCodeType_::kReadHoldingResisters_: {
308 /*
309 * From http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf - page 15
310 */
311 uint16_t startingAddress = checkedReadHelperPayload2Shorts (requestPayload, 1, 0x7d).first;
312 uint16_t quantity = checkedReadHelperPayload2Shorts (requestPayload, 1, 0x7d).second;
313 Assert (quantity >= 1);
314 uint16_t endInclusiveAddress = startingAddress + quantity - 1u;
315 StackBuffer<uint16_t> results{quantity}; // for now - fill zeros for values not returned by backend
316#if USE_NOISY_TRACE_IN_THIS_MODULE_
317 DbgTrace ("Processing kReadHoldingResisters_ (starting0Address: {}, quantity: {}) message with request-header={}"_f,
318 startingAddress, quantity, requestHeader);
319#endif
320 for (const auto& i : serviceHandler->ReadHoldingRegisters (
321 DiscreteRange<uint16_t>{zeroToOneBased (startingAddress), zeroToOneBased (endInclusiveAddress)}
322 .Elements ()
324 if (startingAddress <= oneBasedToZeroBased (i.fKey) and oneBasedToZeroBased (i.fKey) <= endInclusiveAddress) {
325 results[oneBasedToZeroBased (i.fKey) - startingAddress] = ToNetwork_ (i.fValue);
326 }
327 }
328 {
329 // Response ready - format, toNetwork, and write
330 uint8_t responseLen = static_cast<uint8_t> (quantity * sizeof (results[0])); // (responseLen in bytes) OK cuz validated in checkedReadHelperPayload2Shorts
331 MBAPHeaderIsh_ responseHeader =
332 MBAPHeaderIsh_{requestHeader.fTransactionID, requestHeader.fProtocolID,
333 static_cast<uint16_t> (MBAPHeaderIsh_::kExtraLengthFromThisHeaderAccountedInPayloadLength +
334 sizeof (responseLen) + responseLen),
335 requestHeader.fUnitID, requestHeader.fFunctionCode};
336 out.Write (Memory::AsBytes (ToNetwork_ (responseHeader)));
337 out.Write (Memory::AsBytes (responseLen));
338 out.Write (span{reinterpret_cast<const byte*> (results.begin ()), responseLen});
339#if USE_NOISY_TRACE_IN_THIS_MODULE_
340 DbgTrace ("Sent response: header={}, responseLen={}"_f, responseHeader, responseLen);
341#endif
342 }
343 } break;
344 case FunctionCodeType_::kReadInputRegister_: {
345 /*
346 * From http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf - page 16
347 */
348 uint16_t startingAddress = checkedReadHelperPayload2Shorts (requestPayload, 1, 0x7d).first;
349 uint16_t quantity = checkedReadHelperPayload2Shorts (requestPayload, 1, 0x7d).second;
350 Assert (quantity >= 1);
351 uint16_t endInclusiveAddress = startingAddress + quantity - 1u;
352 StackBuffer<uint16_t> results{quantity}; // for now - fill zeros for values not returned by backend
353#if USE_NOISY_TRACE_IN_THIS_MODULE_
354 DbgTrace ("Processing kReadInputRegister_ (starting0Address: {}, quantity: {}) message with request-header={}"_f,
355 startingAddress, quantity, requestHeader);
356#endif
357 for (const auto& i : serviceHandler->ReadInputRegisters (
358 DiscreteRange<uint16_t>{zeroToOneBased (startingAddress), zeroToOneBased (endInclusiveAddress)}
359 .Elements ()
361 if (startingAddress <= oneBasedToZeroBased (i.fKey) and oneBasedToZeroBased (i.fKey) <= endInclusiveAddress) {
362 results[oneBasedToZeroBased (i.fKey) - startingAddress] = ToNetwork_ (i.fValue);
363 }
364 }
365 {
366 // Response ready - format, toNetwork, and write
367 uint8_t responseLen = static_cast<uint8_t> (quantity * sizeof (results[0])); // (responseLen in bytes) OK cuz validated in checkedReadHelperPayload2Shorts
368 MBAPHeaderIsh_ responseHeader =
369 MBAPHeaderIsh_{requestHeader.fTransactionID, requestHeader.fProtocolID,
370 static_cast<uint16_t> (MBAPHeaderIsh_::kExtraLengthFromThisHeaderAccountedInPayloadLength +
371 sizeof (responseLen) + responseLen),
372 requestHeader.fUnitID, requestHeader.fFunctionCode};
373 out.Write (Memory::AsBytes (ToNetwork_ (responseHeader)));
374 out.Write (Memory::AsBytes (responseLen));
375 out.Write (span{reinterpret_cast<const byte*> (results.begin ()), responseLen});
376#if USE_NOISY_TRACE_IN_THIS_MODULE_
377 DbgTrace ("Sent response: header={}, responseLen={}"_f, responseHeader, responseLen);
378#endif
379 }
380 } break;
381 case FunctionCodeType_::kWriteSingleCoil_: {
382 /*
383 * From http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf - page 17
384 */
385 uint16_t outputAddress = checkedReadHelperPayload2Shorts (requestPayload, 0, 0xff00).first;
386 uint16_t value = checkedReadHelperPayload2Shorts (requestPayload, 0, 0xff00).second;
387#if USE_NOISY_TRACE_IN_THIS_MODULE_
388 DbgTrace ("Processing kWriteSingleCoil_ (outputAddress: {}, value: {}) message with request-header={}"_f,
389 outputAddress, value, requestHeader);
390#endif
391 serviceHandler->WriteCoils ({{zeroToOneBased (outputAddress), value == 0 ? false : true}});
392 {
393 // Response ready - format, toNetwork, and write
394 out.Write (Memory::AsBytes (requestHeader));
395 out.Write (Memory::AsBytes (ToNetwork_ (outputAddress)));
396 out.Write (Memory::AsBytes (ToNetwork_ (value)));
397#if USE_NOISY_TRACE_IN_THIS_MODULE_
398 DbgTrace ("Sent response: header={}"_f, requestHeader);
399#endif
400 }
401 } break;
402 default: {
403 DbgTrace ("UNREGONIZED FunctionCode (NYI probably) - {} - so echo ILLEGAL_FUNCTION code"_f, requestHeader.fFunctionCode);
404 if (options.fLogger) {
405 options.fLogger.value ()->Log (Logger::eWarning, "ModbusTCP unrecognized function code '{}'- rejected as ILLEGAL_FUNCTION"_f,
406 requestHeader.fFunctionCode);
407 }
408 MBAPHeaderIsh_ responseHeader = requestHeader;
409 responseHeader.fFunctionCode = static_cast<FunctionCodeType_> (responseHeader.fFunctionCode | 0x80); // set high bit
410 out.Write (Memory::AsBytes (ToNetwork_ (responseHeader)));
411 out.Write (Memory::AsBytes (static_cast<uint8_t> (ExceptionCode::ILLEGAL_FUNCTION)));
412#if USE_NOISY_TRACE_IN_THIS_MODULE_
413 DbgTrace ("Sent UNREGONIZED_FUNCTION response: header={}, and exceptionCode={}"_f, responseHeader, exceptionCode);
414#endif
415 }
416 }
417 out.Flush (); // since buffering output, be sure to flush after each response!
418 }
419 }
420 catch (const Thread::AbortException&) {
421 ReThrow (); // no warning needed
422 }
423 catch (...) {
424 // Anytime we leave the loop due to an exception, that's worth a log note
425 if (options.fLogger) {
426 options.fLogger.value ()->Log (Logger::eWarning, "ModbusTCP connection ended abnormally: {}"_f, current_exception ());
427 }
428 ReThrow ();
429 }
430 }
431}
432
433/*
434 ********************************************************************************
435 *********** Frameworks::Modbus::MakeModbusTCPServerThread **********************
436 ********************************************************************************
437 */
438Thread::Ptr Modbus::MakeModbusTCPServerThread (const shared_ptr<IModbusService>& serviceHandler, const ServerOptions& options)
439{
440 shared_ptr<ThreadPool> usingThreadPool = options.fThreadPool;
441 if (usingThreadPool == nullptr) {
442 usingThreadPool = MakeSharedPtr<ThreadPool> (ThreadPool::Options{.fThreadCount = 1});
443 }
444
445 // Note - we return thread not started, so caller must explicitly start, but internal threads start immediately
446 auto onModbusConnection = [serviceHandler, options, usingThreadPool] (const ConnectionOrientedStreamSocket::Ptr& s) {
447 usingThreadPool->AddTask ([serviceHandler, options, s] () { ConnectionHandler_ (s, serviceHandler, options); });
448 };
449 return Thread::New (
450 [onModbusConnection, options] () {
451#if USE_NOISY_TRACE_IN_THIS_MODULE_
452 TraceContextBumper ctx{"Modbus-Listener"};
453#endif
454 uint16_t usingPortNumber = options.fListenPort.value_or (502);
455 if (options.fLogger) {
456 options.fLogger.value ()->Log (Logger::eInfo, "Listening for ModbusTCP requests on port {}"_f, usingPortNumber);
457 }
458 WaitableEvent{}.Wait (); // forever (til thread abort)
459 },
460 "Modbus-Listener"_k);
461}
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:48
#define DbgTrace
Definition Trace.h:309
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
nonvirtual size_t size() const noexcept
Definition String.inl:534
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:334
nonvirtual void Wait(Time::DurationSeconds timeout=Time::kInfinity)
nonvirtual optional< IO::Network::SocketAddress > GetPeerAddress() const
nonvirtual const byte * begin() const
Definition BLOB.inl:258
nonvirtual size_t size() const
Definition BLOB.inl:286
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
InputOutputStream is single stream object that acts much as a InputStream::Ptr and an OutputStream::P...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual String ReadAll(size_t upTo=numeric_limits< size_t >::max()) const
OutputStream<>::Ptr is Smart pointer to a stream-based sink of data.
nonvirtual void Write(span< ELEMENT_TYPE2, EXTENT_2 > elts) const
nonvirtual void Flush() const
forces any data contained in this stream to be written.
A DiscreteRange is a Range where the underlying endpoints are integral (discrete, not continuous); th...
nonvirtual Iterable< T > Elements() 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
constexpr Endian GetEndianness()
returns native (std::endian::native) Endianness flag. Can be complicated (mixed, etc)....
Definition Endian.inl:25
constexpr T EndianConverter(T value, Endian from, Endian to)
Definition Endian.inl:60
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:959
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
Execution::Thread::Ptr MakeModbusTCPServerThread(const shared_ptr< IModbusService > &serviceHandler, const ServerOptions &options=ServerOptions{})
Definition Server.cpp:438