PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
virtual_profinet_rpc_device.cpp
Go to the documentation of this file.
1
3
5
6#include <algorithm>
7#include <chrono>
8#include <stdexcept>
9#include <utility>
10
11#include "profinet/wire.h"
12
13namespace profinet::sim
14{
15
16namespace
17{
18
19using namespace std::chrono_literals;
20
21constexpr std::uint16_t kBlockHeaderSize = 4U;
22constexpr std::uint16_t kControlBlockSize = 32U;
23
24bool IsControlPrmBegin(const std::uint16_t type)
25{
26 return type == static_cast<std::uint16_t>(BlockType::ParameterizationBeginRequest);
27}
28
29bool IsControlPrmEnd(const std::uint16_t type)
30{
31 return type == static_cast<std::uint16_t>(BlockType::ControlPrmEndRequest);
32}
33
34bool IsControlRelease(const std::uint16_t type)
35{
36 return type == static_cast<std::uint16_t>(BlockType::ReleaseRequest);
37}
38
39} // namespace
40
41const char* ToString(const VirtualProfinetDeviceState state) noexcept
42{
43 switch (state)
44 {
46 return "Idle";
48 return "Connected";
50 return "Parameterizing";
52 return "Parameterized";
54 return "ApplicationReady";
56 return "Released";
58 return "Fault";
59 }
60 return "Unknown";
61}
62
65 : config(std::move(configRef))
66{
67}
68
73
75{
76 if (running.exchange(true))
77 {
78 return;
79 }
80
81 ioContext = std::make_unique<asio::io_context>();
82 socket = std::make_unique<asio::ip::udp::socket>(*ioContext);
83
84 asio::error_code error;
85 socket->open(asio::ip::udp::v4(), error);
86 if (error)
87 {
88 running = false;
89 throw std::system_error(error, "VirtualProfinetRpcDevice::open");
90 }
91
92 socket->set_option(asio::socket_base::reuse_address(true), error);
93 if (error)
94 {
95 running = false;
96 throw std::system_error(error, "VirtualProfinetRpcDevice::set_option");
97 }
98
99 socket->bind(
100 asio::ip::udp::endpoint(asio::ip::udp::v4(), config.rpcPort),
101 error);
102 if (error)
103 {
104 running = false;
105 throw std::system_error(error, "VirtualProfinetRpcDevice::bind");
106 }
107
108 worker = std::thread([this]()
109 {
110 Run();
111 });
112}
114{
115 if (!running.exchange(false))
116 {
117 return;
118 }
119
120 // Wake both state waiters and the delayed ApplicationReady worker.
121 stateCondition.notify_all();
122
123 // The worker is blocked in synchronous receive_from(). Do not close the
124 // socket yet. First inject a local datagram so receive_from() returns.
125 WakeWorker();
126
127 if (worker.joinable())
128 {
129 worker.join();
130 }
131
132 // The delayed ApplicationReady operation must also be joined before the
133 // socket and io_context are destroyed.
134 std::thread applicationReadyThread;
135 {
136 std::scoped_lock lock(mutex);
137 applicationReadyThread = std::move(applicationReadyWorker);
138 }
139
140 if (applicationReadyThread.joinable())
141 {
142 applicationReadyThread.join();
143 }
144
145 asio::error_code error;
146 if (socket != nullptr)
147 {
148 socket->cancel(error);
149 socket->close(error);
150 }
151
152 socket.reset();
153 ioContext.reset();
154}
155
157{
158 if (socket == nullptr)
159 {
160 return;
161 }
162
163 try
164 {
165 asio::io_context wakeContext;
166 asio::ip::udp::socket wakeSocket(wakeContext);
167
168 asio::error_code error;
169
170 wakeSocket.open(asio::ip::udp::v4(), error);
171 if (error)
172 {
173 return;
174 }
175
176 constexpr std::uint8_t wakeByte = 0;
177
178 const asio::ip::udp::endpoint endpoint(
179 asio::ip::address_v4::loopback(),
181
182 wakeSocket.send_to(
183 asio::buffer(&wakeByte, sizeof(wakeByte)),
184 endpoint,
185 0,
186 error);
187 }
188 catch (...)
189 {
190 // Stop() is noexcept. If the wake-up cannot be sent, the caller will
191 // still proceed with shutdown.
192 }
193}
194
196{
197 std::scoped_lock lock(mutex);
198 return state;
199}
200
201std::size_t VirtualProfinetRpcDevice::ConnectCount() const noexcept
202{
203 std::scoped_lock lock(mutex);
204 return connectCount;
205}
206
207std::size_t VirtualProfinetRpcDevice::PrmBeginCount() const noexcept
208{
209 std::scoped_lock lock(mutex);
210 return prmBeginCount;
211}
212
213std::size_t VirtualProfinetRpcDevice::PrmEndCount() const noexcept
214{
215 std::scoped_lock lock(mutex);
216 return prmEndCount;
217}
218
220{
221 std::scoped_lock lock(mutex);
223}
224
226{
227 std::scoped_lock lock(mutex);
229}
230
231std::size_t VirtualProfinetRpcDevice::ReleaseCount() const noexcept
232{
233 std::scoped_lock lock(mutex);
234 return releaseCount;
235}
236
238{
239 std::scoped_lock lock(mutex);
240 return sawInputIocr;
241}
242
244{
245 std::scoped_lock lock(mutex);
246 return sawOutputIocr;
247}
248
250{
251 std::scoped_lock lock(mutex);
253}
254
256{
257 std::scoped_lock lock(mutex);
258 return sawAlarmCr;
259}
260
262{
263 std::scoped_lock lock(mutex);
264 return lastError;
265}
266
268 const VirtualProfinetDeviceState expected,
269 const std::chrono::milliseconds timeout) const
270{
271 std::unique_lock lock(mutex);
272 return stateCondition.wait_for(
273 lock,
274 timeout,
275 [this, expected]()
276 {
277 return state == expected;
278 });
279}
280
281std::uint16_t VirtualProfinetRpcDevice::RpcPort() const noexcept
282{
283 return config.rpcPort;
284}
285
287{
288 while (running.load())
289 {
290 Bytes buffer(65535U);
291 asio::ip::udp::endpoint sender;
292 asio::error_code error;
293
294 const std::size_t received = socket->receive_from(
295 asio::buffer(buffer),
296 sender,
297 0,
298 error);
299
300 if (!running.load())
301 {
302 break;
303 }
304
305 if (error)
306 {
307 continue;
308 }
309
310 buffer.resize(received);
311
312 try
313 {
315 }
316 catch (const std::exception& exception)
317 {
318 std::scoped_lock lock(mutex);
320 lastError = exception.what();
321 stateCondition.notify_all();
322 }
323 }
324}
325
327 const PNRPCHeader& request,
328 const asio::ip::udp::endpoint& sender)
329{
330 if (request.packetType == PNRPCHeader::RESPONSE &&
332 {
334 return;
335 }
336
337 if (request.packetType != PNRPCHeader::REQUEST)
338 {
339 return;
340 }
341
342 switch (request.operationNumber)
343 {
345 HandleConnect(request, sender);
346 break;
347
350 HandleControl(request, sender);
351 break;
352 default:
353 // A lifecycle simulator deliberately does not implement acyclic record
354 // access yet. The cyclic/record simulator can be layered on top later.
355 break;
356 }
357}
358
360{
361 std::scoped_lock lock(mutex);
362
363 if (!running.load())
364 {
365 return;
366 }
367
368 if (applicationReadyWorker.joinable())
369 {
370 return;
371 }
372
373 applicationReadyWorker = std::thread([this]()
374 {
375 std::unique_lock lock(mutex);
376
377 const bool stopped = stateCondition.wait_for(
378 lock,
380 [this]()
381 {
382 return !running.load();
383 });
384
385 if (stopped)
386 {
387 return;
388 }
389
390 lock.unlock();
391
393 });
394}
395
397 const PNRPCHeader& request,
398 const asio::ip::udp::endpoint& sender)
399{
400 const PNNRDData nrd = PNNRDData::Parse(request.payload);
401
402 if (!ValidateConnect(nrd.payload))
403 {
404 return;
405 }
406
408
409 {
410 std::scoped_lock lock(mutex);
411 controllerEndpoint = sender;
412 ++connectCount;
414 lastError.clear();
415 }
416 stateCondition.notify_all();
417
418 Bytes responsePayload;
419
420 // Echo a valid IOCR configuration into IOCRBlockRes blocks. RPCCon only
421 // needs the response frame IDs at this stage, so the response intentionally
422 // contains no application-specific data.
423 std::size_t offset = 0;
424 while (offset + kBlockHeaderSize <= nrd.payload.size())
425 {
426 const auto header = rpc::PeekBlockHeader(nrd.payload, offset);
427 const std::size_t blockSize =
428 kBlockHeaderSize + static_cast<std::size_t>(header.blockLength);
429
430 if (blockSize > nrd.payload.size() - offset)
431 {
432 break;
433 }
434
435 if (header.blockType == PNIOCRBlockReqHeader::BLOCK_TYPE)
436 {
437 const Bytes block(
438 nrd.payload.begin() + static_cast<std::ptrdiff_t>(offset),
439 nrd.payload.begin() + static_cast<std::ptrdiff_t>(offset + blockSize));
441 const std::uint16_t frameId =
443 const Bytes response = MakeIocrResponse(iocr, frameId);
444 responsePayload.insert(responsePayload.end(), response.begin(), response.end());
445 }
446
447 offset += blockSize;
448 }
449
450 if (sawAlarmCr)
451 {
453 responsePayload.insert(responsePayload.end(), alarm.begin(), alarm.end());
454 }
455
456 SendResponse(request, MakeNrd(responsePayload), sender);
457}
458
460 const PNRPCHeader& request,
461 const asio::ip::udp::endpoint& sender)
462{
463 const PNNRDData nrd = PNNRDData::Parse(request.payload);
464 if (nrd.payload.size() < kControlBlockSize)
465 {
466 return;
467 }
468
469 wire::Reader reader(nrd.payload);
470 const std::uint16_t blockType = reader.U16();
472 const std::uint16_t command = reader.U16();
473
474 if (IsControlPrmBegin(blockType) && command == rpc::CONTROL_CMD_PRM_BEGIN)
475 {
476 std::scoped_lock lock(mutex);
478 {
480 lastError = "PrmBegin received outside Connected state";
481 return;
482 }
485 stateCondition.notify_all();
486 SendResponse(request, MakeNrd({}), sender);
487 return;
488 }
489
490 if (IsControlPrmEnd(blockType) && command == rpc::CONTROL_CMD_PRM_END)
491 {
492 {
493 std::scoped_lock lock(mutex);
495 {
497 lastError = "PrmEnd received before PrmBegin";
498 stateCondition.notify_all();
499 return;
500 }
501 ++prmEndCount;
503 }
504 stateCondition.notify_all();
505 SendResponse(request, MakeNrd({}), sender);
506
508 {
510
511 // std::thread([this]()
512 // {
513 // std::this_thread::sleep_for(config.applicationReadyDelay);
514 // SendApplicationReady();
515 // }).detach();
516 }
517 return;
518 }
519
520 if (IsControlRelease(blockType))
521 {
522 {
523 std::scoped_lock lock(mutex);
524 ++releaseCount;
526 }
527 stateCondition.notify_all();
528 SendResponse(request, MakeNrd({}), sender);
529 }
530}
531
533 const PNRPCHeader& response)
534{
535 std::scoped_lock lock(mutex);
536
538 {
540 lastError = "ApplicationReady response received outside Parameterized state";
541 stateCondition.notify_all();
542 return;
543 }
544
545 if (!lastApplicationReadyRequest.has_value())
546 {
548 lastError = "ApplicationReady response received without a request";
549 stateCondition.notify_all();
550 return;
551 }
552
553 if (response.activityUuid != lastApplicationReadyRequest->activityUuid ||
554 response.objectUuid != lastApplicationReadyRequest->objectUuid ||
555 response.interfaceUuid != lastApplicationReadyRequest->interfaceUuid ||
556 response.sequenceNumber != lastApplicationReadyRequest->sequenceNumber)
557 {
559 lastError = "ApplicationReady response did not preserve RPC correlation";
560 stateCondition.notify_all();
561 return;
562 }
563
566 stateCondition.notify_all();
567}
568
570{
571 const bool hasAr = ContainsBlock(nrdPayload, BlockType::ApplicationRelationRequest);
572 if (!hasAr)
573 {
574 std::scoped_lock lock(mutex);
576 lastError = "CONNECT missing ARBlockReq";
577 stateCondition.notify_all();
578 return false;
579 }
580
581 bool input = false;
582 bool output = false;
583
584 std::size_t offset = 0;
585 while (offset + kBlockHeaderSize <= nrdPayload.size())
586 {
587 const auto header = rpc::PeekBlockHeader(nrdPayload, offset);
588 const std::size_t blockSize =
589 kBlockHeaderSize + static_cast<std::size_t>(header.blockLength);
590
591 if (blockSize > nrdPayload.size() - offset)
592 {
593 break;
594 }
595
596 if (header.blockType == PNIOCRBlockReqHeader::BLOCK_TYPE)
597 {
598 const Bytes block(
599 nrdPayload.begin() + static_cast<std::ptrdiff_t>(offset),
600 nrdPayload.begin() + static_cast<std::ptrdiff_t>(offset + blockSize));
602 input = input || iocr.iocrType == 1U;
603 output = output || iocr.iocrType == 2U;
604 }
605
606 offset += blockSize;
607 }
608
609 {
610 std::scoped_lock lock(mutex);
611 sawInputIocr = input;
612 sawOutputIocr = output;
614 nrdPayload,
617 nrdPayload,
619 }
620
621 const bool cyclicConnect = input || output;
622 if (cyclicConnect && config.requireBothIocrs && (!input || !output))
623 {
624 std::scoped_lock lock(mutex);
626 lastError = "Cyclic CONNECT must contain both Input and Output IOCR";
627 stateCondition.notify_all();
628 return false;
629 }
630
632 {
633 std::scoped_lock lock(mutex);
635 lastError = "Cyclic CONNECT missing ExpectedSubmoduleBlockReq";
636 stateCondition.notify_all();
637 return false;
638 }
639
640 if (cyclicConnect && config.requireAlarmCr && !sawAlarmCr)
641 {
642 std::scoped_lock lock(mutex);
644 lastError = "Cyclic CONNECT missing AlarmCRBlockReq";
645 stateCondition.notify_all();
646 return false;
647 }
648
649 return true;
650}
651
653 const PNRPCHeader& request,
654 const Bytes& nrdPayload,
655 const asio::ip::udp::endpoint& sender)
656{
657 const PNRPCHeader response = MakeResponse(request, nrdPayload);
658 const Bytes bytes = response.ToBytes();
659
660 asio::error_code error;
661 socket->send_to(asio::buffer(bytes), sender, 0, error);
662 if (error)
663 {
664 std::scoped_lock lock(mutex);
666 lastError = "Failed to send RPC response: " + error.message();
667 stateCondition.notify_all();
668 }
669}
670
672{
673 asio::ip::udp::endpoint controller;
674
675 {
676 std::scoped_lock lock(mutex);
678 !controllerEndpoint.has_value())
679 {
680 return;
681 }
682 controller = {
683 asio::ip::make_address(config.controllerAddress),
685 }
686
687 PNBlockHeader block;
689 block.blockLength = 28U;
690 block.blockVersionHigh = 1U;
691 block.blockVersionLow = 0U;
692
693 PNIODReleaseBlock control;
694 std::ranges::copy(
695 block.ToBytes(),
696 control.blockHeader.begin());
697 control.padding1 = 0;
698 control.arUuid = arUuid;
699 control.sessionKey = sessionKey;
700 control.padding2 = 0;
702 control.controlBlockProperties = 0;
703
704 const Bytes nrd = MakeNrd(control.ToBytes());
705
706 PNRPCHeader request;
707 request.version = 4;
709 request.flags1 = static_cast<std::uint8_t>(
711 request.flags2 = 0;
712 request.dataRepresentation = {0, 0, 0};
713 request.serialNrHigh = 0;
716 request.activityUuid = arUuid;
717 request.serverBootTime = 0;
718 request.interfaceVersion = 1;
719 request.sequenceNumber = 1;
723 request.lengthOfBody = static_cast<std::uint16_t>(nrd.size());
724 request.fragmentNumber = 0;
725 request.authenticationProtocol = 0;
726 request.serialNrLow = 0;
727 request.payload = nrd;
728
729 {
730 std::scoped_lock lock(mutex);
733 }
734
735 const Bytes bytes = request.ToBytes();
736 asio::error_code error;
737 socket->send_to(asio::buffer(bytes), controller, 0, error);
738
739 if (error)
740 {
741 std::scoped_lock lock(mutex);
743 lastError = "Failed to send ApplicationReady: " + error.message();
744 stateCondition.notify_all();
745 }
746}
747
749{
750 PNNRDData nrd;
751 nrd.argsMaximumStatus = 0;
752 nrd.argsLength = static_cast<std::uint32_t>(payload.size());
753 nrd.maximumCount = static_cast<std::uint32_t>(payload.size());
754 nrd.offset = 0;
755 nrd.actualCount = static_cast<std::uint32_t>(payload.size());
756 nrd.payload = payload;
757 return nrd.ToBytes();
758}
759
761 const PNRPCHeader& request,
762 const Bytes& nrdPayload)
763{
764 PNRPCHeader response = request;
766 response.flags1 = static_cast<std::uint8_t>(
768 response.flags2 = 0;
769 response.lengthOfBody =
770 static_cast<std::uint16_t>(nrdPayload.size());
771 response.payload = nrdPayload;
772 return response;
773}
774
776 const PNIOCRBlockReqHeader& request,
777 const std::uint16_t frameId)
778{
779 Bytes bytes;
781 wire::PutU16(bytes, static_cast<std::uint16_t>(PNIOCRBlockRes::kSize - 4U));
784 wire::PutU16(bytes, request.iocrType);
785 wire::PutU16(bytes, request.iocrReference);
786 wire::PutU16(bytes, frameId);
787 return bytes;
788}
789
791 const std::uint16_t deviceAlarmReference)
792{
793 PNAlarmCRBlockRes response;
794 PNBlockHeader header;
796 header.blockLength =
797 static_cast<std::uint16_t>(PNAlarmCRBlockRes::kSize - 4U);
800 std::ranges::copy(
801 header.ToBytes(),
802 response.blockHeader.begin());
803 response.localAlarmReference = deviceAlarmReference;
804 return response.ToBytes();
805}
806
808 const Bytes& data,
809 const BlockType blockType)
810{
811 std::size_t offset = 0;
812 while (offset + kBlockHeaderSize <= data.size())
813 {
814 const auto header = rpc::PeekBlockHeader(data, offset);
815 const std::size_t blockSize =
816 kBlockHeaderSize + static_cast<std::size_t>(header.blockLength);
817
818 if (blockSize > data.size() - offset)
819 {
820 return false;
821 }
822
823 if (header.blockType == blockType)
824 {
825 return true;
826 }
827
828 offset += blockSize;
829 }
830 return false;
831}
832
834 const Bytes& connectPayload,
835 std::array<std::uint8_t, uuidLenght>& arUuidOut,
836 std::uint16_t& sessionKeyOut)
837{
838 if (connectPayload.size() < 8U + uuidLenght + sizeof(std::uint16_t))
839 {
840 throw std::invalid_argument("CONNECT ARBlockReq is truncated");
841 }
842
843 wire::Reader reader(connectPayload);
844 reader.SkipTo(8U);
845 arUuidOut = reader.Fixed<uuidLenght>();
846 sessionKeyOut = reader.U16();
847}
848
849} // namespace profinet::sim
static bool ContainsBlock(const Bytes &data, BlockType blockType)
Find a block type in a serialized CONNECT payload.
void WakeWorker() noexcept
Wake the synchronous receive so the worker can observe shutdown.
std::size_t PrmEndCount() const noexcept
Return the number of PrmEnd requests accepted.
std::array< std::uint8_t, uuidLenght > arUuid
std::size_t PrmBeginCount() const noexcept
Return the number of PrmBegin requests accepted.
void ScheduleApplicationReady()
Start a cancellable ApplicationReady delay.
void SendApplicationReady()
Send a device-originated ApplicationReady request.
std::uint16_t RpcPort() const noexcept
Return the configured device RPC port.
std::unique_ptr< asio::ip::udp::socket > socket
std::size_t ApplicationReadyResponseCount() const noexcept
Return the number of ApplicationReady responses accepted.
std::optional< asio::ip::udp::endpoint > controllerEndpoint
void Stop() noexcept
Stop listening and join the worker thread.
void Start()
Start listening for controller RPC packets.
static Bytes MakeAlarmCrResponse(std::uint16_t deviceAlarmReference)
Build a minimal successful AlarmCRBlockRes.
void Run()
Worker loop receiving controller datagrams.
std::thread applicationReadyWorker
Delayed ApplicationReady worker.
std::unique_ptr< asio::io_context > ioContext
bool SawInputIocr() const noexcept
Return whether the controller supplied an Input IOCR.
void HandleApplicationReadyResponse(const PNRPCHeader &response)
Handle the response to the device-originated ApplicationReady.
void HandleControl(const PNRPCHeader &request, const asio::ip::udp::endpoint &sender)
Handle a CONTROL request such as PrmBegin or PrmEnd.
VirtualProfinetRpcDevice(VirtualProfinetDeviceConfiguration config={})
Construct a stopped simulator.
std::size_t ConnectCount() const noexcept
Return the number of CONNECT requests accepted.
std::size_t ApplicationReadyRequestCount() const noexcept
Return the number of ApplicationReady requests sent.
static void ParseArIdentity(const Bytes &connectPayload, std::array< std::uint8_t, uuidLenght > &arUuid, std::uint16_t &sessionKey)
Parse the AR UUID and session key from a CONNECT request.
VirtualProfinetDeviceState State() const noexcept
Return the current protocol state.
static Bytes MakeIocrResponse(const PNIOCRBlockReqHeader &request, std::uint16_t frameId)
Build one IOCRBlockRes from a controller IOCR request.
std::string LastError() const
Return the last diagnostic/rejection text.
bool ValidateConnect(const Bytes &nrdPayload)
Validate and extract the CONNECT blocks.
void HandlePacket(const PNRPCHeader &request, const asio::ip::udp::endpoint &sender)
Dispatch one complete RPC packet.
void SendResponse(const PNRPCHeader &request, const Bytes &nrdPayload, const asio::ip::udp::endpoint &sender)
Send a successful RPC response.
std::size_t ReleaseCount() const noexcept
Return the number of Release requests observed.
bool SawExpectedSubmodules() const noexcept
Return whether the controller supplied ExpectedSubmodule blocks.
static Bytes MakeNrd(const Bytes &payload)
Build an NRD wrapper with successful status.
void HandleConnect(const PNRPCHeader &request, const asio::ip::udp::endpoint &sender)
Handle a CONNECT request.
static PNRPCHeader MakeResponse(const PNRPCHeader &request, const Bytes &nrdPayload)
Build an RPC response preserving correlation fields.
bool SawAlarmCr() const noexcept
Return whether the controller supplied an AlarmCR.
bool SawOutputIocr() const noexcept
Return whether the controller supplied an Output IOCR.
bool WaitForState(VirtualProfinetDeviceState expected, std::chrono::milliseconds timeout) const
Wait until the simulator reaches a state.
Lightweight cursor for parsing a big-endian byte buffer without copies.
Definition wire.h:192
std::array< std::uint8_t, N > Fixed()
Read and consume a fixed-size byte array.
Definition wire.h:316
void SkipTo(std::size_t offset)
Move the read position to an absolute byte offset.
Definition wire.h:343
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
Definition wire.h:258
@ LastFragment
Bit 1 (0x02): Final fragment in the sequence.
@ Idempotent
Bit 5 (0x20): Activity call is idempotent (safe to retry).
@ NoFack
Bit 3 (0x08): Do not send FACK (fragment acknowledgment) frames.
constexpr std::uint8_t VERSION_HIGH
Version high.
Definition rpc.h:42
constexpr std::uint16_t CONTROL_CMD_PRM_BEGIN
Control command bit: PrmBegin.
Definition rpcTypes.h:88
constexpr std::uint16_t CONTROL_CMD_PRM_END
Control command bit: PrmEnd.
Definition rpcTypes.h:76
BlockHeaderView PeekBlockHeader(const Bytes &data, std::size_t offset)
Definition rpc.h:54
constexpr std::uint8_t VERSION_LOW
Version low.
Definition rpc.h:44
VirtualProfinetDeviceState
Observable Application Relation state of the virtual device.
@ ApplicationReady
The controller confirmed the device's ApplicationReady request.
@ Connected
A CONNECT was accepted and the AR is being configured.
@ Fault
The simulator rejected a request.
@ Parameterizing
PrmBegin was accepted.
@ Parameterized
PrmEnd was accepted and the device can issue ApplicationReady.
@ Idle
No Application Relation exists.
@ Released
The controller released the AR.
const char * ToString(VirtualAumaScenario scenario) noexcept
Convert a virtual AUMA scenario into its stable CLI spelling.
void PutU8(std::vector< std::uint8_t > &out, std::uint8_t value)
Append a single byte to a buffer.
Definition wire.h:89
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t value)
Append a 16-bit value to a buffer in big-endian order.
Definition wire.h:97
BlockType
PROFINET block types.
Definition indices.h:89
@ ControlPrmEndRequest
IOD Control Prm End request.
@ ApplicationRelationRequest
AR block request.
@ ExpectedSubmoduleRequest
Expected submodule block request.
@ ReleaseRequest
IOD Release request.
@ AlarmCrResponse
AlarmCR block response.
@ ControlApplicationReadyRequest
IOD Control Application Ready request.
@ ParameterizationBeginRequest
Parameterization Begin request.
constexpr int uuidLenght
Constant lenght of a UUID.
Definition util.h:40
@ ApplicationReady
Application Ready command.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
asio::error_code error
Bytes buffer
static constexpr BlockType BLOCK_TYPE
Block type identifier for AlarmCRBlockReq.
Definition protocol.h:2118
AlarmCRBlockRes: device's response confirming the alarm connection (part of the CONNECT response).
Definition protocol.h:2216
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:2257
std::array< std::uint8_t, blockHeaderLength > blockHeader
Nested 6-byte block header.
Definition protocol.h:2223
static constexpr std::size_t kSize
Size in bytes of this block.
Definition protocol.h:2235
std::uint16_t localAlarmReference
Local alarm reference assigned by the device.
Definition protocol.h:2229
Bytes ToBytes() const
Serialize this header back to raw bytes.
Definition protocol.h:1347
BlockType blockType
Block type identifier.
Definition protocol.h:1308
std::uint8_t blockVersionHigh
Block format major version.
Definition protocol.h:1317
std::uint8_t blockVersionLow
Block format minor version.
Definition protocol.h:1320
std::uint16_t blockLength
Length in bytes of the block body (excludes Type/Length fields).
Definition protocol.h:1311
IOCRBlockReq header: requests establishment of one IOCR (part of the CONNECT request).
Definition protocol.h:1984
static constexpr BlockType BLOCK_TYPE
Block type identifier for IOCRBlockReq.
Definition protocol.h:1987
std::uint16_t iocrType
IOCR type (Input/Output/MulticastProvider/MulticastConsumer).
Definition protocol.h:1994
std::uint16_t iocrReference
IOCR reference chosen by the controller.
Definition protocol.h:1997
static PNIOCRBlockReqHeader Parse(const Bytes &data)
Parse the fixed IOCRBlockReq header.
Definition protocol.h:2048
static constexpr std::uint16_t BLOCK_TYPE
Block type identifier for IOCRBlockRes.
Definition protocol.h:1943
static constexpr std::size_t kSize
Size in bytes of this block.
Definition protocol.h:1959
static constexpr std::uint16_t kContolCommandOffset
ControlCommand is located at offset 28 inside PNIODControlBlockReq (Block Header: 6 bytes + ARUUID: 1...
Definition protocol.h:1193
Control block used for Release/PrmBegin/PrmEnd/ApplicationReady/RTClass3 requests.
Definition protocol.h:1449
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID this control operation applies to.
Definition protocol.h:1457
std::uint16_t controlBlockProperties
Control block property flags.
Definition protocol.h:1469
std::uint16_t padding1
Reserved/padding.
Definition protocol.h:1454
std::uint16_t sessionKey
Session key matching the AR.
Definition protocol.h:1460
std::array< std::uint8_t, blockHeaderLength > blockHeader
Nested 6-byte block header.
Definition protocol.h:1451
ControlCommand controlCommand
Control command bitmask (PrmEnd/AppReady/Release/Done/...).
Definition protocol.h:1466
std::uint16_t padding2
Reserved/padding.
Definition protocol.h:1463
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:1497
NRD (Network Representation Data) wrapper carrying the actual IOD payload.
Definition protocol.h:1128
Bytes payload
The wrapped IOD data.
Definition protocol.h:1145
static PNNRDData Parse(const Bytes &data)
Parse an NRD wrapper and payload from raw bytes.
Definition protocol.h:1153
Bytes ToBytes() const
Serialize this wrapper and payload back to raw bytes.
Definition protocol.h:1175
std::uint32_t argsLength
Length in bytes of the arguments (usually equals ActualCount).
Definition protocol.h:1133
std::uint32_t offset
Byte offset into the logical result (0 unless fragmented).
Definition protocol.h:1139
std::uint32_t argsMaximumStatus
Maximum status/argument buffer size accepted by the caller.
Definition protocol.h:1130
std::uint32_t maximumCount
Maximum number of bytes the caller can accept.
Definition protocol.h:1136
std::uint32_t actualCount
Actual length in bytes of Payload.
Definition protocol.h:1142
DCE/RPC PDU header used to carry PROFINET connect/read/write/control requests.
Definition protocol.h:860
Bytes ToBytes() const
Serialize this header and body back to raw bytes.
Definition protocol.h:1045
static constexpr std::uint16_t defaultInterfaceHint
Definition protocol.h:915
std::array< std::uint8_t, 3 > dataRepresentation
Data representation format label.
Definition protocol.h:931
static constexpr std::uint16_t RELEASE
Operation number: RELEASE.
Definition protocol.h:899
std::uint16_t operationNumber
Operation number (CONNECT/RELEASE/READ/WRITE/CONTROL/IMPLICIT_READ).
Definition protocol.h:955
std::uint16_t fragmentNumber
Fragment number for fragmented PDUs.
Definition protocol.h:967
std::uint16_t interfaceHint
Interface hint (0xFFFF if unused).
Definition protocol.h:958
std::uint8_t flags1
Packet flags, first byte.
Definition protocol.h:925
static constexpr std::uint16_t defaultActivityHint
Definition protocol.h:916
static PNRPCHeader Parse(const Bytes &data)
Parse an RPC header and body from raw bytes.
Definition protocol.h:1012
std::uint8_t packetType
Packet type (REQUEST/RESPONSE/FAULT/...).
Definition protocol.h:922
std::array< std::uint8_t, uuidLenght > interfaceUuid
Target interface UUID (identifies device/controller/supervisor role).
Definition protocol.h:940
std::uint8_t authenticationProtocol
Authentication protocol identifier (unused by PROFINET).
Definition protocol.h:970
std::uint8_t serialNrHigh
High byte of the serial number.
Definition protocol.h:934
static std::array< std::uint8_t, uuidLenght > IfaceUuidDevice()
Interface UUID identifying the IO-Device role.
Definition protocol.h:983
std::uint8_t serialNrLow
Low byte of the serial number.
Definition protocol.h:973
std::uint32_t sequenceNumber
Sequence number within the activity.
Definition protocol.h:952
static constexpr std::uint16_t CONNECT
Operation number: CONNECT.
Definition protocol.h:896
std::uint8_t version
DCE/RPC version (always 4 for PROFINET).
Definition protocol.h:919
static constexpr std::uint8_t REQUEST
Packet type: REQUEST.
Definition protocol.h:863
static constexpr std::uint16_t CONTROL
Operation number: CONTROL.
Definition protocol.h:908
std::uint32_t serverBootTime
Server boot time counter.
Definition protocol.h:946
static constexpr std::uint8_t RESPONSE
Packet type: RESPONSE.
Definition protocol.h:869
std::uint8_t flags2
Packet flags, second byte.
Definition protocol.h:928
Bytes payload
PDU body (NRD data for read/write/control, etc.).
Definition protocol.h:976
std::uint32_t interfaceVersion
Interface version.
Definition protocol.h:949
std::uint16_t activityHint
Activity hint (0xFFFF if unused).
Definition protocol.h:961
std::uint16_t lengthOfBody
Length in bytes of Payload.
Definition protocol.h:964
std::array< std::uint8_t, uuidLenght > objectUuid
Target object UUID.
Definition protocol.h:937
std::array< std::uint8_t, uuidLenght > activityUuid
Activity (call) UUID correlating request/response pairs.
Definition protocol.h:943
Configuration for a controller-facing virtual PROFINET device.
bool requireExpectedSubmodules
Require ExpectedSubmoduleBlockReq in cyclic CONNECT requests.
std::chrono::milliseconds applicationReadyDelay
Delay between accepting PrmEnd and sending ApplicationReady.
bool automaticApplicationReady
Automatically send ApplicationReady after PrmEnd.
bool requireBothIocrs
Require an Input and Output IOCR when CONNECT contains cyclic data.
std::string controllerAddress
Controller loopback address used for ApplicationReady.
std::uint16_t outputFrameId
Frame ID returned for the output IOCR.
std::uint16_t inputFrameId
Frame ID returned for the input IOCR.
bool requireAlarmCr
Require AlarmCR when cyclic CONNECT is requested.
std::uint16_t controllerCcontrolPort
Controller CControl listener port used for ApplicationReady.
std::uint16_t rpcPort
UDP port on which the virtual device accepts controller RPC.
std::uint16_t deviceAlarmReference
Alarm reference returned by the device when AlarmCR is supported.
std::uint16_t frameId
Ethernet FrameID.
std::unique_ptr< cyclic::CyclicController > controller
Bytes payload
Definition test_dcp.cpp:392
Stateful PROFINET IO-Device DCE/RPC lifecycle simulator.
Generic helpers for reading and writing integer and byte data in big- and little-endian wire formats.