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