PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_rpc.cpp
Go to the documentation of this file.
1
26
27#include <gtest/gtest.h>
28
29#include <deque>
30#include <optional>
31#include <thread>
32
34#include "profinet/rpc.h"
35#include "profinet/rpcTypes.h"
36
37namespace profinet::rpc
38{
39class RpcTests : public ::testing::Test
40{
41 protected:
42 void SetUp() override
43 {
44 transport = std::make_unique<TestRpcTransport>();
45 rawTransport = transport.get();
46 ctransport = std::make_unique<TestRpcTransport>();
49 }
50
51 void TearDown() override
52 {
53 }
54
60 {
61 dcp::DCPDeviceDescription info(String2Mac("aa:bb:cc:dd:ee:ff"), {});
62 info.name = "test-device";
63 info.deviceHigh = 0x12;
64 info.deviceLow = 0x34;
65 info.vendorHigh = 0x56;
66 info.vendorLow = 0x78;
67 return info;
68 }
69
75 bool IsAllZeroUuid(const std::array<std::uint8_t, uuidLenght>& uuid)
76 {
77 return std::ranges::all_of(uuid, [](const std::uint8_t byte)
78 {
79 return byte == 0;
80 });
81 }
82
89 {
90 if (conn != nullptr)
91 {
92 return conn->CreateRpc(PNRPCHeader::CONNECT, payload);
93 }
94 return this->conn->CreateRpc(PNRPCHeader::CONNECT, payload);
95 }
96
104 {
105 return rpc::RPCCon::BuildIocrBlock(iocrType, setup);
106 }
107
114 Bytes MakeEpmTowerFloor(const Bytes& lhs, const Bytes& rhs)
115 {
116 Bytes result;
117 wire::PutLittleEndian(result, static_cast<std::uint16_t>(lhs.size()));
118
119 wire::PutFixed(result, lhs);
120
121 wire::PutLittleEndian(result, static_cast<std::uint16_t>(rhs.size()));
122
123 wire::PutFixed(result, rhs);
124
125 return result;
126 }
127
133 Bytes MakeEpmTower(const std::vector<Bytes>& floors)
134 {
135 Bytes result;
136
137 wire::PutLittleEndian(result, static_cast<std::uint16_t>(floors.size()));
138
139 for (const auto& floor : floors)
140 {
141 wire::PutFixed(result, floor);
142 }
143
144 return result;
145 }
146
153 Bytes MakeInterfaceTowerLhs(const std::string& uuid, std::uint16_t major)
154 {
155 Bytes lhs;
156 lhs.push_back(0x0D);
157
158 const auto uuidBytes = StringToUuidBytes(uuid);
159 wire::PutFixed(lhs, uuidBytes);
160
161 wire::PutLittleEndian(lhs, major);
162
163 return lhs;
164 }
165
171 Bytes MakeInterfaceTowerRhs(std::uint16_t minor)
172 {
173 Bytes rhs;
174 wire::PutLittleEndian(rhs, minor);
175 return rhs;
176 }
177
187 Bytes MakeCompleteEpmTower(const std::string& interfaceUuid, std::uint16_t major, std::uint16_t minor,
188 std::uint16_t port, const std::array<std::uint8_t, 4>& ipAddress)
189 {
190 const Bytes interfaceFloor = MakeEpmTowerFloor(MakeInterfaceTowerLhs(interfaceUuid, major), MakeInterfaceTowerRhs(minor));
191
192 const Bytes udpFloor = MakeEpmTowerFloor({0x0A}, {});
193
194 Bytes portData;
195 wire::PutBigEndian(portData, port);
196 const Bytes portFloor = MakeEpmTowerFloor({0x08}, portData);
197
198 const Bytes ipFloor = MakeEpmTowerFloor({0x09}, {
199 ipAddress[0],
200 ipAddress[1],
201 ipAddress[2],
202 ipAddress[3],
203 });
204
205 return MakeEpmTower({interfaceFloor, udpFloor, portFloor, ipFloor});
206 }
207
213 bool IsZeroUuid(const std::array<std::uint8_t, uuidLenght>& uuid)
214 {
215 return std::ranges::all_of(uuid, [](std::uint8_t value)
216 {
217 return value == 0;
218 });
219 }
220
228 Bytes MakeEpmLookupBody(const std::array<std::uint8_t, uuidLenght>& objectUuid, const std::string& annotation, const Bytes& tower)
229 {
230 Bytes body;
231 // wire::PutLittleEndian<std::uint32_t>(body, 0);
232
233 wire::PutLittleEndian<std::uint32_t>(body, 0); // entry_handle
234 wire::PutLittleEndian<std::uint32_t>(body, 1); // num_entries
235
236 wire::PutLittleEndian<std::uint32_t>(body, 1); // max_count
237 wire::PutLittleEndian<std::uint32_t>(body, 0); // offset
238 wire::PutLittleEndian<std::uint32_t>(body, 1); // actual_count
239
240 wire::PutFixed(body, objectUuid);
241
242 wire::PutLittleEndian<std::uint32_t>(body, 0); // tower pointer
243
244 wire::PutLittleEndian(body, static_cast<std::uint32_t>(annotation.size() + 1));
245
246 wire::PutFixed(body, annotation);
247
248 body.push_back(0);
249
250 while ((body.size() & 3U) != 0U)
251 {
252 body.push_back(0);
253 }
254
255 wire::PutLittleEndian(body, static_cast<std::uint32_t>(tower.size()));
256
257 body.insert(body.end(), tower.begin(), tower.end());
258
259 while ((body.size() & 3U) != 0U)
260 {
261 body.push_back(0);
262 }
263
264 return body;
265 }
266
273 {
274 Bytes response;
275 response.reserve(PNRPCHeader::kFixedSize + body.size());
276
277 response.push_back(4);
278 response.push_back(PNRPCHeader::RESPONSE);
279 response.push_back(0x00);
280 response.push_back(0x00);
281
282 response.push_back(0x10);
283 response.push_back(0x00);
284 response.push_back(0x00);
285 response.push_back(0x00);
286
287 response.insert(response.end(), 16, 0x00);
288 response.insert(response.end(), 16, 0x00);
289 response.insert(response.end(), 16, 0x00);
290
291 wire::PutLittleEndian<std::uint32_t>(response, 0);
292 wire::PutLittleEndian<std::uint32_t>(response, 3);
293 wire::PutLittleEndian<std::uint32_t>(response, 0);
294
295 wire::PutLittleEndian<std::uint16_t>(response, EPM_LOOKUP);
298 wire::PutLittleEndian(response, static_cast<std::uint16_t>(body.size()));
299 wire::PutLittleEndian<std::uint16_t>(response, 0);
300 response.push_back(0);
301 response.push_back(0);
302 wire::PutFixed(response, body);
303
304 return response;
305 }
306
312 void ReplyToEpmLookup(asio::ip::udp::socket& server, const Bytes& response, Bytes& request)
313 {
314 std::array<std::uint8_t, 4096> buffer{};
315 asio::ip::udp::endpoint sender;
316
317 const std::size_t received = server.receive_from(asio::buffer(buffer), sender);
318
319 request.assign(buffer.begin(), buffer.begin() + received);
320
321 server.send_to(asio::buffer(response), sender);
322 }
323
332 {
333 PNDCPBlockRequest block;
334 block.option = option;
335 block.suboption = suboption;
336 block.length = static_cast<std::uint16_t>(payload.size());
337 block.payload = payload;
338
339 Bytes result = block.ToBytes();
340
341 if ((payload.size() % 2U) != 0U)
342 {
343 result.push_back(0);
344 }
345
346 return result;
347 }
348
358 Bytes MakeDcpResponseFrame(const MacAddress& src, const MacAddress& dst, DcpServiceId serviceId, std::uint32_t xid, const Bytes& payload)
359 {
360 PNDCPHeader dcp;
362 dcp.serviceId = serviceId;
364 dcp.xId = xid;
365 dcp.respDelay = 0;
366 dcp.length = static_cast<std::uint16_t>(payload.size());
367 dcp.payload = payload;
368
369 EthernetHeader eth;
370 eth.dst = dst;
371 eth.src = src;
373 eth.payload = dcp.ToBytes();
374
375 return eth.ToBytes();
376 }
377
385 Bytes MakeStationNameResponse(const MacAddress& deviceMac, const MacAddress& controllerMac, const std::string& stationName)
386 {
387 const Bytes block = MakeDcpBlock(DcpOption::Device, PNDCPBlock::NAME_OF_STATION.second, Bytes(stationName.begin(), stationName.end()));
388
389 return MakeDcpResponseFrame(deviceMac, controllerMac, DcpServiceId::Get, 0x12345678, block);
390 }
391
400 Bytes MakeDeviceResponse(const MacAddress& deviceMac, const MacAddress& controllerMac, const std::string& stationName, const Bytes& additionalBlocks = {})
401 {
402 Bytes payload = MakeDcpBlock(DcpOption::Device, PNDCPBlock::NAME_OF_STATION.second, Bytes(stationName.begin(), stationName.end()));
403
404 payload.insert(payload.end(), additionalBlocks.begin(), additionalBlocks.end());
405
406 return MakeDcpResponseFrame(deviceMac, controllerMac, DcpServiceId::Get, 0x12345678, payload);
407 }
408
414 std::map<dcp::BlockKey, std::vector<std::uint8_t>> MakeStationBlocks(const std::string& stationName)
415 {
416 return {
417 {
418 dcp::MakeBlockKey(static_cast<std::uint16_t>(DcpOption::Device), PNDCPBlock::NAME_OF_STATION.second),
419 std::vector<std::uint8_t>(stationName.begin(), stationName.end()),
420 },
421 };
422 }
423
431 {
432 return conn->CreateNrd(payload);
433 }
434
442 {
443 return conn->BuildAlarmCrBlock(transport, priority);
444 }
445
450 int InvokeParseAlarmCrResponse(const Bytes& responseData)
451 {
452 return conn->ParseAlarmCrResponse(responseData);
453 }
454
458 std::unique_ptr<RPCCon> MakeTestRpcConnection()
459 {
460 return std::make_unique<RPCCon>(MakeRpcTestDeviceInfo());
461 }
462
467 Bytes MakeAlarmCrResponseBlock(std::uint16_t localAlarmReference)
468 {
469 PNAlarmCRBlockRes response;
470
471 PNBlockHeader header;
472 header.blockType = BlockType::AlarmCrResponse; // PNAlarmCRBlockRes::BLOCK_TYPE;
473 header.blockLength = static_cast<std::uint16_t>(PNAlarmCRBlockRes::kSize - 4);
474 header.blockVersionHigh = 0x01;
475 header.blockVersionLow = 0x00;
476
477 const Bytes headerBytes = header.ToBytes();
478
479 std::ranges::copy(headerBytes, response.blockHeader.begin());
480
481 response.localAlarmReference = localAlarmReference;
482
483 return response.ToBytes();
484 }
485
491 Bytes MakeBlockHeader(BlockType blockType, std::uint16_t blockLength)
492 {
493 PNBlockHeader header;
494 header.blockType = blockType;
495 header.blockLength = blockLength;
496 header.blockVersionHigh = 0x01;
497 header.blockVersionLow = 0x00;
498
499 return header.ToBytes();
500 }
501
503 class TestRpcTransport final : public IRpcTransport
504 {
505 public:
506 using ResponseHandler = std::function<Bytes(const Bytes&)>;
507
510
512 std::deque<Bytes> responses;
513
515 std::vector<Bytes> receivedRequests;
516
518 std::vector<Bytes> sentRequests;
519
521 std::string openedAddress;
522
524 std::uint16_t openedPort = 0;
525
527 bool openedAsListener = false;
528
530 std::size_t openCount = 0;
531
533 std::size_t receiveCount = 0;
534
536 std::size_t sendToCount = 0;
537
539 std::size_t closeCount = 0;
540
542 asio::ip::udp::endpoint lastSendToEndpoint;
543
545 asio::ip::udp::endpoint receiveEndpoint;
546
549
551 std::deque<Bytes> incomingDatagrams;
552
554 std::exception_ptr receiveException;
555
557 std::exception_ptr sendException;
558
564 void Open(const std::string& address, std::uint16_t port, bool isListener = false) override
565 {
566 openedAddress = address;
567 openedPort = port;
568 openedAsListener = isListener;
569 ++openCount;
570 }
571
577 Bytes Receive(std::chrono::milliseconds, asio::ip::udp::endpoint& remoteEndpoint) override
578 {
579 ++receiveCount;
581 {
582 std::rethrow_exception(receiveException);
583 }
585 if (!incomingDatagrams.empty())
586 {
587 Bytes result = std::move(incomingDatagrams.front());
588 incomingDatagrams.pop_front();
589 return result;
590 }
591 return receiveData;
592 }
593
597 void QueueIncomingDatagram(const Bytes& data)
598 {
599 incomingDatagrams.push_back(data);
600 }
601
606 void SendTo(const Bytes& data, const asio::ip::udp::endpoint& targetEndpoint) override
607 {
608 sentRequests.push_back(data);
609 lastSendToEndpoint = targetEndpoint;
610 ++sendToCount;
611 }
612
613 Bytes SendReceive(const Bytes& request, std::chrono::milliseconds) override
614 {
615 receivedRequests.push_back(request);
616
617 if (responseHandler)
618 {
619 return responseHandler(request);
620 }
621
622 if (responses.empty())
623 {
624 throw RPCError("TestRpcTransport: no response configured");
625 }
626
627 Bytes response = responses.front();
628 responses.pop_front();
629 return response;
630 }
631
632 void Send(const Bytes& request) override
633 {
634 if (sendException)
635 {
636 std::rethrow_exception(sendException);
637 }
638 sentRequests.push_back(request);
639 }
640
642 void Close() noexcept override
643 {
644 ++closeCount;
645 }
646 };
647
652 std::unique_ptr<RPCCon> MakeTestTransportRpcConnection(std::unique_ptr<IRpcTransport> transport, std::unique_ptr<IRpcTransport> ctransport = nullptr)
653 {
654 RPCConOptions options;
655 options.timeoutSec = 1.0;
656 options.rpcPort = 0;
657
658 return std::make_unique<RPCCon>(MakeRpcTestDeviceInfo(), options, std::move(transport), std::move(ctransport));
659 }
660
667 Bytes MakeRpcResponseBytes(std::uint8_t packetType = PNRPCHeader::RESPONSE, std::uint16_t operationNumber = 0, const Bytes& payload = {})
668 {
669 PNRPCHeader response;
670 response.packetType = packetType;
671 response.operationNumber = operationNumber;
672 response.lengthOfBody = static_cast<std::uint16_t>(payload.size());
673 response.payload = payload;
674
675 return response.ToBytes();
676 }
677
683 {
684 PNNRDData nrd;
685 nrd.argsMaximumStatus = 0;
686 nrd.argsLength = static_cast<std::uint32_t>(payload.size());
687 nrd.maximumCount = 1500;
688 nrd.offset = 0;
689 nrd.actualCount = static_cast<std::uint32_t>(payload.size());
690 nrd.payload = payload;
691
692 return nrd.ToBytes();
693 }
694
700 Bytes MakeNrdRpcResponseBytes(std::uint16_t operationNumber, const Bytes& payload = {})
701 {
704 operationNumber,
706 }
707
713 Bytes MakePnioErrorNrdResponse(std::uint32_t status, const Bytes& payload = {})
714 {
715 PNNRDData nrd;
716 nrd.argsMaximumStatus = status;
717 nrd.argsLength = static_cast<std::uint32_t>(payload.size());
718 nrd.maximumCount = 1500;
719 nrd.offset = 0;
720 nrd.actualCount = static_cast<std::uint32_t>(payload.size());
721 nrd.payload = payload;
722
723 return nrd.ToBytes();
724 }
725
734 Bytes MakeIodResponsePayload(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot,
735 std::uint16_t idx, const Bytes& payload)
736 {
737 PNBlockHeader block;
739 block.blockLength = static_cast<std::uint16_t>(PNIODHeader::kFixedSize - 4);
740 block.blockVersionHigh = 0x01;
741 block.blockVersionLow = 0x00;
742
743 PNIODHeader iod;
744 const Bytes blockBytes = block.ToBytes();
745 std::ranges::copy(blockBytes, iod.blockHeader.begin());
746
747 iod.sequenceNumber = 0;
748 iod.api = api;
749 iod.slot = slot;
750 iod.subslot = subslot;
751 iod.index = idx;
752 iod.length = static_cast<std::uint32_t>(payload.size());
753 iod.payload = payload;
754
755 return iod.ToBytes();
756 }
757
762 Bytes MakeImPayload(std::uint16_t index)
763 {
764 std::size_t size = 6;
765
766 switch (index)
767 {
768 case PNInM0::IDX:
769 size = PNInM0::kSize;
770 break;
771
772 case PNInM1::IDX:
773 size = PNInM1::kSize;
774 break;
775
776 case PNInM2::IDX:
777 size = PNInM2::kSize;
778 break;
779
780 case PNInM3::IDX:
781 size = PNInM3::kSize;
782 break;
783
784 case PNInM4::IDX:
785 size = PNInM4::kSize;
786 break;
787
788 case PNInM5::IDX:
789 size = PNInM5::kSize;
790 break;
791
792 default:
793 size = 6;
794 break;
795 }
796
797 return Bytes(size, 0);
798 }
799
804 {
805 Bytes data;
806
807 wire::PutU16(data, 0x0010); // DiagnosisData block type
808 wire::PutU16(data, 14); // block length
809 wire::PutU8(data, 1);
810 wire::PutU8(data, 0);
811
812 wire::PutU16(data, 1); // channel
813 wire::PutU16(data, 0); // channel properties
814 wire::PutU16(data, 0x8000); // ChannelDiagnosis USI
815 wire::PutU16(data, 0x0006); // Line break
816
817 return data;
818 }
819
824 {
825 Bytes data;
826
827 wire::PutU16(data, 1); // number of modules
828 wire::PutU16(data, 1); // slot
829 wire::PutU32(data, 0x42); // module ident
830 wire::PutU16(data, 1); // number of subslots
831 wire::PutU16(data, 1); // subslot
832 wire::PutU32(data, 0x99); // submodule ident
833
834 return data;
835 }
836
841 {
842 Bytes body;
843
844 wire::PutU16(body, 1); // number of APIs
845 wire::PutU32(body, 0); // API
846 wire::PutU16(body, 1); // number of modules
847 wire::PutU16(body, 1); // slot
848 wire::PutU32(body, 0x42); // module ident
850 wire::PutU16(body, 1); // number of submodules
851 wire::PutU16(body, 1); // subslot
852 wire::PutU32(body, 0x99); // submodule ident
854
855 Bytes data = MakeBlockHeader(BlockType::ModuleDifference, static_cast<std::uint16_t>(body.size() + 2));
856
857 data.insert(data.end(), body.begin(), body.end());
858 return data;
859 }
860
865 {
866 Bytes data(64, 0);
867
868 // Record data length at offset 36.
869 data[36] = 0x00;
870 data[37] = 0x00;
871 data[38] = 0x00;
872 data[39] = 0x38; // 56-byte response block
873
874 wire::PutU16(data, 0x8008); // block type
875 wire::PutU16(data, 52); // block length
876 wire::PutU8(data, 1);
877 wire::PutU8(data, 0);
878
879 wire::PutU16(data, 0); // sequence
880 data.insert(data.end(), uuidLenght, 0);
881 wire::PutU32(data, 0); // API
882 wire::PutU16(data, 1); // slot
883 wire::PutU16(data, 1); // subslot
884 wire::PutU16(data, 0); // padding
885 wire::PutU16(data, 0xAFF1); // index
886 wire::PutU32(data, 0); // record data length
887 wire::PutU16(data, 0); // additional value 1
888 wire::PutU16(data, 0); // additional value 2
889 wire::PutU32(data, 0); // status
890
891 return data;
892 }
899 Bytes MakeIocrResponseBlock(std::uint16_t iocrType, std::uint16_t iocrReference, std::uint16_t frameId)
900 {
901 Bytes data;
902
905 wire::PutU8(data, 1);
906 wire::PutU8(data, 0);
907 wire::PutU16(data, iocrType);
908 wire::PutU16(data, iocrReference);
909 wire::PutU16(data, frameId);
910
911 return data;
912 }
913 std::uint16_t InvokeParseIocrResponse(const Bytes& responseData, int iocrType)
914 {
915 return RPCCon::ParseIocrResponse(responseData, iocrType);
916 }
917
918 std::unique_ptr<RPCCon> conn;
919 std::unique_ptr<TestRpcTransport> transport;
921
922 std::unique_ptr<TestRpcTransport> ctransport;
924
926 {
927 return conn->SendReceive(request);
928 }
929 void InvokeSetRPCConLive(bool live)
930 {
931 conn->live = live;
932 }
933 void InvokeSetRPCConArUuid(std::uint8_t byte)
934 {
935 conn->arUuid.fill(byte);
936 }
937
938 void InvokeSetRPCConArUuid(const std::array<std::uint8_t, uuidLenght>& bytes)
939 {
940 conn->arUuid = bytes;
941 }
942
943 void InvokeSetRPCConSessionKey(std::uint16_t byte)
944 {
945 conn->sessionKey = byte;
946 }
947 void InvokeSetRPCConInfo(std::uint8_t deviceHigh, std::uint8_t deviceLow,
948 std::uint8_t vendorHigh, std::uint8_t vendorLow)
949 {
950 conn->info.deviceHigh = deviceHigh;
951 conn->info.deviceLow = deviceLow;
952 conn->info.vendorHigh = vendorHigh;
953 conn->info.vendorLow = vendorLow;
954 conn->remoteObjectUuid = BuildObjectUuid(0x00, 0x01, conn->info.deviceHigh, conn->info.deviceLow, conn->info.vendorHigh, conn->info.vendorLow);
955 }
956
957 void InvokeSetRPCConSrcMac(std::string mac)
958 {
959 conn->srcMac = String2Mac(mac);
960 }
961
962 void InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::time_point time)
963 {
964 conn->liveMonotonic = time;
965 }
967 {
968 conn->CheckTimeout();
969 }
971 {
972 return conn->live;
973 }
974
975 std::optional<MacAddress> InvokeGetRPCConSrcMac()
976 {
977 return conn->srcMac;
978 }
980 bool waitResponse = true, const Bytes& subBlocks = {})
981 {
982 return conn->SendControl(blockType, controlCommand, waitResponse, subBlocks);
983 }
984
1001 Bytes MakeRawRpcHeader(bool littleEndian, std::uint8_t packetType, std::uint32_t interfaceVersion,
1002 std::uint32_t sequenceNumber, std::uint16_t operationNumber, const Bytes& payload)
1003 {
1004 Bytes data;
1005 data.reserve(PNRPCHeader::kFixedSize + payload.size());
1006
1007 wire::PutU8(data, 0x04);
1008 wire::PutU8(data, packetType);
1009 wire::PutU8(data, 0x01);
1010 wire::PutU8(data, 0x02);
1011
1012 // DREP.
1013 wire::PutU8(data, littleEndian ? 0x10 : 0x00);
1014 wire::PutU8(data, 0x00);
1015 wire::PutU8(data, 0x00);
1016 wire::PutU8(data, 0x5A);
1017
1018 std::array<std::uint8_t, uuidLenght> objectUuid{};
1019 std::array<std::uint8_t, uuidLenght> interfaceUuid{};
1020 std::array<std::uint8_t, uuidLenght> activityUuid{};
1021
1022 for (std::size_t i = 0U; i < uuidLenght; ++i)
1023 {
1024 objectUuid[i] = static_cast<std::uint8_t>(0x10U + i);
1025 interfaceUuid[i] = static_cast<std::uint8_t>(0x20U + i);
1026 activityUuid[i] = static_cast<std::uint8_t>(0x30U + i);
1027 }
1028
1029 wire::PutFixed(data, objectUuid);
1030 wire::PutFixed(data, interfaceUuid);
1031 wire::PutFixed(data, activityUuid);
1032
1033 // Bytes 56-59: server_boot_time.
1034 data.insert(data.end(), 4U, 0U);
1035
1036 if (littleEndian)
1037 {
1038 wire::PutLittleEndian(data, interfaceVersion);
1039 wire::PutLittleEndian(data, sequenceNumber);
1040 wire::PutLittleEndian(data, operationNumber);
1041 }
1042 else
1043 {
1044 wire::PutBigEndian(data, interfaceVersion);
1045 wire::PutBigEndian(data, sequenceNumber);
1046 wire::PutBigEndian(data, operationNumber);
1047 }
1048
1049 // Bytes 70-79: interface_hint, activity_hint, args_len, fragnum,
1050 // auth_proto and remaining fixed header fields.
1051 data.insert(data.end(), PNRPCHeader::kFixedSize - data.size(), 0U);
1052
1053 wire::PutFixed(data, payload);
1054
1055 return data;
1056 }
1057
1065 {
1066 PNBlockHeader block;
1067 block.blockType = static_cast<BlockType>(blockType);
1068 block.blockLength = static_cast<std::uint16_t>(PNIODReleaseBlock::kSize - 4);
1069 block.blockVersionHigh = 0x01;
1070 block.blockVersionLow = 0x00;
1071
1072 PNIODReleaseBlock control;
1073 const Bytes blockBytes = block.ToBytes();
1074 std::ranges::copy(blockBytes, control.blockHeader.begin());
1075
1076 control.padding1 = 0;
1077 control.arUuid = {};
1078 control.sessionKey = 0x1234;
1079 control.padding2 = 0;
1080 control.controlCommand = controlCommand;
1081 control.controlBlockProperties = 0;
1082
1083 return control.ToBytes();
1084 }
1085
1091 Bytes MakeApplicationReadyNrd(const Bytes& block, bool littleEndian)
1092 {
1093 Bytes nrd;
1094
1095 const auto blockLength = static_cast<std::uint32_t>(block.size());
1096
1097 if (littleEndian)
1098 {
1099 wire::PutLittleEndian<std::uint32_t>(nrd, 0);
1100 wire::PutLittleEndian(nrd, blockLength);
1101 wire::PutLittleEndian(nrd, blockLength);
1102 wire::PutLittleEndian<std::uint32_t>(nrd, 0);
1103 wire::PutLittleEndian(nrd, blockLength);
1104 }
1105 else
1106 {
1107 wire::PutU32(nrd, 0);
1108 wire::PutU32(nrd, blockLength);
1109 wire::PutU32(nrd, blockLength);
1110 wire::PutU32(nrd, 0);
1111 wire::PutU32(nrd, blockLength);
1112 }
1113
1114 nrd.insert(nrd.end(), block.begin(), block.end());
1115 return nrd;
1116 }
1117
1126 std::uint16_t blockType = BLOCK_IOD_CONTROL_APP_READY_REQ,
1128 {
1129 const Bytes controlBlock = MakeApplicationReadyControlBlock(blockType, controlCommand);
1130
1131 const Bytes nrd = MakeApplicationReadyNrd(controlBlock, littleEndian);
1132
1133 return MakeRawRpcHeader(littleEndian,
1135 0x11223344,
1136 0x55667788,
1138 nrd);
1139 }
1140};
1141
1144TEST_F(RpcTests, UuidBytesToStringStringToUuidBytesRoundtrip)
1145{
1146 auto readBytes = StringToUuidBytes("dea00001-6c97-11d1-8271-00a02442df7d");
1147
1148 const std::string back = UuidBytesToString(readBytes);
1149
1150 EXPECT_EQ(back, "dea00001-6c97-11d1-8271-00a02442df7d");
1151}
1152
1155TEST_F(RpcTests, StringToUuidBytesRejectsMalformedInput)
1156{
1157 EXPECT_THROW(StringToUuidBytes("not-a-uuid"), std::exception);
1158
1159 EXPECT_THROW(StringToUuidBytes("dea00001-6c97-11d1-8271"), std::exception);
1160}
1161
1164TEST_F(RpcTests, EPMEndpointResolvesWellKnownProfinetInterfaceNames)
1165{
1167
1169 EXPECT_EQ(ep.InterfaceName(), "PNIO-Device");
1170
1172 EXPECT_EQ(ep.InterfaceName(), "PNIO-Controller");
1173
1174 ep.interfaceUuid = "00000000-0000-0000-0000-000000000000";
1175
1176 EXPECT_EQ(ep.InterfaceName().rfind("Unknown(", 0), 0U);
1177}
1178
1181TEST_F(RpcTests, EPMEndpointUnknownUuidIncludesUuid)
1182{
1184
1185 ep.interfaceUuid = "11111111-2222-3333-4444-555555555555";
1186
1187 EXPECT_EQ(ep.InterfaceName(), "Unknown(11111111-2222-3333-4444-555555555555)");
1188}
1189
1192TEST_F(RpcTests, EPMEndpointInterfaceNameIsCaseInsensitive)
1193{
1195
1196 ep.interfaceUuid = "DEA00001-6C97-11D1-8271-00A02442DF7D";
1197
1198 EXPECT_EQ(ep.InterfaceName(), "PNIO-Device");
1199}
1200
1202TEST_F(RpcTests, IOCRSetupCycleTimeCalculation)
1203{
1205 setup.sendClockFactor = 32;
1206 setup.reductionRatio = 32;
1207
1208 // cycle_time_us = reduction_ratio * send_clock_factor * 125 / 4
1209 // = 32 * 32 * 125 / 4 = 32000 us = 32ms
1210 EXPECT_EQ(setup.CycleTimeUs(), static_cast<std::uint32_t>(32000));
1211
1212 EXPECT_GT(setup.CycleTimeMs(), 31.9);
1213 EXPECT_LT(setup.CycleTimeMs(), 32.1);
1214}
1215
1217TEST_F(RpcTests, IOCRSetupValidateFlagsEmptySlotsAndLowWatchdog)
1218{
1220 setup.watchdogFactor = 1;
1221
1222 auto warnings = setup.Validate();
1223
1224 EXPECT_FALSE(warnings.empty());
1225}
1226
1228TEST_F(RpcTests, IOCRSetupValidateEmptySlots)
1229{
1231
1232 setup.watchdogFactor = 6;
1233
1234 const auto warnings = setup.Validate();
1235
1236 EXPECT_NE(std::ranges::find(warnings, "No IO slots configured"), warnings.end());
1237}
1238
1240TEST_F(RpcTests, IOCRSetupValidateLowWatchdog)
1241{
1243
1244 setup.watchdogFactor = 1;
1245
1246 const auto warnings = setup.Validate();
1247
1248 EXPECT_NE(std::ranges::find(warnings, "Watchdog factor too low (use 6+)"), warnings.end());
1249}
1250
1252TEST_F(RpcTests, IOCRSetupValidateNormalConfiguration)
1253{
1255
1256 setup.sendClockFactor = 32;
1257 setup.reductionRatio = 32;
1258 setup.watchdogFactor = 6;
1259
1260 setup.slots.push_back({});
1261
1262 const auto warnings = setup.Validate();
1263
1264 EXPECT_TRUE(warnings.empty());
1265}
1266
1268TEST_F(RpcTests, CreateRpcBuildsExpectedHeader)
1269{
1270 // profinet::dcp::DCPDeviceDescription info(profinet::String2Mac("aa:bb:cc:dd:ee:ff"), {});
1271 // profinet::rpc::RPCCon conn(info);
1272
1273 const profinet::Bytes payload{0x01, 0x02, 0x03};
1274
1275 const auto rpc = CreateRpc(payload);
1276
1277 EXPECT_EQ(rpc.version, 4);
1278 EXPECT_EQ(rpc.packetType, profinet::PNRPCHeader::REQUEST);
1279 EXPECT_EQ(rpc.flags1, 0x20);
1280 EXPECT_EQ(rpc.interfaceVersion, 1);
1281 EXPECT_EQ(rpc.operationNumber, PNRPCHeader::CONNECT);
1282 EXPECT_EQ(rpc.interfaceHint, 0xFFFF);
1283 EXPECT_EQ(rpc.activityHint, 0xFFFF);
1284 EXPECT_EQ(rpc.lengthOfBody, payload.size());
1285 EXPECT_EQ(rpc.payload, payload);
1286}
1287
1289TEST_F(RpcTests, PNRPCHeaderInterfaceUuidBytes)
1290{ // PROFINET interface UUIDs are embedded on the wire as a literal byte
1291 // sequence matching their canonical hex string directly (unlike EPM
1292 // tower UUIDs, which use genuine DCE/RPC mixed-endian NDR encoding).
1293 auto deviceUuid = profinet::PNRPCHeader::IfaceUuidDevice();
1294
1295 EXPECT_EQ(ToHex(deviceUuid.data(), deviceUuid.size()), "dea000016c9711d1827100a02442df7d");
1296}
1297
1300TEST_F(RpcTests, BuildIocrBlockUsesConfiguredSlots)
1301{
1302 IOCRSetup setup;
1303
1304 setup.slots = {
1305 {
1306 .slot = 0,
1307 .subslot = 0x0001,
1308 .inputLength = 0,
1309 .outputLength = 0,
1310 .moduleIdent = 0x80010000,
1311 .submoduleIdent = 0x00000001,
1312 },
1313 {
1314 .slot = 0,
1315 .subslot = 0x0002,
1316 .inputLength = 0,
1317 .outputLength = 0,
1318 .moduleIdent = 0x80010000,
1319 .submoduleIdent = 0x00000010,
1320 },
1321 {
1322 .slot = 0,
1323 .subslot = 0x0003,
1324 .inputLength = 0,
1325 .outputLength = 0,
1326 .moduleIdent = 0x80010000,
1327 .submoduleIdent = 0x00000010,
1328 },
1329 {
1330 .slot = 0,
1331 .subslot = 0x0004,
1332 .inputLength = 0,
1333 .outputLength = 0,
1334 .moduleIdent = 0x80010000,
1335 .submoduleIdent = 0x00000010,
1336 },
1337 {
1338 .slot = 0,
1339 .subslot = 0x8000,
1340 .inputLength = 0,
1341 .outputLength = 0,
1342 .moduleIdent = 0x80010000,
1343 .submoduleIdent = 0x00000002,
1344 },
1345 {
1346 .slot = 0,
1347 .subslot = 0x8001,
1348 .inputLength = 0,
1349 .outputLength = 0,
1350 .moduleIdent = 0x80010000,
1351 .submoduleIdent = 0x00000003,
1352 },
1353 {
1354 .slot = 0,
1355 .subslot = 0x8002,
1356 .inputLength = 0,
1357 .outputLength = 0,
1358 .moduleIdent = 0x80010000,
1359 .submoduleIdent = 0x00000003,
1360 },
1361 {
1362 .slot = 1,
1363 .subslot = 0x0001,
1364 .inputLength = 34,
1365 .outputLength = 0,
1366 .moduleIdent = 0x00008000,
1367 .submoduleIdent = 0x00002200,
1368 },
1369 {
1370 .slot = 2,
1371 .subslot = 0x0001,
1372 .inputLength = 0,
1373 .outputLength = 16,
1374 .moduleIdent = 0x00008100,
1375 .submoduleIdent = 0x10001000,
1376 },
1377 };
1378
1379 const Bytes input = InvokeBuildIocrBlock(IocrType::Input, setup);
1380
1381 const Bytes output = InvokeBuildIocrBlock(IocrType::Output, setup);
1382
1383 // Parse the fixed headers.
1384 const auto inputHeader = PNIOCRBlockReqHeader::Parse(input);
1385
1386 const auto outputHeader = PNIOCRBlockReqHeader::Parse(output);
1387
1388 EXPECT_EQ(inputHeader.iocrType, 1);
1389 EXPECT_EQ(outputHeader.iocrType, 2);
1390
1391 EXPECT_EQ(inputHeader.dataLength, 43);
1392 EXPECT_EQ(outputHeader.dataLength, 40);
1393
1394 EXPECT_EQ(inputHeader.numberOfApis, 1);
1395 EXPECT_EQ(outputHeader.numberOfApis, 1);
1396}
1399TEST_F(RpcTests, GenerateNonZeroArUuid)
1400{
1401 const auto info = MakeRpcTestDeviceInfo();
1402
1403 RPCCon conn(info);
1404
1405 const auto& arUuid = conn.ArUuid();
1406
1407 EXPECT_FALSE(IsAllZeroUuid(arUuid));
1408 EXPECT_EQ(arUuid.size(), uuidLenght);
1409}
1410
1413TEST_F(RpcTests, GenerateDifferentArUuids)
1414{
1415 const auto info = MakeRpcTestDeviceInfo();
1416
1417 RPCCon first(info);
1418 RPCCon second(info);
1419
1420 EXPECT_FALSE(IsAllZeroUuid(first.ArUuid()));
1421 EXPECT_FALSE(IsAllZeroUuid(second.ArUuid()));
1422
1423 // Two independently generated 128-bit random UUIDs should not match.
1424 EXPECT_NE(first.ArUuid(), second.ArUuid());
1425}
1426
1429TEST_F(RpcTests, GenerateIndependentArAndActivityUuids)
1430{
1431 const auto rpc = CreateRpc({});
1432
1433 EXPECT_FALSE(IsAllZeroUuid(conn->ArUuid()));
1434 EXPECT_FALSE(IsAllZeroUuid(rpc.activityUuid));
1435 EXPECT_NE(conn->ArUuid(), rpc.activityUuid);
1436}
1437
1440TEST_F(RpcTests, GenerateNonZeroSessionKey)
1441{
1442 const auto info = MakeRpcTestDeviceInfo();
1443
1444 RPCCon conn(info);
1445
1446 EXPECT_NE(conn.SessionKey(), static_cast<std::uint16_t>(0));
1447}
1448
1451TEST_F(RpcTests, GeneratedSessionKeysAreAlwaysNonZero)
1452{
1453 const auto info = MakeRpcTestDeviceInfo();
1454
1455 constexpr int connectionCount = 32;
1456
1457 for (int i = 0; i < connectionCount; ++i)
1458 {
1459 RPCCon conn(info);
1460
1461 EXPECT_NE(conn.SessionKey(), static_cast<std::uint16_t>(0));
1462 }
1463}
1464
1466TEST_F(RpcTests, BuildsExpectedRemoteObjectUuid)
1467{
1468 const auto rpc = CreateRpc({});
1469
1470 EXPECT_EQ(ToHex(rpc.objectUuid.data(), rpc.objectUuid.size()), "dea000006c9711d18271000112345678");
1471}
1472
1475TEST_F(RpcTests, BuildObjectUuidPreservesDeviceAndVendorBytes)
1476{
1477 InvokeSetRPCConInfo(0xA1, 0xB2, 0xC3, 0xD4);
1478
1479 const auto rpc = CreateRpc({});
1480
1481 EXPECT_EQ(ToHex(rpc.objectUuid.data(), rpc.objectUuid.size()), "dea000006c9711d182710001a1b2c3d4");
1482}
1483
1486TEST_F(RpcTests, BuildObjectUuidUsesFixedProfinetPrefixAndSuffix)
1487{
1488 InvokeSetRPCConInfo(0xAA, 0xBB, 0xCC, 0xDD);
1489 const auto rpc = CreateRpc({});
1490
1491 const std::string uuidHex = ToHex(rpc.objectUuid.data(), rpc.objectUuid.size());
1492
1493 EXPECT_EQ(uuidHex.substr(0, 20), "dea000006c9711d18271");
1494 EXPECT_EQ(uuidHex.substr(20), "0001aabbccdd");
1495}
1496
1499TEST_F(RpcTests, RemoteObjectUuidHasExpectedLength)
1500{
1501 const auto rpc = CreateRpc({});
1502
1503 EXPECT_EQ(rpc.objectUuid.size(), uuidLenght);
1504}
1505
1507TEST_F(RpcTests, EPMEndpointInterfaceNamePnioDevice)
1508{
1509 EPMEndpoint endpoint;
1511
1512 EXPECT_EQ(endpoint.InterfaceName(), "PNIO-Device");
1513}
1514
1516TEST_F(RpcTests, EPMEndpointInterfaceNamePnioController)
1517{
1518 EPMEndpoint endpoint;
1520
1521 EXPECT_EQ(endpoint.InterfaceName(), "PNIO-Controller");
1522}
1523
1525TEST_F(RpcTests, EPMEndpointInterfaceNamePnioSupervisor)
1526{
1527 EPMEndpoint endpoint;
1528 endpoint.interfaceUuid = "dea00003-6c97-11d1-8271-00a02442df7d";
1529
1530 EXPECT_EQ(endpoint.InterfaceName(), "PNIO-Supervisor");
1531}
1532
1534TEST_F(RpcTests, EPMEndpointInterfaceNamePnioParameterServer)
1535{
1536 EPMEndpoint endpoint;
1537 endpoint.interfaceUuid = "dea00004-6c97-11d1-8271-00a02442df7d";
1538
1539 EXPECT_EQ(endpoint.InterfaceName(), "PNIO-ParameterServer");
1540}
1541
1543TEST_F(RpcTests, EPMEndpointInterfaceNameEpm)
1544{
1545 EPMEndpoint endpoint;
1546 endpoint.interfaceUuid = UUID_EPM_V4;
1547
1548 EXPECT_EQ(endpoint.InterfaceName(), "EPM");
1549}
1550
1552TEST_F(RpcTests, EPMEndpointInterfaceNameUnknown)
1553{
1554 EPMEndpoint endpoint;
1555 endpoint.interfaceUuid = "11111111-2222-3333-4444-555555555555";
1556
1557 EXPECT_EQ(endpoint.InterfaceName(), "Unknown(11111111-2222-3333-4444-555555555555)");
1558}
1559
1561TEST_F(RpcTests, EPMEndpointInterfaceNameIsCaseInsensitiveForAllKnownInterfaces)
1562{
1563 const std::array<std::pair<std::string_view, std::string_view>, 6> cases = {{{UUID_PNIO_DEVICE, "PNIO-Device"}, {UUID_PNIO_CONTROLLER, "PNIO-Controller"}, {UUID_PNIO_SUPERVISOR, "PNIO-Supervisor"}, {UUID_PNIO_PARAMETER_SERVER, "PNIO-ParameterServer"}, {UUID_EPM_V4, "EPM"}, {"DEA00006-6C97-11D1-8271-00A02442DF7D", "Unknown(DEA00006-6C97-11D1-8271-00A02442DF7D)"}}};
1564
1565 for (const auto& [uuid, expected] : cases)
1566 {
1567 EPMEndpoint endpoint;
1568 endpoint.interfaceUuid = uuid;
1569
1570 EXPECT_EQ(endpoint.InterfaceName(), expected);
1571 }
1572}
1573
1575TEST_F(RpcTests, ParseEpmTowerRejectsTooShortTower)
1576{
1577 EXPECT_FALSE(ParseEpmTower({}).has_value());
1578 EXPECT_FALSE(ParseEpmTower({0x01, 0x00, 0x00}).has_value());
1579}
1580
1582TEST_F(RpcTests, ParseEpmTowerStopsOnTruncatedFloorHeader)
1583{
1584 const Bytes tower = {0x01, 0x00, 0x01};
1585
1586 EXPECT_FALSE(ParseEpmTower(tower).has_value());
1587}
1588
1590TEST_F(RpcTests, ParseEpmTowerStopsOnTruncatedLhs)
1591{
1592 const Bytes tower = {0x01, 0x00, 0x05, 0x00, 0x0D, 0x01};
1593
1594 EXPECT_FALSE(ParseEpmTower(tower).has_value());
1595}
1596
1598TEST_F(RpcTests, ParseEpmTowerStopsOnTruncatedRhsLength)
1599{
1600 const Bytes tower = {0x01, 0x00, 0x01, 0x00, 0x0A};
1601
1602 EXPECT_FALSE(ParseEpmTower(tower).has_value());
1603}
1604
1606TEST_F(RpcTests, ParseEpmTowerStopsOnTruncatedRhs)
1607{
1608 const Bytes tower = {0x01, 0x00, 0x01, 0x00, 0x0A, 0x04, 0x00, 0x01};
1609
1610 EXPECT_FALSE(ParseEpmTower(tower).has_value());
1611}
1614TEST_F(RpcTests, ParseEpmTowerSkipsEmptyLhs)
1615{
1616 const Bytes interfaceFloor = MakeEpmTowerFloor(
1617 MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 3),
1618 MakeInterfaceTowerRhs(7));
1619
1620 const Bytes emptyFloor = MakeEpmTowerFloor({}, {});
1621
1622 const Bytes tower = MakeEpmTower({
1623 interfaceFloor,
1624 emptyFloor,
1625 });
1626
1627 const auto result = ParseEpmTower(tower);
1628
1629 ASSERT_TRUE(result.has_value());
1630
1631 EXPECT_EQ(result->interfaceUuid, UUID_PNIO_DEVICE);
1632 EXPECT_EQ(result->interfaceVersionMajor, 3);
1633 EXPECT_EQ(result->interfaceVersionMinor, 7);
1634}
1635
1638TEST_F(RpcTests, MakeInterfaceTowerLhsCreatesValidInterfaceBlock)
1639{
1640 const Bytes lhs = MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 3);
1641
1642 ASSERT_EQ(lhs.size(), 19U);
1643
1644 EXPECT_EQ(lhs[0], 0x0D);
1645
1646 const std::array<std::uint8_t, uuidLenght> uuidBytes = StringToUuidBytes(UUID_PNIO_DEVICE);
1647
1648 EXPECT_TRUE(std::equal(uuidBytes.begin(), uuidBytes.end(), lhs.begin() + 1));
1649
1650 EXPECT_EQ(lhs[17], 0x03);
1651 EXPECT_EQ(lhs[18], 0x00);
1652}
1653
1656TEST_F(RpcTests, ParseEpmTowerParsesInterfaceFloor)
1657{
1658 const Bytes interfaceFloor =
1659 MakeEpmTowerFloor(
1660 MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 3),
1661 MakeInterfaceTowerRhs(7));
1662
1663 const Bytes tower =
1664 MakeEpmTower({interfaceFloor});
1665
1666 const auto result =
1667 ParseEpmTower(tower);
1668
1669 ASSERT_TRUE(result.has_value());
1670
1671 EXPECT_EQ(result->interfaceUuid, UUID_PNIO_DEVICE);
1672 EXPECT_EQ(result->interfaceVersionMajor, 3);
1673 EXPECT_EQ(result->interfaceVersionMinor, 7);
1674}
1677TEST_F(RpcTests, ParseEpmTowerSkipsEmptyFloor)
1678{
1679 const Bytes emptyFloor = MakeEpmTowerFloor({}, {});
1680
1681 ASSERT_EQ(emptyFloor, (Bytes{0x00, 0x00, 0x00, 0x00}));
1682}
1683
1686TEST_F(RpcTests, MakeEpmTowerEncodesEmptyThenInterfaceFloor)
1687{
1688 const Bytes emptyFloor = MakeEpmTowerFloor({}, {});
1689
1690 const Bytes interfaceFloor = MakeEpmTowerFloor(
1691 MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 3),
1692 MakeInterfaceTowerRhs(7));
1693
1694 const Bytes tower = MakeEpmTower({emptyFloor, interfaceFloor});
1695
1696 ASSERT_GE(tower.size(), 6U);
1697
1698 EXPECT_EQ(tower[0], 0x02);
1699 EXPECT_EQ(tower[1], 0x00);
1700
1701 EXPECT_EQ(tower[2], 0x00);
1702 EXPECT_EQ(tower[3], 0x00);
1703 EXPECT_EQ(tower[4], 0x00);
1704 EXPECT_EQ(tower[5], 0x00);
1705
1706 EXPECT_EQ(tower[6], 0x13);
1707 EXPECT_EQ(tower[7], 0x00);
1708}
1709
1711TEST_F(RpcTests, ParseEpmTowerParsesInterfaceUuidAndVersions)
1712{
1713 const Bytes tower = MakeEpmTower({
1714 MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 0x1234), MakeInterfaceTowerRhs(0x5678)),
1715 });
1716
1717 const auto result = ParseEpmTower(tower);
1718
1719 ASSERT_TRUE(result.has_value());
1720 EXPECT_EQ(result->interfaceUuid, UUID_PNIO_DEVICE);
1721 EXPECT_EQ(result->interfaceVersionMajor, 0x1234);
1722 EXPECT_EQ(result->interfaceVersionMinor, 0x5678);
1723}
1724
1727TEST_F(RpcTests, ParseEpmTowerInterfaceFloorWithoutMinorVersion)
1728{
1729 const Bytes tower = MakeEpmTower({
1730 MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 5), {0x01}),
1731 });
1732
1733 const auto result = ParseEpmTower(tower);
1734
1735 ASSERT_TRUE(result.has_value());
1736 EXPECT_EQ(result->interfaceUuid, UUID_PNIO_DEVICE);
1737 EXPECT_EQ(result->interfaceVersionMajor, 5);
1738 EXPECT_EQ(result->interfaceVersionMinor, 0);
1739}
1740
1743TEST_F(RpcTests, ParseEpmTowerRejectsShortInterfaceFloor)
1744{
1745 const Bytes tower = MakeEpmTower({
1746 MakeEpmTowerFloor({0x0D, 0x01, 0x02}, {}),
1747 });
1748
1749 EXPECT_FALSE(ParseEpmTower(tower).has_value());
1750}
1751
1753TEST_F(RpcTests, ParseEpmTowerParsesUdpProtocol)
1754{
1755 const Bytes interfaceFloor = MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 1), {});
1756
1757 const Bytes udpFloor = MakeEpmTowerFloor({0x0A}, {});
1758
1759 const Bytes tower = MakeEpmTower({interfaceFloor, udpFloor});
1760
1761 const auto result = ParseEpmTower(tower);
1762
1763 ASSERT_TRUE(result.has_value());
1764 EXPECT_EQ(result->protocol, "ncadg_ip_udp");
1765}
1766
1768TEST_F(RpcTests, ParseEpmTowerParsesBigEndianPort)
1769{
1770 const Bytes interfaceFloor = MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 1), {});
1771
1772 const Bytes portFloor = MakeEpmTowerFloor({0x08}, {0x12, 0x34});
1773
1774 const Bytes tower = MakeEpmTower({interfaceFloor, portFloor});
1775
1776 const auto result = ParseEpmTower(tower);
1777
1778 ASSERT_TRUE(result.has_value());
1779 EXPECT_EQ(result->port, 0x1234);
1780}
1781
1783TEST_F(RpcTests, ParseEpmTowerIgnoresShortPortRhs)
1784{
1785 const Bytes interfaceFloor = MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 1), {});
1786
1787 const Bytes portFloor = MakeEpmTowerFloor({0x08}, {0x12});
1788
1789 const Bytes tower = MakeEpmTower({interfaceFloor, portFloor});
1790
1791 const auto result = ParseEpmTower(tower);
1792
1793 ASSERT_TRUE(result.has_value());
1794 EXPECT_EQ(result->port, 0);
1795}
1796
1798TEST_F(RpcTests, ParseEpmTowerParsesIpv4Address)
1799{
1800 const Bytes interfaceFloor = MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 1), {});
1801
1802 const Bytes ipFloor = MakeEpmTowerFloor({0x09}, {192, 168, 10, 25});
1803
1804 const Bytes tower = MakeEpmTower({interfaceFloor, ipFloor});
1805
1806 const auto result = ParseEpmTower(tower);
1807
1808 ASSERT_TRUE(result.has_value());
1809 EXPECT_EQ(result->ipAddress, "192.168.10.25");
1810}
1811
1813TEST_F(RpcTests, ParseEpmTowerIgnoresShortIpRhs)
1814{
1815 const Bytes interfaceFloor = MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 1), {});
1816
1817 const Bytes ipFloor = MakeEpmTowerFloor({0x09}, {192, 168, 10});
1818
1819 const Bytes tower = MakeEpmTower({interfaceFloor, ipFloor});
1820
1821 const auto result = ParseEpmTower(tower);
1822
1823 ASSERT_TRUE(result.has_value());
1824 EXPECT_TRUE(result->ipAddress.empty());
1825}
1826
1828TEST_F(RpcTests, ParseEpmTowerParsesCompleteEndpoint)
1829{
1830 const Bytes tower = MakeCompleteEpmTower(UUID_PNIO_CONTROLLER, 3, 7, 0x8894, {10, 250, 21, 110});
1831
1832 const auto result = ParseEpmTower(tower);
1833
1834 ASSERT_TRUE(result.has_value());
1835 EXPECT_EQ(result->interfaceUuid, UUID_PNIO_CONTROLLER);
1836 EXPECT_EQ(result->interfaceVersionMajor, 3);
1837 EXPECT_EQ(result->interfaceVersionMinor, 7);
1838 EXPECT_EQ(result->protocol, "ncadg_ip_udp");
1839 EXPECT_EQ(result->port, 0x8894);
1840 EXPECT_EQ(result->ipAddress, "10.250.21.110");
1841}
1842
1845TEST_F(RpcTests, ParseEpmTowerOnlyUsesFirstInterfaceFloorForInterfaceData)
1846{
1847 const Bytes firstInterface = MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 1), MakeInterfaceTowerRhs(2));
1848
1849 const Bytes secondInterface = MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_CONTROLLER, 3), MakeInterfaceTowerRhs(4));
1850
1851 const Bytes tower = MakeEpmTower({firstInterface, secondInterface});
1852
1853 const auto result = ParseEpmTower(tower);
1854
1855 ASSERT_TRUE(result.has_value());
1856 EXPECT_EQ(result->interfaceUuid, UUID_PNIO_DEVICE);
1857 EXPECT_EQ(result->interfaceVersionMajor, 1);
1858 EXPECT_EQ(result->interfaceVersionMinor, 2);
1859}
1860
1862TEST_F(RpcTests, ParseEpmTowerIgnoresInterfaceDataFromLaterFloor)
1863{
1864 const Bytes firstFloor = MakeEpmTowerFloor({0x0A}, {});
1865 const Bytes secondFloor = MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 1), MakeInterfaceTowerRhs(2));
1866
1867 const Bytes tower = MakeEpmTower({firstFloor, secondFloor});
1868
1869 const auto result = ParseEpmTower(tower);
1870
1871 EXPECT_FALSE(result.has_value());
1872}
1875TEST_F(RpcTests, ParseEpmTowerIgnoresUnknownProtocol)
1876{
1877 const Bytes interfaceFloor = MakeEpmTowerFloor(MakeInterfaceTowerLhs(UUID_PNIO_DEVICE, 1), {});
1878
1879 const Bytes unknownFloor = MakeEpmTowerFloor({0xFE}, {0x01, 0x02, 0x03});
1880
1881 const Bytes tower = MakeEpmTower({interfaceFloor, unknownFloor});
1882
1883 const auto result = ParseEpmTower(tower);
1884
1885 ASSERT_TRUE(result.has_value());
1886 EXPECT_EQ(result->interfaceUuid, UUID_PNIO_DEVICE);
1887 EXPECT_TRUE(result->protocol.empty());
1888 EXPECT_EQ(result->port, 0);
1889 EXPECT_TRUE(result->ipAddress.empty());
1890}
1893TEST_F(RpcTests, ParseEpmTowerContinuesAcrossAllFloors)
1894{
1895 const auto result = ParseEpmTower(
1896 MakeCompleteEpmTower(
1898 1,
1899 2,
1900 0x8894,
1901 {192, 168, 1, 20}));
1902
1903 ASSERT_TRUE(result.has_value());
1904
1905 EXPECT_EQ(result->protocol, "ncadg_ip_udp");
1906 EXPECT_EQ(result->port, 0x8894);
1907 EXPECT_EQ(result->ipAddress, "192.168.1.20");
1908}
1909
1912TEST_F(RpcTests, EpmLookupReturnsParsedEndpoint)
1913{
1914 asio::io_context serverIo;
1915 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
1916
1917 const std::uint16_t serverPort = server.local_endpoint().port();
1918
1919 const auto objectUuid = StringToUuidBytes("01234567-89ab-cdef-0123-456789abcdef");
1920
1921 const Bytes tower = MakeCompleteEpmTower(UUID_PNIO_DEVICE, 3, 7, 0x8894, {10, 250, 21, 110});
1922
1923 const Bytes body = MakeEpmLookupBody(objectUuid, "AUMA-SGx", tower);
1924
1925 const Bytes response = MakeEpmResponse(body);
1926
1927 // 80-byte RPC header:
1928
1929 // 0..3 version/type/flags
1930 // 4..7 data representation
1931 // 8..23 object UUID
1932 // 24..39 interface UUID
1933 // 40..55 activity UUID
1934
1935 // 56..59 server_boot_time
1936 // 60..63 interface_version
1937 // 64..67 sequence_number
1938 // 68..69 operation_number
1939 // 70..71 interface_hint
1940 // 72..73 activity_hint
1941 // 74..75 length_of_body
1942 // 76..77 fragment_number
1943 // 78 authentication_protocol
1944 // 79 serial_low
1945
1946 std::thread serverThread([&server, &response, this]()
1947 {
1948 Bytes request;
1949 ReplyToEpmLookup(server, response, request);
1950
1951 ASSERT_GE(request.size(), PNRPCHeader::kFixedSize);
1952 EXPECT_EQ(request[0], 4);
1953 EXPECT_EQ(request[1], PNRPCHeader::REQUEST);
1954 EXPECT_EQ(request[2], 0x20);
1955 wire::Reader r(request);
1956 r.SkipTo(56);
1957
1958 // server_boot_time
1959 EXPECT_EQ(r.U32LittleEndian(), 0U);
1960
1961 // interface_version
1962 EXPECT_EQ(r.U32LittleEndian(), 3U);
1963
1964 // sequence_number
1965 EXPECT_EQ(r.U32LittleEndian(), 0U);
1966
1967 // operation_number
1968 EXPECT_EQ(r.U16LittleEndian(), EPM_LOOKUP);
1969 });
1970
1971 const auto results = conn->EpmLookup("127.0.0.1", serverPort, 1.0);
1972
1973 serverThread.join();
1974
1975 ASSERT_EQ(results.size(), 1U);
1976
1977 EXPECT_EQ(results[0].objectUuid, "01234567-89ab-cdef-0123-456789abcdef");
1978 EXPECT_EQ(results[0].interfaceUuid, UUID_PNIO_DEVICE);
1979 EXPECT_EQ(results[0].interfaceVersionMajor, 3);
1980 EXPECT_EQ(results[0].interfaceVersionMinor, 7);
1981 EXPECT_EQ(results[0].protocol, "ncadg_ip_udp");
1982 EXPECT_EQ(results[0].port, 0x8894);
1983 EXPECT_EQ(results[0].ipAddress, "10.250.21.110");
1984 EXPECT_EQ(results[0].annotation, "AUMA-SGx");
1985}
1986
1989TEST_F(RpcTests, EpmLookupBuildsInterfaceFilteredRequest)
1990{
1991 asio::io_context serverIo;
1992 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
1993
1994 const std::uint16_t serverPort = server.local_endpoint().port();
1995
1996 const Bytes responseBody = {
1997 0x00,
1998 0x00,
1999 0x00,
2000 0x00,
2001 0x00,
2002 0x00,
2003 0x00,
2004 0x00,
2005 0x00,
2006 0x00,
2007 0x00,
2008 0x00,
2009 };
2010
2011 const Bytes response = MakeEpmResponse(responseBody);
2012
2013 Bytes request;
2014
2015 std::jthread serverThread([&server, &response, &request, this]()
2016 {
2017 ReplyToEpmLookup(server, response, request);
2018 });
2019
2020 EXPECT_NO_THROW(conn->EpmLookup("127.0.0.1", serverPort, 1.0, UUID_PNIO_DEVICE));
2021
2022 serverThread.join();
2023
2024 ASSERT_GE(request.size(), PNRPCHeader::kFixedSize + 4U + 16U + 4U);
2025
2026 const std::size_t bodyOffset = PNRPCHeader::kFixedSize;
2027 wire::Reader r(request);
2028 r.SkipTo(bodyOffset);
2029
2030 EXPECT_EQ(r.U32LittleEndian(), EPM_INQUIRY_INTERFACE);
2031
2032 const auto expectedUuid = StringToUuidBytes(UUID_PNIO_DEVICE);
2033
2034 EXPECT_TRUE(std::equal(expectedUuid.begin(), expectedUuid.end(), request.begin() + bodyOffset + 4));
2035 r.SkipTo(bodyOffset + 20);
2036 EXPECT_EQ(r.U16LittleEndian(), 1);
2037 r.SkipTo(bodyOffset + 22);
2038 EXPECT_EQ(r.U16LittleEndian(), 0);
2039}
2042TEST_F(RpcTests, EpmLookupReturnsEmptyForShortResponse)
2043{
2044 asio::io_context serverIo;
2045 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2046
2047 const Bytes response = {0x04, 0x02};
2048
2049 Bytes request;
2050
2051 std::jthread serverThread([&server, &response, &request, this]()
2052 {
2053 ReplyToEpmLookup(server, response, request);
2054 });
2055
2056 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2057
2058 serverThread.join();
2059
2060 EXPECT_TRUE(results.empty());
2061}
2062
2064TEST_F(RpcTests, EpmLookupReturnsEmptyForFaultResponse)
2065{
2066 asio::io_context serverIo;
2067 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2068
2069 Bytes response(PNRPCHeader::kFixedSize, 0);
2070 response[0] = 4;
2071 response[1] = 0x03;
2072
2073 Bytes request;
2074
2075 std::jthread serverThread([&server, &response, &request, this]()
2076 {
2077 ReplyToEpmLookup(server, response, request);
2078 });
2079
2080 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2081
2082 serverThread.join();
2083
2084 EXPECT_TRUE(results.empty());
2085}
2086
2089TEST_F(RpcTests, EpmLookupReturnsEmptyForUnexpectedResponseType)
2090{
2091 asio::io_context serverIo;
2092 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2093
2094 Bytes response(PNRPCHeader::kFixedSize, 0);
2095 response[0] = 4;
2096 response[1] = 0x07;
2097
2098 Bytes request;
2099
2100 std::jthread serverThread([&server, &response, &request, this]()
2101 {
2102 ReplyToEpmLookup(server, response, request);
2103 });
2104
2105 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2106
2107 serverThread.join();
2108
2109 EXPECT_TRUE(results.empty());
2110}
2113TEST_F(RpcTests, EpmLookupReturnsEmptyForShortBody)
2114{
2115 asio::io_context serverIo;
2116 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2117
2118 const Bytes response = MakeEpmResponse({0x00, 0x00, 0x00, 0x00});
2119
2120 Bytes request;
2121
2122 std::jthread serverThread([&server, &response, &request, this]()
2123 {
2124 ReplyToEpmLookup(server, response, request);
2125 });
2126
2127 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2128
2129 serverThread.join();
2130
2131 EXPECT_TRUE(results.empty());
2132}
2133
2136TEST_F(RpcTests, EpmLookupClampsBodyLengthToReceivedData)
2137{
2138 asio::io_context serverIo;
2139 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2140
2141 Bytes response = MakeEpmResponse({
2142 0x00,
2143 0x00,
2144 0x00,
2145 0x00,
2146 0x01,
2147 0x00,
2148 0x00,
2149 0x00,
2150 0x00,
2151 0x00,
2152 0x00,
2153 0x00,
2154 });
2155
2156 ASSERT_EQ(response.size(), PNRPCHeader::kFixedSize + 12U);
2157 wire::PutLittleEndian<std::uint16_t>(response, 0xFFFF);
2158
2159 // The previous append is intentionally not at offset 74, so patch the
2160 // existing body-length bytes directly.
2161 response[74] = 0xFF;
2162 response[75] = 0xFF;
2163
2164 Bytes request;
2165
2166 std::jthread serverThread([&server, &response, &request, this]()
2167 {
2168 ReplyToEpmLookup(server, response, request);
2169 });
2170
2171 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2172
2173 serverThread.join();
2174
2175 EXPECT_TRUE(results.empty());
2176}
2179TEST_F(RpcTests, EpmLookupStopsOnTruncatedEntryUuid)
2180{
2181 asio::io_context serverIo;
2182 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2183
2184 Bytes body;
2185 wire::PutLittleEndian<std::uint32_t>(body, 0);
2186 wire::PutLittleEndian<std::uint32_t>(body, 1);
2187 wire::PutLittleEndian<std::uint32_t>(body, 1);
2188 wire::PutLittleEndian<std::uint32_t>(body, 0);
2189 wire::PutLittleEndian<std::uint32_t>(body, 1);
2190 body.insert(body.end(), 5, 0x00);
2191
2192 const Bytes response = MakeEpmResponse(body);
2193 Bytes request;
2194
2195 std::jthread serverThread([&server, &response, &request, this]()
2196 {
2197 ReplyToEpmLookup(server, response, request);
2198 });
2199
2200 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2201
2202 serverThread.join();
2203
2204 EXPECT_TRUE(results.empty());
2205}
2206
2208TEST_F(RpcTests, EpmLookupStopsOnTruncatedTowerPointer)
2209{
2210 asio::io_context serverIo;
2211 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2212
2213 Bytes body;
2214 wire::PutLittleEndian<std::uint32_t>(body, 0);
2215 wire::PutLittleEndian<std::uint32_t>(body, 1);
2216 wire::PutLittleEndian<std::uint32_t>(body, 1);
2217 wire::PutLittleEndian<std::uint32_t>(body, 0);
2218 wire::PutLittleEndian<std::uint32_t>(body, 1);
2219 body.insert(body.end(), uuidLenght, 0x00);
2220 body.push_back(0x00);
2221
2222 const Bytes response = MakeEpmResponse(body);
2223 Bytes request;
2224
2225 std::jthread serverThread([&server, &response, &request, this]()
2226 {
2227 ReplyToEpmLookup(server, response, request);
2228 });
2229
2230 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2231
2232 serverThread.join();
2233
2234 EXPECT_TRUE(results.empty());
2235}
2236
2238TEST_F(RpcTests, EpmLookupStopsOnTruncatedAnnotationLength)
2239{
2240 asio::io_context serverIo;
2241 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2242
2243 Bytes body;
2244 wire::PutLittleEndian<std::uint32_t>(body, 0);
2245 wire::PutLittleEndian<std::uint32_t>(body, 1);
2246 wire::PutLittleEndian<std::uint32_t>(body, 1);
2247 wire::PutLittleEndian<std::uint32_t>(body, 0);
2248 wire::PutLittleEndian<std::uint32_t>(body, 1);
2249 body.insert(body.end(), uuidLenght, 0x00);
2250 wire::PutLittleEndian<std::uint32_t>(body, 0);
2251 body.push_back(0x00);
2252
2253 const Bytes response = MakeEpmResponse(body);
2254 Bytes request;
2255
2256 std::jthread serverThread([&server, &response, &request, this]()
2257 {
2258 ReplyToEpmLookup(server, response, request);
2259 });
2260
2261 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2262
2263 serverThread.join();
2264
2265 EXPECT_TRUE(results.empty());
2266}
2267
2270TEST_F(RpcTests, EpmLookupHandlesZeroLengthAnnotation)
2271{
2272 asio::io_context serverIo;
2273 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2274
2275 const auto objectUuid = StringToUuidBytes("01234567-89ab-cdef-0123-456789abcdef");
2276
2277 const Bytes tower = MakeCompleteEpmTower(UUID_PNIO_DEVICE, 1, 2, 0x8894, {192, 168, 1, 10});
2278
2279 Bytes body;
2280 wire::PutLittleEndian<std::uint32_t>(body, 0);
2281 wire::PutLittleEndian<std::uint32_t>(body, 1);
2282 wire::PutLittleEndian<std::uint32_t>(body, 1);
2283 wire::PutLittleEndian<std::uint32_t>(body, 0);
2284 wire::PutLittleEndian<std::uint32_t>(body, 1);
2285
2286 body.insert(body.end(), objectUuid.begin(), objectUuid.end());
2287
2288 wire::PutLittleEndian<std::uint32_t>(body, 0);
2289 wire::PutLittleEndian<std::uint32_t>(body, 0);
2290
2291 while ((body.size() & 3U) != 0U)
2292 {
2293 body.push_back(0);
2294 }
2295
2296 wire::PutLittleEndian<std::uint32_t>(body, static_cast<std::uint32_t>(tower.size()));
2297 body.insert(body.end(), tower.begin(), tower.end());
2298
2299 while ((body.size() & 3U) != 0U)
2300 {
2301 body.push_back(0);
2302 }
2303
2304 const Bytes response = MakeEpmResponse(body);
2305 Bytes request;
2306
2307 std::jthread serverThread([&server, &response, &request, this]()
2308 {
2309 ReplyToEpmLookup(server, response, request);
2310 });
2311
2312 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2313
2314 serverThread.join();
2315
2316 ASSERT_EQ(results.size(), 1U);
2317 EXPECT_TRUE(results[0].annotation.empty());
2318}
2319
2321TEST_F(RpcTests, EpmLookupTrimsTrailingAnnotationZeros)
2322{
2323 asio::io_context serverIo;
2324 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2325
2326 const auto objectUuid = StringToUuidBytes("11111111-2222-3333-4444-555555555555");
2327
2328 const Bytes tower = MakeCompleteEpmTower(UUID_PNIO_DEVICE, 1, 1, 0x8894, {10, 0, 0, 1});
2329
2330 Bytes body;
2331 wire::PutLittleEndian<std::uint32_t>(body, 0);
2332 wire::PutLittleEndian<std::uint32_t>(body, 1);
2333 wire::PutLittleEndian<std::uint32_t>(body, 1);
2334 wire::PutLittleEndian<std::uint32_t>(body, 0);
2335 wire::PutLittleEndian<std::uint32_t>(body, 1);
2336
2337 body.insert(body.end(), objectUuid.begin(), objectUuid.end());
2338
2339 wire::PutLittleEndian<std::uint32_t>(body, 0);
2340
2341 const Bytes annotation = {'A', 'B', 'C', 0, 0, 0};
2342
2343 wire::PutLittleEndian<std::uint32_t>(body, static_cast<std::uint32_t>(annotation.size()));
2344
2345 body.insert(body.end(), annotation.begin(), annotation.end());
2346
2347 while ((body.size() & 3U) != 0U)
2348 {
2349 body.push_back(0);
2350 }
2351
2352 wire::PutLittleEndian<std::uint32_t>(body, static_cast<std::uint32_t>(tower.size()));
2353
2354 body.insert(body.end(), tower.begin(), tower.end());
2355
2356 while ((body.size() & 3U) != 0U)
2357 {
2358 body.push_back(0);
2359 }
2360
2361 const Bytes response = MakeEpmResponse(body);
2362 Bytes request;
2363
2364 std::jthread serverThread([&server, &response, &request, this]()
2365 {
2366 ReplyToEpmLookup(server, response, request);
2367 });
2368
2369 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2370
2371 serverThread.join();
2372
2373 ASSERT_EQ(results.size(), 1U);
2374 EXPECT_EQ(results[0].annotation, "ABC");
2375}
2376
2378TEST_F(RpcTests, EpmLookupIgnoresEntryWithInvalidTower)
2379{
2380 asio::io_context serverIo;
2381 asio::ip::udp::socket server(serverIo, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
2382
2383 const auto objectUuid = StringToUuidBytes("01234567-89ab-cdef-0123-456789abcdef");
2384
2385 const Bytes invalidTower = MakeEpmTower({
2386 MakeEpmTowerFloor({0x0A}, {}),
2387 });
2388
2389 const Bytes body = MakeEpmLookupBody(objectUuid, "invalid", invalidTower);
2390
2391 const Bytes response = MakeEpmResponse(body);
2392 Bytes request;
2393
2394 std::jthread serverThread([&server, &response, &request, this]()
2395 {
2396 ReplyToEpmLookup(server, response, request);
2397 });
2398
2399 const auto results = conn->EpmLookup("127.0.0.1", server.local_endpoint().port(), 1.0);
2400
2401 serverThread.join();
2402
2403 EXPECT_TRUE(results.empty());
2404}
2405
2408TEST_F(RpcTests, GetStationInfoReturnsFirstDirectResponse)
2409{
2410 using testing::_;
2411 using testing::InSequence;
2412 using testing::Return;
2413 using testing::StrictMock;
2414
2415 StrictMock<MockRawEthernetSocket> sock;
2416
2417 const MacAddress src = String2Mac("08:00:27:31:39:dc");
2418
2419 const MacAddress deviceMac = String2Mac("a0:d3:85:00:f6:13");
2420
2421 const std::string stationName = "valve2";
2422
2423 const Bytes response = MakeStationNameResponse(deviceMac, src, stationName);
2424
2425 {
2426 InSequence sequence;
2427
2428 EXPECT_CALL(sock, Send(_))
2429 .Times(1);
2430
2431 EXPECT_CALL(sock, SetTimeout(_))
2432 .Times(1);
2433
2434 EXPECT_CALL(sock, Recv())
2435 .WillOnce(Return(response))
2436 .WillRepeatedly(Return(Bytes{}));
2437 }
2438
2439 const auto result = GetStationInfo(sock, src, stationName, 1);
2440
2441 EXPECT_EQ(result.mac, "a0:d3:85:00:f6:13");
2442 EXPECT_EQ(result.name, stationName);
2443}
2446TEST_F(RpcTests, GetStationInfoSendsStationNameRequest)
2447{
2448 using testing::_;
2449 using testing::Return;
2450 using testing::StrictMock;
2451
2452 StrictMock<MockRawEthernetSocket> sock;
2453
2454 const MacAddress src = String2Mac("08:00:27:31:39:dc");
2455
2456 const MacAddress deviceMac = String2Mac("a0:d3:85:00:f6:13");
2457
2458 const std::string stationName = "valve2";
2459
2460 const Bytes response = MakeStationNameResponse(deviceMac, src, stationName);
2461
2462 Bytes transmitted;
2463
2464 EXPECT_CALL(sock, Send(_))
2465 .WillOnce([&transmitted](const std::vector<std::uint8_t>& frame)
2466 {
2467 transmitted = frame;
2468 });
2469
2470 EXPECT_CALL(sock, SetTimeout(_))
2471 .Times(1);
2472
2473 EXPECT_CALL(sock, Recv())
2474 .WillOnce(Return(response))
2475 .WillRepeatedly(Return(Bytes{}));
2476
2477 const auto result = GetStationInfo(sock, src, stationName, 1);
2478
2479 ASSERT_FALSE(transmitted.empty());
2480
2481 EXPECT_EQ(result.name, stationName);
2482
2483 EthernetHeader eth = EthernetHeader::Parse(transmitted);
2484
2485 EXPECT_EQ(eth.dst, String2Mac(dcp::DCP_MULTICAST_MAC));
2486
2487 EXPECT_EQ(eth.src, src);
2488 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
2489
2490 const PNDCPHeader dcpHeader = PNDCPHeader::Parse(eth.payload);
2491
2492 EXPECT_EQ(dcpHeader.serviceId, DcpServiceId::Identify);
2493}
2494
2497TEST_F(RpcTests, GetStationInfoFallsBackToDiscovery)
2498{
2499 using testing::_;
2500 using testing::InSequence;
2501 using testing::Return;
2502 using testing::StrictMock;
2503
2504 StrictMock<MockRawEthernetSocket> sock;
2505
2506 const MacAddress src = String2Mac("08:00:27:31:39:dc");
2507
2508 const MacAddress deviceMac = String2Mac("a0:d3:85:00:f6:13");
2509
2510 const std::string stationName = "valve2";
2511
2512 const Bytes response = MakeStationNameResponse(deviceMac, src, stationName);
2513
2514 EXPECT_CALL(sock, Send(_))
2515 .Times(2);
2516
2517 EXPECT_CALL(sock, SetTimeout(_))
2518 .Times(2);
2519
2520 EXPECT_CALL(sock, Recv())
2521 .WillOnce(Return(Bytes{}))
2522 .WillOnce(Return(response))
2523 .WillRepeatedly(Return(Bytes{}));
2524
2525 const auto result = GetStationInfo(sock, src, stationName, 0.01);
2526
2527 EXPECT_EQ(result.mac, "a0:d3:85:00:f6:13");
2528 EXPECT_EQ(result.name, stationName);
2529}
2531TEST_F(RpcTests, GetStationInfoDiscoveryMatchesStationNameCaseInsensitive)
2532{
2533 using testing::_;
2534 using testing::InSequence;
2535 using testing::Return;
2536 using testing::StrictMock;
2537
2538 StrictMock<MockRawEthernetSocket> sock;
2539
2540 const MacAddress src = String2Mac("08:00:27:31:39:dc");
2541
2542 const MacAddress deviceMac = String2Mac("a0:d3:85:00:f6:13");
2543
2544 const Bytes response = MakeStationNameResponse(deviceMac, src, "VaLvE2");
2545
2546 EXPECT_CALL(sock, Send(_))
2547 .Times(2);
2548
2549 EXPECT_CALL(sock, SetTimeout(_))
2550 .Times(2);
2551
2552 EXPECT_CALL(sock, Recv())
2553 .WillOnce(Return(Bytes{}))
2554 .WillOnce(Return(response))
2555 .WillRepeatedly(Return(Bytes{}));
2556
2557 const auto result = GetStationInfo(sock, src, "vAlVe2", 0.01);
2558
2559 EXPECT_EQ(result.mac, "a0:d3:85:00:f6:13");
2560 EXPECT_EQ(result.name, "VaLvE2");
2561}
2564TEST_F(RpcTests, GetStationInfoDiscoverySkipsNonMatchingDevice)
2565{
2566 using testing::_;
2567 using testing::InSequence;
2568 using testing::Return;
2569 using testing::StrictMock;
2570
2571 StrictMock<MockRawEthernetSocket> sock;
2572
2573 const MacAddress src = String2Mac("08:00:27:31:39:dc");
2574
2575 const MacAddress firstMac = String2Mac("a0:d3:85:00:f6:13");
2576
2577 const MacAddress secondMac = String2Mac("a0:d3:85:00:f6:14");
2578
2579 // This requires a single response containing multiple DCP devices.
2580 // Build that using the normal IdentifyResponse block construction used by
2581 // the DCP tests in this repository.
2582 const Bytes discoveryResponse = MakeStationNameResponse(secondMac, src, "valve2");
2583
2584 EXPECT_CALL(sock, Send(_))
2585 .Times(2);
2586
2587 EXPECT_CALL(sock, SetTimeout(_))
2588 .Times(2);
2589
2590 EXPECT_CALL(sock, Recv())
2591 .WillOnce(Return(Bytes{}))
2592 .WillOnce(Return(discoveryResponse))
2593 .WillRepeatedly(Return(Bytes{}));
2594
2595 const auto result = GetStationInfo(sock, src, "VALVE2", 0.01);
2596
2597 EXPECT_EQ(result.mac, "a0:d3:85:00:f6:14");
2598}
2601TEST_F(RpcTests, GetStationInfoThrowsWhenDiscoveryFindsNoMatchingDevice)
2602{
2603 using testing::_;
2604 using testing::InSequence;
2605 using testing::Return;
2606 using testing::StrictMock;
2607
2608 StrictMock<MockRawEthernetSocket> sock;
2609
2610 const MacAddress src = String2Mac("08:00:27:31:39:dc");
2611
2612 const MacAddress deviceMac = String2Mac("a0:d3:85:00:f6:13");
2613
2614 const Bytes response = MakeStationNameResponse(deviceMac, src, "different-device");
2615
2616 EXPECT_CALL(sock, Send(_))
2617 .Times(2);
2618
2619 EXPECT_CALL(sock, SetTimeout(_))
2620 .Times(2);
2621
2622 EXPECT_CALL(sock, Recv())
2623 .WillOnce(Return(Bytes{}))
2624 .WillOnce(Return(response))
2625 .WillRepeatedly(Return(Bytes{}));
2626
2627 EXPECT_THROW(GetStationInfo(sock, src, "valve2", 0.01), DCPDeviceNotFoundError);
2628}
2631TEST_F(RpcTests, GetStationInfoNotFoundExceptionContainsStationName)
2632{
2633 using testing::_;
2634 using testing::InSequence;
2635 using testing::Return;
2636 using testing::StrictMock;
2637
2638 StrictMock<MockRawEthernetSocket> sock;
2639
2640 const MacAddress src = String2Mac("08:00:27:31:39:dc");
2641
2642 const MacAddress deviceMac = String2Mac("a0:d3:85:00:f6:13");
2643
2644 const Bytes response = MakeStationNameResponse(deviceMac, src, "different-device");
2645
2646 EXPECT_CALL(sock, Send(_))
2647 .Times(2);
2648
2649 EXPECT_CALL(sock, SetTimeout(_))
2650 .Times(2);
2651
2652 EXPECT_CALL(sock, Recv())
2653 .WillOnce(Return(Bytes{}))
2654 .WillOnce(Return(response))
2655 .WillRepeatedly(Return(Bytes{}));
2656 EXPECT_THROW(GetStationInfo(sock, src, "Valve2", 0.01), DCPDeviceNotFoundError);
2657}
2660TEST_F(RpcTests, GetStationInfoThrowsWhenDiscoveryReturnsNoResponses)
2661{
2662 using testing::_;
2663 using testing::InSequence;
2664 using testing::Return;
2665 using testing::StrictMock;
2666
2667 StrictMock<MockRawEthernetSocket> sock;
2668
2669 const MacAddress src = String2Mac("08:00:27:31:39:dc");
2670
2671 EXPECT_CALL(sock, Send(_))
2672 .Times(2);
2673
2674 EXPECT_CALL(sock, SetTimeout(_))
2675 .Times(2);
2676
2677 EXPECT_CALL(sock, Recv())
2678 .WillOnce(Return(Bytes{}))
2679 .WillOnce(Return(Bytes{}))
2680 .WillRepeatedly(Return(Bytes{}));
2681
2682 EXPECT_THROW(GetStationInfo(sock, src, "valve2", 0.01), DCPDeviceNotFoundError);
2683}
2686TEST_F(RpcTests, GetStationInfoDoesNotFilterNonEmptyDirectResponse)
2687{
2688 using testing::_;
2689 using testing::InSequence;
2690 using testing::Return;
2691 using testing::StrictMock;
2692
2693 StrictMock<MockRawEthernetSocket> sock;
2694
2695 const MacAddress src = String2Mac("08:00:27:31:39:dc");
2696
2697 const MacAddress deviceMac = String2Mac("a0:d3:85:00:f6:13");
2698
2699 const Bytes response = MakeStationNameResponse(deviceMac, src, "some-other-device");
2700
2701 {
2702 InSequence sequence;
2703
2704 EXPECT_CALL(sock, Send(_))
2705 .Times(1);
2706
2707 EXPECT_CALL(sock, SetTimeout(_))
2708 .Times(1);
2709
2710 EXPECT_CALL(sock, Recv())
2711 .WillOnce(Return(response))
2712 .WillRepeatedly(Return(Bytes{}));
2713 }
2714
2715 const auto result = GetStationInfo(sock, src, "valve2", 0.01);
2716
2717 EXPECT_EQ(result.name, "some-other-device");
2718}
2719
2722TEST_F(RpcTests, CreateNrdBuildsExpectedWrapper)
2723{
2724 const Bytes payload = {
2725 0x01,
2726 0x02,
2727 0x03,
2728 0x04,
2729 };
2730
2731 const PNNRDData result = InvokeCreateNrd(payload);
2732
2733 EXPECT_EQ(result.argsMaximumStatus, 1500U);
2734 EXPECT_EQ(result.argsLength, payload.size());
2735 EXPECT_EQ(result.maximumCount, 1500U);
2736 EXPECT_EQ(result.offset, 0U);
2737 EXPECT_EQ(result.actualCount, payload.size());
2738 EXPECT_EQ(result.payload, payload);
2739}
2740
2742TEST_F(RpcTests, CreateNrdHandlesEmptyPayload)
2743{
2744 const PNNRDData result = InvokeCreateNrd({});
2745
2746 EXPECT_EQ(result.argsMaximumStatus, 1500U);
2747 EXPECT_EQ(result.argsLength, 0U);
2748 EXPECT_EQ(result.maximumCount, 1500U);
2749 EXPECT_EQ(result.offset, 0U);
2750 EXPECT_EQ(result.actualCount, 0U);
2751 EXPECT_TRUE(result.payload.empty());
2752}
2753
2756TEST_F(RpcTests, CreateNrdRoundTripsThroughSerialization)
2757{
2758 const Bytes payload = {
2759 0x10,
2760 0x20,
2761 0x30,
2762 0x40,
2763 0x50,
2764 };
2765
2766 const PNNRDData original = InvokeCreateNrd(payload);
2767
2768 const PNNRDData parsed = PNNRDData::Parse(original.ToBytes());
2769
2770 EXPECT_EQ(parsed.argsMaximumStatus, original.argsMaximumStatus);
2771 EXPECT_EQ(parsed.argsLength, original.argsLength);
2772 EXPECT_EQ(parsed.maximumCount, original.maximumCount);
2773 EXPECT_EQ(parsed.offset, original.offset);
2774 EXPECT_EQ(parsed.actualCount, original.actualCount);
2775 EXPECT_EQ(parsed.payload, original.payload);
2776}
2778TEST_F(RpcTests, BuildAlarmCrBlockUsesProfinetEtherTypeForTransportZero)
2779{
2780 const Bytes bytes = InvokeBuildAlarmCrBlock(0, 0);
2781
2782 ASSERT_EQ(bytes.size(), PNAlarmCRBlockReq::kSize);
2783
2784 const PNAlarmCRBlockReq block = PNAlarmCRBlockReq::Parse(bytes);
2785
2786 EXPECT_EQ(block.alarmCrType, 0x0001);
2787 EXPECT_EQ(block.etherTypeLT, PROFINET_ETHERTYPE);
2788 EXPECT_EQ(block.alarmCrProperties, 0U);
2789 EXPECT_EQ(block.localAlarmReference, conn->LocalAlarmRef());
2795}
2796
2799TEST_F(RpcTests, BuildAlarmCrBlockUsesIpEtherTypeForNonZeroTransport)
2800{
2801 const Bytes bytes = InvokeBuildAlarmCrBlock(1, 0);
2802
2803 const PNAlarmCRBlockReq block = PNAlarmCRBlockReq::Parse(bytes);
2804
2805 EXPECT_EQ(block.etherTypeLT, IP_ETHERTYPE);
2806 EXPECT_EQ(block.alarmCrProperties, 2U);
2807}
2808
2810TEST_F(RpcTests, BuildAlarmCrBlockEncodesPriority)
2811{
2812 const Bytes bytes = InvokeBuildAlarmCrBlock(0, 1);
2813
2814 const PNAlarmCRBlockReq block = PNAlarmCRBlockReq::Parse(bytes);
2815
2816 EXPECT_EQ(block.etherTypeLT, PROFINET_ETHERTYPE);
2817 EXPECT_EQ(block.alarmCrProperties, 1U);
2818}
2819
2821TEST_F(RpcTests, BuildAlarmCrBlockCombinesTransportAndPriority)
2822{
2823 const Bytes bytes = InvokeBuildAlarmCrBlock(1, 1);
2824
2825 const PNAlarmCRBlockReq block = PNAlarmCRBlockReq::Parse(bytes);
2826 EXPECT_EQ(block.etherTypeLT, IP_ETHERTYPE);
2827 EXPECT_EQ(block.alarmCrProperties, 3U);
2828}
2829
2832TEST_F(RpcTests, BuildAlarmCrBlockCreatesExpectedBlockHeader)
2833{
2834 const Bytes bytes = InvokeBuildAlarmCrBlock(0, 0);
2835
2836 const PNBlockHeader header = PNBlockHeader::Parse(bytes);
2837
2838 EXPECT_EQ(header.blockType, PNAlarmCRBlockReq::BLOCK_TYPE);
2839 EXPECT_EQ(header.blockLength, PNAlarmCRBlockReq::kSize - 4);
2840 EXPECT_EQ(header.blockVersionHigh, 0x01);
2841 EXPECT_EQ(header.blockVersionLow, 0x00);
2842}
2844TEST_F(RpcTests, ParseAlarmCrResponseReturnsMinusOneForEmptyData)
2845{
2846 EXPECT_EQ(InvokeParseAlarmCrResponse({}), -1);
2847}
2850TEST_F(RpcTests, ParseAlarmCrResponseReturnsMinusOneForShortData)
2851{
2852 const Bytes data = {
2853 0x01,
2854 0x02,
2855 0x03,
2856 };
2857
2858 EXPECT_EQ(InvokeParseAlarmCrResponse(data), -1);
2859}
2862TEST_F(RpcTests, ParseAlarmCrResponseIgnoresUnrelatedBlock)
2863{
2864 const Bytes data = MakeBlockHeader(static_cast<BlockType>(0x9999), 0);
2865
2866 EXPECT_EQ(InvokeParseAlarmCrResponse(data), -1);
2867}
2870TEST_F(RpcTests, ParseAlarmCrResponseReturnsLocalAlarmReference)
2871{
2872 constexpr std::uint16_t expectedAlarmReference = 0x1234;
2873
2874 const Bytes data = MakeAlarmCrResponseBlock(expectedAlarmReference);
2875
2876 EXPECT_EQ(InvokeParseAlarmCrResponse(data), static_cast<int>(expectedAlarmReference));
2877}
2880TEST_F(RpcTests, ParseAlarmCrResponseContinuesAfterUnrelatedBlock)
2881{
2882 constexpr std::uint16_t expectedAlarmReference = 0x4321;
2883
2884 Bytes data = MakeBlockHeader(static_cast<BlockType>(0x9999), 2);
2885
2886 const Bytes alarmBlock = MakeAlarmCrResponseBlock(expectedAlarmReference);
2887
2888 data.insert(data.end(), alarmBlock.begin(), alarmBlock.end());
2889
2890 EXPECT_EQ(InvokeParseAlarmCrResponse(data), static_cast<int>(expectedAlarmReference));
2891}
2893TEST_F(RpcTests, ParseAlarmCrResponseContinuesAfterMalformedAlarmBlock)
2894{
2895 Bytes malformed = MakeAlarmCrResponseBlock(0x1234);
2896
2897 ASSERT_GT(malformed.size(), blockHeaderLength);
2898
2899 malformed.resize(blockHeaderLength);
2900
2901 const int result = InvokeParseAlarmCrResponse(malformed);
2902
2903 EXPECT_EQ(result, -1);
2904}
2907TEST_F(RpcTests, ParseAlarmCrResponseIgnoresTruncatedAlarmBlock)
2908{
2909 Bytes malformed = MakeAlarmCrResponseBlock(0x1234);
2910
2911 malformed[2] = 0xFF;
2912 malformed[3] = 0xFF;
2913
2914 EXPECT_EQ(InvokeParseAlarmCrResponse(malformed), -1);
2915}
2918TEST_F(RpcTests, ParseAlarmCrResponseContinuesAfterMalformedBlock)
2919{
2920 Bytes malformed = MakeAlarmCrResponseBlock(0x1111);
2921
2922 malformed[2] = 0xFF;
2923 malformed[3] = 0xFF;
2924
2925 // The malformed block's declared length must not consume the valid block, // so construct the malformed header separately with a zero-sized actual
2926 // block followed by a valid block.
2927 malformed.resize(blockHeaderLength);
2928
2929 PNBlockHeader malformedHeader;
2930 malformedHeader.blockType = BlockType::AlarmCrResponse;
2931 malformedHeader.blockLength = 0xFFFF;
2932 malformedHeader.blockVersionHigh = 0x01;
2933 malformedHeader.blockVersionLow = 0x00;
2934
2935 const Bytes malformedHeaderBytes = malformedHeader.ToBytes();
2936
2937 const Bytes validBlock = MakeAlarmCrResponseBlock(0x5678);
2938
2939 Bytes data = malformedHeaderBytes;
2940 data.insert(data.end(), validBlock.begin(), validBlock.end());
2941
2942 const int result = InvokeParseAlarmCrResponse(data);
2943
2944 // Current parser uses the declared block length to advance its offset, so
2945 // this malformed block consumes the remainder of the input.
2946 EXPECT_EQ(result, -1);
2947}
2950TEST_F(RpcTests, ParseAlarmCrResponseReturnsMinusOneAfterUnrelatedBlocks)
2951{
2952 Bytes data = MakeBlockHeader(static_cast<BlockType>(0x1000), 0);
2953
2954 const Bytes second = MakeBlockHeader(static_cast<BlockType>(0x2000), 0);
2955
2956 const Bytes third = MakeBlockHeader(static_cast<BlockType>(0x3000), 0);
2957
2958 data.insert(data.end(), second.begin(), second.end());
2959
2960 data.insert(data.end(), third.begin(), third.end());
2961
2962 EXPECT_EQ(InvokeParseAlarmCrResponse(data), -1);
2963}
2964
2966TEST_F(RpcTests, IsConnectedReturnsFalseInitially)
2967{
2968 EXPECT_FALSE(conn->IsConnected());
2969}
2970
2972TEST_F(RpcTests, InfoReturnsDeviceDescription)
2973{
2974 const auto& info = conn->Info();
2975
2976 EXPECT_EQ(info.name, "test-device");
2977}
2978
2980TEST_F(RpcTests, ArUuidReturnsConnectionArUuid)
2981{
2982 const auto& uuid = conn->ArUuid();
2983
2984 EXPECT_EQ(uuid.size(), uuidLenght);
2985}
2986
2988TEST_F(RpcTests, SessionKeyReturnsInitialValue)
2989{
2990 EXPECT_NE(conn->SessionKey(), 0U);
2991}
2992
2994TEST_F(RpcTests, LocalAlarmRefReturnsConfiguredValue)
2995{
2996 EXPECT_EQ(conn->LocalAlarmRef(), conn->LocalAlarmRef());
2997}
2998
3000TEST_F(RpcTests, DeviceAlarmRefReturnsZeroInitially)
3001{
3002 EXPECT_EQ(conn->DeviceAlarmRef(), 0);
3003}
3004
3006TEST_F(RpcTests, HasAlarmCrReturnsFalseInitially)
3007{
3008 EXPECT_FALSE(conn->HasAlarmCr());
3009}
3010
3012TEST_F(RpcTests, ParseIocrResponseReturnsZeroForEmptyData)
3013{
3014 EXPECT_EQ(InvokeParseIocrResponse({}, 1), 0);
3015}
3016
3018TEST_F(RpcTests, ParseIocrResponseReturnsZeroForShortData)
3019{
3020 const Bytes data = {0x81, 0x02, 0x00};
3021
3022 EXPECT_EQ(InvokeParseIocrResponse(data, 1), 0);
3023}
3024
3026TEST_F(RpcTests, ParseIocrResponseIgnoresUnrelatedBlock)
3027{
3028 Bytes data = MakeBlockHeader(static_cast<BlockType>(0x9999), 2);
3029
3030 EXPECT_EQ(InvokeParseIocrResponse(data, 1), 0);
3031}
3032
3034TEST_F(RpcTests, ParseIocrResponseIgnoresDifferentIocrType)
3035{
3036 const Bytes data = MakeIocrResponseBlock(2, 7, 0x1234);
3037
3038 EXPECT_EQ(InvokeParseIocrResponse(data, 1), 0);
3039}
3040
3042TEST_F(RpcTests, ParseIocrResponseReturnsMatchingFrameId)
3043{
3044 const Bytes data = MakeIocrResponseBlock(1, 7, 0x8001);
3045
3046 EXPECT_EQ(InvokeParseIocrResponse(data, 1), 0x8001);
3047}
3048
3050TEST_F(RpcTests, ParseIocrResponseIgnoresMalformedIocrBlock)
3051{
3052 Bytes malformed = MakeBlockHeader(static_cast<BlockType>(PNIOCRBlockRes::BLOCK_TYPE), PNIOCRBlockRes::kSize - 4);
3053
3054 ASSERT_EQ(malformed.size(), blockHeaderLength);
3055
3056 EXPECT_EQ(InvokeParseIocrResponse(malformed, 1), 0);
3057}
3058
3060TEST_F(RpcTests, ParseIocrResponseFindsMatchingBlockAfterUnrelatedBlock)
3061{
3062 Bytes data = MakeBlockHeader(static_cast<BlockType>(0x9999), 2);
3063
3064 const Bytes iocr = MakeIocrResponseBlock(2, 9, 0x8123);
3065 data.insert(data.end(), iocr.begin(), iocr.end());
3066
3067 EXPECT_EQ(InvokeParseIocrResponse(data, 2), 0x8123);
3068}
3070TEST_F(RpcTests, SendReceiveReturnsResponseAndMarksConnectionLive)
3071{
3072 transport = std::make_unique<TestRpcTransport>();
3073 rawTransport = transport.get();
3074
3075 const Bytes payload = {0x10, 0x20, 0x30};
3076 rawTransport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::RESPONSE, PNRPCHeader::READ, payload));
3077
3078 conn = MakeTestTransportRpcConnection(std::move(transport));
3079
3080 PNRPCHeader request;
3083
3084 const PNRPCHeader response = InvokeSendReceive(request);
3085
3086 EXPECT_EQ(response.packetType, PNRPCHeader::RESPONSE);
3087 EXPECT_EQ(response.operationNumber, PNRPCHeader::READ);
3088 EXPECT_EQ(response.payload, payload);
3089 EXPECT_TRUE(conn->IsConnected());
3090 EXPECT_EQ(rawTransport->receivedRequests.size(), 1U);
3091}
3092
3094TEST_F(RpcTests, SendReceiveRetriesAfterRequestPacket)
3095{
3096 transport = std::make_unique<TestRpcTransport>();
3097 rawTransport = transport.get();
3098
3099 rawTransport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::REQUEST, PNRPCHeader::READ));
3100
3101 rawTransport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::RESPONSE, PNRPCHeader::READ));
3102
3103 conn = MakeTestTransportRpcConnection(std::move(transport));
3104
3105 PNRPCHeader request;
3107
3108 const PNRPCHeader response = InvokeSendReceive(request);
3109
3110 EXPECT_EQ(response.packetType, PNRPCHeader::RESPONSE);
3111 EXPECT_EQ(rawTransport->receivedRequests.size(), 2U);
3112}
3113
3115TEST_F(RpcTests, SendReceiveThrowsForShortResponse)
3116{
3117 transport = std::make_unique<TestRpcTransport>();
3118 transport->responses.emplace_back(10, 0);
3119
3120 conn = MakeTestTransportRpcConnection(std::move(transport));
3121
3122 PNRPCHeader request;
3123
3124 EXPECT_THROW(InvokeSendReceive(request), RPCError);
3125}
3126
3128TEST_F(RpcTests, SendReceiveThrowsRpcFaultError)
3129{
3130 transport = std::make_unique<TestRpcTransport>();
3131 transport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::FAULT, PNRPCHeader::READ));
3132
3133 conn = MakeTestTransportRpcConnection(std::move(transport));
3134
3135 PNRPCHeader request;
3137
3138 EXPECT_THROW(InvokeSendReceive(request), RPCFaultError);
3139}
3140
3142TEST_F(RpcTests, SendReceiveThrowsForRejectedResponse)
3143{
3144 transport = std::make_unique<TestRpcTransport>();
3145 transport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::REJECT, PNRPCHeader::READ));
3146
3147 conn = MakeTestTransportRpcConnection(std::move(transport));
3148
3149 PNRPCHeader request;
3150
3151 EXPECT_THROW(InvokeSendReceive(request), RPCError);
3152}
3153
3155TEST_F(RpcTests, SendReceiveThrowsForUnexpectedPacketType)
3156{
3157 transport = std::make_unique<TestRpcTransport>();
3158 transport->responses.push_back(MakeRpcResponseBytes(0x99, PNRPCHeader::READ));
3159
3160 conn = MakeTestTransportRpcConnection(std::move(transport));
3161
3162 PNRPCHeader request;
3163
3164 try
3165 {
3166 InvokeSendReceive(request);
3167 FAIL() << "Expected RPCError";
3168 }
3169 catch (const RPCError& e)
3170 {
3171 EXPECT_NE(std::string(e.what()).find("Unexpected RPC packet type"), std::string::npos);
3172 }
3173}
3175TEST_F(RpcTests, CheckTimeoutDoesNothingWhenNotConnected)
3176{
3177 InvokeSetRPCConLive(false);
3178
3179 EXPECT_FALSE(conn->IsConnected());
3180}
3181
3183TEST_F(RpcTests, CheckTimeoutDoesNotReconnectBeforeTimeout)
3184{
3185 InvokeSetRPCConLive(true);
3186 InvokeSetRPCConSrcMac("02:00:00:00:00:01");
3187 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3188 InvokeRPCConCheckTimeout();
3189
3190 EXPECT_TRUE(InvokeGetRPCConLive());
3191 EXPECT_TRUE(rawTransport->receivedRequests.empty());
3192}
3193
3195TEST_F(RpcTests, CheckTimeoutReconnectsAfterTimeout)
3196{
3197 transport = std::make_unique<TestRpcTransport>();
3198 rawTransport = transport.get();
3199
3200 rawTransport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONNECT));
3201
3202 conn = MakeTestTransportRpcConnection(std::move(transport));
3203
3204 InvokeSetRPCConLive(true);
3205 InvokeSetRPCConSrcMac("02:00:00:00:00:01");
3206 auto timePoint = std::chrono::steady_clock::now() -
3207 std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(CONNECTION_TIMEOUT + 1.0));
3208
3209 InvokeSetRPCConLiveMonotonic(timePoint);
3210 InvokeRPCConCheckTimeout();
3211
3212 EXPECT_TRUE(InvokeGetRPCConLive());
3213 EXPECT_EQ(rawTransport->receivedRequests.size(), 1U);
3214}
3215
3217TEST_F(RpcTests, ConnectRequiresSourceMacForInitialConnection)
3218{
3219 EXPECT_THROW(conn->Connect(), std::invalid_argument);
3220}
3221
3223TEST_F(RpcTests, ConnectEstablishesBasicApplicationRelation)
3224{
3225 transport = std::make_unique<TestRpcTransport>();
3226 rawTransport = transport.get();
3227
3228 rawTransport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONNECT));
3229
3230 conn = MakeTestTransportRpcConnection(std::move(transport));
3231
3232 const MacAddress mac = String2Mac("02:00:00:00:00:01");
3233
3234 const auto result = conn->Connect(mac);
3235
3236 EXPECT_FALSE(result.has_value());
3237 EXPECT_TRUE(conn->IsConnected());
3238 EXPECT_EQ(conn->LocalAlarmRef(), 0x8001);
3239 EXPECT_FALSE(conn->HasAlarmCr());
3240 EXPECT_EQ(rawTransport->receivedRequests.size(), 1U);
3241}
3242
3244TEST_F(RpcTests, ConnectRequestsAlarmCr)
3245{
3246 transport = std::make_unique<TestRpcTransport>();
3247
3248 const Bytes alarm = MakeAlarmCrResponseBlock(0x4321);
3249
3250 Bytes responsePayload = alarm;
3251
3252 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONNECT, responsePayload));
3253
3254 conn = MakeTestTransportRpcConnection(std::move(transport));
3255
3256 const auto result = conn->Connect(String2Mac("02:00:00:00:00:01"), true);
3257
3258 EXPECT_FALSE(result.has_value());
3259 EXPECT_TRUE(conn->IsConnected());
3260 EXPECT_TRUE(conn->HasAlarmCr());
3261 EXPECT_EQ(conn->DeviceAlarmRef(), 0x4321);
3262}
3263
3265TEST_F(RpcTests, ConnectLeavesAlarmCrDisabledWhenResponseHasNoAlarmCr)
3266{
3267 transport = std::make_unique<TestRpcTransport>();
3268 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONNECT));
3269
3270 conn = MakeTestTransportRpcConnection(std::move(transport));
3271
3272 const auto result = conn->Connect(String2Mac("02:00:00:00:00:01"), true);
3273
3274 EXPECT_FALSE(result.has_value());
3275 EXPECT_FALSE(conn->HasAlarmCr());
3276 EXPECT_EQ(conn->DeviceAlarmRef(), -1);
3277}
3278
3280TEST_F(RpcTests, ConnectConvertsDevicePnioError)
3281{
3282 transport = std::make_unique<TestRpcTransport>();
3283
3284 transport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::RESPONSE, PNRPCHeader::CONNECT, MakePnioErrorNrdResponse(0x00000001)));
3285
3286 conn = MakeTestTransportRpcConnection(std::move(transport));
3287
3288 EXPECT_THROW(conn->Connect(String2Mac("02:00:00:00:00:01")), RPCConnectionError);
3289}
3290
3292TEST_F(RpcTests, ConnectConvertsRpcError)
3293{
3294 transport = std::make_unique<TestRpcTransport>();
3295
3296 transport->responseHandler = [](const Bytes&) -> Bytes
3297 {
3298 throw RPCError("transport failure");
3299 };
3300
3301 conn = MakeTestTransportRpcConnection(std::move(transport));
3302
3303 EXPECT_THROW(conn->Connect(String2Mac("02:00:00:00:00:01")), RPCConnectionError);
3304}
3305
3307TEST_F(RpcTests, ConnectReusesSourceMacDuringReconnect)
3308{
3309 transport = std::make_unique<TestRpcTransport>();
3310 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONNECT));
3311 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONNECT));
3312
3313 conn = MakeTestTransportRpcConnection(std::move(transport));
3314
3315 const MacAddress mac = String2Mac("02:00:00:00:00:01");
3316
3317 ASSERT_NO_THROW(conn->Connect(mac));
3318
3319 InvokeSetRPCConLive(true);
3320
3321 EXPECT_NO_THROW(conn->Connect());
3322}
3323
3325TEST_F(RpcTests, ConnectReplacesSourceMacDuringReconnect)
3326{
3327 transport = std::make_unique<TestRpcTransport>();
3328 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONNECT));
3329 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONNECT));
3330
3331 conn = MakeTestTransportRpcConnection(std::move(transport));
3332
3333 const MacAddress initialMac = String2Mac("02:00:00:00:00:01");
3334
3335 const MacAddress replacementMac = String2Mac("02:00:00:00:00:02");
3336
3337 ASSERT_NO_THROW(conn->Connect(initialMac));
3338
3339 auto rpcConMac = InvokeGetRPCConSrcMac();
3340 ASSERT_TRUE(rpcConMac.has_value());
3341
3342 ASSERT_NO_THROW(conn->Connect(replacementMac));
3343
3344 rpcConMac = InvokeGetRPCConSrcMac();
3345 EXPECT_EQ(rpcConMac.value(), replacementMac);
3346}
3347
3349TEST_F(RpcTests, ConnectRejectsMissingSourceMacDuringReconnect)
3350{
3351 conn = MakeTestTransportRpcConnection(std::make_unique<TestRpcTransport>());
3352
3353 InvokeSetRPCConLive(true);
3354 InvokeGetRPCConSrcMac().reset();
3355
3356 EXPECT_THROW(conn->Connect(), std::invalid_argument);
3357}
3359TEST_F(RpcTests, ConnectReturnsConnectResultForIocrSetup)
3360{
3361 transport = std::make_unique<TestRpcTransport>();
3362
3363 Bytes responsePayload = MakeIocrResponseBlock(1, 1, 0x8001);
3364
3365 const Bytes outputIocr = MakeIocrResponseBlock(2, 2, 0x8002);
3366
3367 responsePayload.insert(responsePayload.end(), outputIocr.begin(), outputIocr.end());
3368
3369 const Bytes alarm = MakeAlarmCrResponseBlock(0x4321);
3370
3371 responsePayload.insert(responsePayload.end(), alarm.begin(), alarm.end());
3372
3373 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONNECT, responsePayload));
3374
3375 conn = MakeTestTransportRpcConnection(std::move(transport));
3376
3377 IOCRSetup setup;
3378 setup.slots.push_back({
3379 .slot = 1,
3380 .subslot = 1,
3381 .inputLength = 4,
3382 .outputLength = 4,
3383 .moduleIdent = 0x42,
3384 .submoduleIdent = 0x99,
3385 });
3386
3387 const auto result = conn->Connect(String2Mac("02:00:00:00:00:01"), false, setup);
3388
3389 ASSERT_TRUE(result.has_value());
3390 EXPECT_EQ(result->inputFrameId, 0x8001);
3391 EXPECT_EQ(result->outputFrameId, 0x8002);
3392 EXPECT_EQ(result->deviceAlarmRef, 0x4321);
3393 EXPECT_TRUE(conn->HasAlarmCr());
3394}
3396TEST_F(RpcTests, ReadReturnsParsedIodResponse)
3397{
3398 constexpr std::uint16_t index = 0xAFF0;
3399 const Bytes record = {0x11, 0x22, 0x33};
3400
3401 transport = std::make_unique<TestRpcTransport>();
3402 rawTransport = transport.get();
3403
3404 transport->responses.push_back(
3405 MakeNrdRpcResponseBytes(
3407 MakeIodResponsePayload(7, 3, 4, index, record)));
3408
3409 conn = MakeTestTransportRpcConnection(std::move(transport));
3410
3411 InvokeSetRPCConLive(true);
3412 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3413
3414 const PNIODHeader result = conn->Read(7, 3, 4, index);
3415
3416 EXPECT_EQ(result.api, 7U);
3417 EXPECT_EQ(result.slot, 3U);
3418 EXPECT_EQ(result.subslot, 4U);
3419 EXPECT_EQ(result.index, index);
3420 EXPECT_EQ(result.payload, record);
3421 EXPECT_EQ(rawTransport->receivedRequests.size(), 1U);
3422}
3423
3425TEST_F(RpcTests, ReadThrowsPnioErrorForDeviceError)
3426{
3427 transport = std::make_unique<TestRpcTransport>();
3428
3429 transport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::RESPONSE, PNRPCHeader::READ, MakePnioErrorNrdResponse(1)));
3430
3431 conn = MakeTestTransportRpcConnection(std::move(transport));
3432
3433 InvokeSetRPCConLive(true);
3434 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3435
3436 EXPECT_THROW(conn->Read(0, 0, 1, 0xAFF0), PNIOError);
3437}
3439TEST_F(RpcTests, ReadImplicitReturnsParsedIodResponse)
3440{
3441 constexpr std::uint16_t index = 0xAFF0;
3442 const Bytes record = {0xAA, 0xBB};
3443
3444 transport = std::make_unique<TestRpcTransport>();
3445 rawTransport = transport.get();
3446
3447 transport->responses.push_back(MakeNrdRpcResponseBytes(
3449 MakeIodResponsePayload(0, 0, 1, index, record)));
3450
3451 conn = MakeTestTransportRpcConnection(std::move(transport));
3452
3453 const PNIODHeader result = conn->ReadImplicit(0, 0, 1, index);
3454
3455 EXPECT_EQ(result.index, index);
3456 EXPECT_EQ(result.payload, record);
3457 EXPECT_EQ(rawTransport->receivedRequests.size(), 1U);
3458}
3459
3461TEST_F(RpcTests, ReadImplicitThrowsPnioErrorForDeviceError)
3462{
3463 transport = std::make_unique<TestRpcTransport>();
3464
3465 transport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::RESPONSE, PNRPCHeader::IMPLICIT_READ, MakePnioErrorNrdResponse(1)));
3466
3467 conn = MakeTestTransportRpcConnection(std::move(transport));
3468
3469 EXPECT_THROW(conn->ReadImplicit(0, 0, 1, 0xAFF0), PNIOError);
3470}
3472TEST_F(RpcTests, WriteCompletesSuccessfully)
3473{
3474 transport = std::make_unique<TestRpcTransport>();
3475 rawTransport = transport.get();
3476
3477 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::WRITE));
3478
3479 conn = MakeTestTransportRpcConnection(std::move(transport));
3480
3481 InvokeSetRPCConLive(true);
3482 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3483
3484 EXPECT_NO_THROW(conn->Write(1, 2, 3., 0xAFF1, {0x10, 0x20, 0x30}));
3485
3486 EXPECT_EQ(rawTransport->receivedRequests.size(), 1U);
3487}
3488
3490TEST_F(RpcTests, WriteThrowsPnioErrorForDeviceError)
3491{
3492 transport = std::make_unique<TestRpcTransport>();
3493
3494 transport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::RESPONSE, PNRPCHeader::WRITE, MakePnioErrorNrdResponse(1)));
3495
3496 conn = MakeTestTransportRpcConnection(std::move(transport));
3497
3498 InvokeSetRPCConLive(true);
3499 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3500
3501 EXPECT_THROW(conn->Write(0, 0, 1, 0xAFF1, {0x01}), PNIOError);
3502}
3504TEST_F(RpcTests, ReadRawReturnsPayload)
3505{
3506 constexpr std::uint16_t index = 0xAFF0;
3507 const Bytes payload = {0x01, 0x02, 0x03};
3508
3509 transport = std::make_unique<TestRpcTransport>();
3510
3511 transport->responses.push_back(MakeNrdRpcResponseBytes(
3513 MakeIodResponsePayload(0, 0, 1, index, payload)));
3514
3515 conn = MakeTestTransportRpcConnection(std::move(transport));
3516
3517 InvokeSetRPCConLive(true);
3518 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3519
3520 EXPECT_EQ(conn->ReadRaw(index, 2, 3), payload);
3521}
3523TEST_F(RpcTests, ReadImWrappersReadAllIndividualIndices)
3524{
3525 transport = std::make_unique<TestRpcTransport>();
3526
3527 transport->responseHandler = [this](const Bytes& request) -> Bytes
3528 {
3529 const PNRPCHeader rpcRequest = PNRPCHeader::Parse(request);
3530
3531 const PNNRDData nrd = PNNRDData::Parse(rpcRequest.payload);
3532
3533 const PNIODHeader iod = PNIODHeader::Parse(nrd.payload);
3534
3535 return MakeNrdRpcResponseBytes(
3536 rpcRequest.operationNumber,
3537 MakeIodResponsePayload(0, 0, 1, iod.index,
3538 MakeImPayload(iod.index)));
3539 };
3540
3541 conn = MakeTestTransportRpcConnection(std::move(transport));
3542
3543 InvokeSetRPCConLive(true);
3544 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3545
3546 EXPECT_NO_THROW(conn->ReadIm0());
3547 EXPECT_NO_THROW(conn->ReadIm1());
3548 EXPECT_NO_THROW(conn->ReadIm2());
3549 EXPECT_NO_THROW(conn->ReadIm3());
3550 EXPECT_NO_THROW(conn->ReadIm4());
3551 EXPECT_NO_THROW(conn->ReadIm5());
3552 EXPECT_NO_THROW(conn->ReadIm6());
3553 EXPECT_NO_THROW(conn->ReadIm7());
3554 EXPECT_NO_THROW(conn->ReadIm8());
3555 EXPECT_NO_THROW(conn->ReadIm9());
3556 EXPECT_NO_THROW(conn->ReadIm10());
3557 EXPECT_NO_THROW(conn->ReadIm11());
3558 EXPECT_NO_THROW(conn->ReadIm12());
3559 EXPECT_NO_THROW(conn->ReadIm13());
3560 EXPECT_NO_THROW(conn->ReadIm14());
3561 EXPECT_NO_THROW(conn->ReadIm15());
3562}
3564TEST_F(RpcTests, ReadAllImReturnsAllSuccessfulRecords)
3565{
3566 transport = std::make_unique<TestRpcTransport>();
3567
3568 transport->responseHandler = [this](const Bytes& request) -> Bytes
3569 {
3570 const PNRPCHeader rpcRequest = PNRPCHeader::Parse(request);
3571
3572 const PNNRDData nrd = PNNRDData::Parse(rpcRequest.payload);
3573
3574 const PNIODHeader iod = PNIODHeader::Parse(nrd.payload);
3575
3576 return MakeNrdRpcResponseBytes(
3577 rpcRequest.operationNumber,
3578 MakeIodResponsePayload(0, 0, 1, iod.index,
3579 MakeImPayload(iod.index)));
3580 };
3581
3582 conn = MakeTestTransportRpcConnection(std::move(transport));
3583
3584 InvokeSetRPCConLive(true);
3585 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3586
3587 const AllIM result = conn->ReadAllIm();
3588
3589 EXPECT_TRUE(result.im0.has_value());
3590 EXPECT_TRUE(result.im1.has_value());
3591 EXPECT_TRUE(result.im2.has_value());
3592 EXPECT_TRUE(result.im3.has_value());
3593 EXPECT_TRUE(result.im4.has_value());
3594 EXPECT_TRUE(result.im5.has_value());
3595 EXPECT_TRUE(result.im6.has_value());
3596 EXPECT_TRUE(result.im7.has_value());
3597 EXPECT_TRUE(result.im8.has_value());
3598 EXPECT_TRUE(result.im9.has_value());
3599 EXPECT_TRUE(result.im10.has_value());
3600 EXPECT_TRUE(result.im11.has_value());
3601 EXPECT_TRUE(result.im12.has_value());
3602 EXPECT_TRUE(result.im13.has_value());
3603 EXPECT_TRUE(result.im14.has_value());
3604 EXPECT_TRUE(result.im15.has_value());
3605}
3606
3608TEST_F(RpcTests, ReadAllImIgnoresRpcErrorForOptionalRecord)
3609{
3610 transport = std::make_unique<TestRpcTransport>();
3611
3612 transport->responseHandler = [this](const Bytes& request) -> Bytes
3613 {
3614 const PNRPCHeader rpcRequest = PNRPCHeader::Parse(request);
3615
3616 const PNNRDData nrd = PNNRDData::Parse(rpcRequest.payload);
3617
3618 const PNIODHeader iod = PNIODHeader::Parse(nrd.payload);
3619
3620 if (iod.index == PNInM2::IDX)
3621 {
3622 throw RPCError("I&M2 unavailable");
3623 }
3624
3625 return MakeNrdRpcResponseBytes(
3626 rpcRequest.operationNumber,
3627 MakeIodResponsePayload(0, 0, 1, iod.index,
3628 MakeImPayload(iod.index)));
3629 };
3630
3631 conn = MakeTestTransportRpcConnection(std::move(transport));
3632
3633 InvokeSetRPCConLive(true);
3634 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3635
3636 const AllIM result = conn->ReadAllIm();
3637
3638 EXPECT_TRUE(result.im0.has_value());
3639 EXPECT_TRUE(result.im1.has_value());
3640 EXPECT_FALSE(result.im2.has_value());
3641 EXPECT_TRUE(result.im3.has_value());
3642}
3643
3645TEST_F(RpcTests, ReadAllImIgnoresStandardExceptionForOptionalRecord)
3646{
3647 transport = std::make_unique<TestRpcTransport>();
3648
3649 transport->responseHandler = [this](const Bytes& request) -> Bytes
3650 {
3651 const PNRPCHeader rpcRequest = PNRPCHeader::Parse(request);
3652
3653 const PNNRDData nrd = PNNRDData::Parse(rpcRequest.payload);
3654
3655 const PNIODHeader iod = PNIODHeader::Parse(nrd.payload);
3656
3657 if (iod.index == PNInM3::IDX)
3658 {
3659 return MakeNrdRpcResponseBytes(rpcRequest.operationNumber, {});
3660 }
3661
3662 return MakeNrdRpcResponseBytes(
3663 rpcRequest.operationNumber,
3664 MakeIodResponsePayload(0, 0, 1, iod.index,
3665 MakeImPayload(iod.index)));
3666 };
3667
3668 conn = MakeTestTransportRpcConnection(std::move(transport));
3669
3670 InvokeSetRPCConLive(true);
3671 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3672
3673 const AllIM result = conn->ReadAllIm();
3674
3675 EXPECT_TRUE(result.im0.has_value());
3676 EXPECT_TRUE(result.im1.has_value());
3677 EXPECT_FALSE(result.im3.has_value());
3678 EXPECT_TRUE(result.im4.has_value());
3679}
3680
3682TEST_F(RpcTests, ReadAllImPropagatesIm0Failure)
3683{
3684 transport = std::make_unique<TestRpcTransport>();
3685
3686 transport->responseHandler = [](const Bytes&) -> Bytes
3687 {
3688 throw RPCError("I&M0 unavailable");
3689 };
3690
3691 conn = MakeTestTransportRpcConnection(std::move(transport));
3692
3693 InvokeSetRPCConLive(true);
3694 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3695
3696 EXPECT_THROW(conn->ReadAllIm(), RPCError);
3697}
3699TEST_F(RpcTests, WriteMultipleReturnsEmptyForNoWrites)
3700{
3701 transport = std::make_unique<TestRpcTransport>();
3702 rawTransport = transport.get();
3703
3704 conn = MakeTestTransportRpcConnection(std::move(transport));
3705
3706 InvokeSetRPCConLive(true);
3707 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3708
3709 const auto result = conn->WriteMultiple({});
3710
3711 EXPECT_TRUE(result.empty());
3712 EXPECT_TRUE(rawTransport->receivedRequests.empty());
3713}
3714
3716TEST_F(RpcTests, WriteMultipleSendsMultipleWrites)
3717{
3718 transport = std::make_unique<TestRpcTransport>();
3719 rawTransport = transport.get();
3720
3721 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::WRITE, MakeWriteMultipleResponsePayload()));
3722
3723 conn = MakeTestTransportRpcConnection(std::move(transport));
3724
3725 InvokeSetRPCConLive(true);
3726 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3727
3728 const std::vector<WriteItem> writes = {
3729 {
3730 .slot = 1,
3731 .subslot = 1,
3732 .index = 0xAFF1,
3733 .data = {0x10, 0x20},
3734 .api = 0,
3735 },
3736 {
3737 .slot = 2,
3738 .subslot = 1,
3739 .index = 0xAFF2,
3740 .data = {0x30, 0x40, 0x50},
3741 .api = 0,
3742 },
3743 };
3744
3745 EXPECT_NO_THROW(conn->WriteMultiple(writes));
3746 EXPECT_EQ(rawTransport->receivedRequests.size(), 1U);
3747}
3748
3750TEST_F(RpcTests, WriteMultipleThrowsPnioErrorForDeviceError)
3751{
3752 transport = std::make_unique<TestRpcTransport>();
3753
3754 transport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::RESPONSE, PNRPCHeader::WRITE, MakePnioErrorNrdResponse(1)));
3755
3756 conn = MakeTestTransportRpcConnection(std::move(transport));
3757
3758 const std::vector<WriteItem> writes = {
3759 {
3760 .slot = 1,
3761 .subslot = 1,
3762 .index = 0xAFF1,
3763 .data = {0x10},
3764 .api = 0,
3765 },
3766 };
3767 InvokeSetRPCConLive(true);
3768 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3769 EXPECT_THROW(conn->WriteMultiple(writes), PNIOError);
3770}
3772TEST_F(RpcTests, ReadModuleDiffReturnsParsedModuleDifference)
3773{
3774 transport = std::make_unique<TestRpcTransport>();
3775
3776 transport->responses.push_back(MakeNrdRpcResponseBytes(
3778 MakeIodResponsePayload(0, 0, 1, MODULE_DIFF_BLOCK,
3779 MakeModuleDiffPayload())));
3780
3781 conn = MakeTestTransportRpcConnection(std::move(transport));
3782
3783 InvokeSetRPCConLive(true);
3784 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3785 const auto result = conn->ReadModuleDiff();
3786
3787 ASSERT_EQ(result.modules.size(), 1U);
3788 EXPECT_EQ(result.modules[0].slotNumber, 1U);
3789}
3791TEST_F(RpcTests, ReadDiagnosisUsesFullDiagnosisParser)
3792{
3793 transport = std::make_unique<TestRpcTransport>();
3794
3795 transport->responses.push_back(MakeNrdRpcResponseBytes(
3797 MakeIodResponsePayload(0, 0, 1, 0xF000,
3798 MakeDiagnosisPayload())));
3799
3800 conn = MakeTestTransportRpcConnection(std::move(transport));
3801
3802 InvokeSetRPCConLive(true);
3803 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3804
3805 const auto result = conn->ReadDiagnosis(3, 4, 0xF000);
3806
3807 ASSERT_EQ(result.entries.size(), 1U);
3808 EXPECT_EQ(result.slot, 3U);
3809 EXPECT_EQ(result.subslot, 4U);
3810}
3811
3813TEST_F(RpcTests, ReadDiagnosisFallsBackToSimpleParser)
3814{
3815 Bytes simple;
3816
3817 wire::PutU16(simple, 0x0010);
3818 wire::PutU16(simple, 6);
3819 wire::PutU8(simple, 1);
3820 wire::PutU8(simple, 0);
3821
3822 // Simple diagnosis data.
3823 // ChannelNumber(2) + ChannelProperties(2) + ErrorType(2).
3824 wire::PutU16(simple, 0);
3825 wire::PutU16(simple, 0);
3826 wire::PutU16(simple, 9); // -> "Error"
3827
3828 // Zero-filled sentinel.
3829 wire::PutU16(simple, 0);
3830 wire::PutU16(simple, 0);
3831 wire::PutU16(simple, 0);
3832
3833 transport = std::make_unique<TestRpcTransport>();
3834
3835 transport->responses.push_back(
3836 MakeNrdRpcResponseBytes(
3838 MakeIodResponsePayload(0, 0, 1, 0xF000, simple)));
3839
3840 conn = MakeTestTransportRpcConnection(std::move(transport));
3841
3842 InvokeSetRPCConLive(true);
3843 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3844
3845 const auto result = conn->ReadDiagnosis();
3846
3847 ASSERT_EQ(result.entries.size(), 1U);
3848 EXPECT_EQ(result.entries[0].base.errorTypeName, "Error");
3849 EXPECT_EQ(result.entries[0].base.errorType, 0x0009);
3850 EXPECT_EQ(result.entries[0].base.channelNumber, 0U);
3851}
3852
3854TEST_F(RpcTests, ReadDiagnosisReturnsEmptyForShortDiagnosisPayload)
3855{
3856 transport = std::make_unique<TestRpcTransport>();
3857
3858 transport->responses.push_back(MakeNrdRpcResponseBytes(
3860
3861 MakeIodResponsePayload(0, 0, 1, 0xF000, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06})));
3862
3863 conn = MakeTestTransportRpcConnection(std::move(transport));
3864
3865 InvokeSetRPCConLive(true);
3866 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3867
3868 const auto result = conn->ReadDiagnosis(7, 8, 0xF000);
3869
3870 EXPECT_TRUE(result.entries.empty());
3871 EXPECT_EQ(result.api, 0U);
3872 EXPECT_EQ(result.slot, 7U);
3873 EXPECT_EQ(result.subslot, 8U);
3874 EXPECT_EQ(result.rawData.size(), 6U);
3875}
3876
3878TEST_F(RpcTests, ReadDiagnosisReturnsEmptyForRpcError)
3879{
3880 transport = std::make_unique<TestRpcTransport>();
3881
3882 transport->responseHandler = [](const Bytes&) -> Bytes
3883 {
3884 throw RPCError("diagnosis unavailable");
3885 };
3886
3887 conn = MakeTestTransportRpcConnection(std::move(transport));
3888
3889 InvokeSetRPCConLive(true);
3890 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3891
3892 const auto result = conn->ReadDiagnosis(2, 3, 0xF000);
3893
3894 EXPECT_TRUE(result.entries.empty());
3895 EXPECT_EQ(result.api, 0U);
3896 EXPECT_EQ(result.slot, 2U);
3897 EXPECT_EQ(result.subslot, 3U);
3898}
3900TEST_F(RpcTests, ReadAllDiagnosisReturnsNonEmptyDiagnosisResults)
3901{
3902 transport = std::make_unique<TestRpcTransport>();
3903
3904 transport->responseHandler = [this](const Bytes& request) -> Bytes
3905 {
3906 const PNRPCHeader rpcRequest = PNRPCHeader::Parse(request);
3907
3908 const PNNRDData nrd = PNNRDData::Parse(rpcRequest.payload);
3909
3910 const PNIODHeader iod = PNIODHeader::Parse(nrd.payload);
3911
3912 if (iod.index == 0x800A)
3913 {
3914 return MakeNrdRpcResponseBytes(
3915 rpcRequest.operationNumber,
3916 MakeIodResponsePayload(0, 0, 1, iod.index, MakeDiagnosisPayload()));
3917 }
3918
3919 return MakeNrdRpcResponseBytes(
3920 rpcRequest.operationNumber,
3921 MakeIodResponsePayload(0, 0, 1, iod.index, {}));
3922 };
3923
3924 conn = MakeTestTransportRpcConnection(std::move(transport));
3925
3926 InvokeSetRPCConLive(true);
3927 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3928
3929 const auto result = conn->ReadAllDiagnosis();
3930
3931 ASSERT_EQ(result.size(), 1U);
3932 EXPECT_TRUE(result.contains(0x800A));
3933}
3934
3936TEST_F(RpcTests, ReadAllDiagnosisContinuesAfterRpcError)
3937{
3938 transport = std::make_unique<TestRpcTransport>();
3939
3940 transport->responseHandler = [this](const Bytes& request) -> Bytes
3941 {
3942 const PNRPCHeader rpcRequest = PNRPCHeader::Parse(request);
3943
3944 const PNNRDData nrd = PNNRDData::Parse(rpcRequest.payload);
3945
3946 const PNIODHeader iod = PNIODHeader::Parse(nrd.payload);
3947
3948 if (iod.index == 0x800B)
3949 {
3950 throw RPCError("probe failed");
3951 }
3952
3953 return MakeNrdRpcResponseBytes(
3954 rpcRequest.operationNumber,
3955
3956 MakeIodResponsePayload(0, 0, 1, iod.index, MakeDiagnosisPayload()));
3957 };
3958
3959 conn = MakeTestTransportRpcConnection(std::move(transport));
3960
3961 InvokeSetRPCConLive(true);
3962 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3963
3964 EXPECT_NO_THROW(conn->ReadAllDiagnosis());
3965}
3967TEST_F(RpcTests, ReadPdRealDataReturnsParsedData)
3968{
3969 transport = std::make_unique<TestRpcTransport>();
3970
3971 transport->responses.push_back(
3972 MakeNrdRpcResponseBytes(
3974 MakeIodResponsePayload(0, 0, 1, PD_REAL_DATA, {})));
3975
3976 conn = MakeTestTransportRpcConnection(std::move(transport));
3977
3978 InvokeSetRPCConLive(true);
3979 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3980
3981 EXPECT_NO_THROW(conn->ReadPdRealData());
3982}
3984TEST_F(RpcTests, ReadRealIdentificationDataReturnsParsedData)
3985{
3986 transport = std::make_unique<TestRpcTransport>();
3987
3988 transport->responses.push_back(
3989 MakeNrdRpcResponseBytes(
3991 MakeIodResponsePayload(0, 0, 1, REAL_ID_API, MakeRealIdentificationPayload())));
3992
3993 conn = MakeTestTransportRpcConnection(std::move(transport));
3994
3995 InvokeSetRPCConLive(true);
3996 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
3997
3998 const auto result = conn->ReadRealIdentificationData();
3999
4000 ASSERT_EQ(result.slots.size(), 1U);
4001 EXPECT_EQ(result.slots[0].slot, 1U);
4002 EXPECT_EQ(result.slots[0].submoduleIdent, 0x99U);
4003}
4005TEST_F(RpcTests, DiscoverSlotsReturnsRealIdentificationSlots)
4006{
4007 transport = std::make_unique<TestRpcTransport>();
4008
4009 transport->responses.push_back(
4010 MakeNrdRpcResponseBytes(
4012 MakeIodResponsePayload(0, 0, 1, REAL_ID_API, MakeRealIdentificationPayload())));
4013
4014 conn = MakeTestTransportRpcConnection(std::move(transport));
4015
4016 InvokeSetRPCConLive(true);
4017 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4018
4019 const auto result = conn->DiscoverSlots();
4020
4021 ASSERT_EQ(result.size(), 1U);
4022 EXPECT_EQ(result[0].slot, 1U);
4023}
4025TEST_F(RpcTests, DiscoverTopologyReturnsBothTopologyRecords)
4026{
4027 transport = std::make_unique<TestRpcTransport>();
4028
4029 transport->responses.push_back(
4030 MakeNrdRpcResponseBytes(
4032 MakeIodResponsePayload(0, 0, 1, PD_REAL_DATA, {})));
4033
4034 transport->responses.push_back(
4035 MakeNrdRpcResponseBytes(
4037 MakeIodResponsePayload(0, 0, 1, REAL_ID_API, MakeRealIdentificationPayload())));
4038
4039 conn = MakeTestTransportRpcConnection(std::move(transport));
4040
4041 InvokeSetRPCConLive(true);
4042 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4043
4044 const auto [pdReal, realId] = conn->DiscoverTopology();
4045
4046 EXPECT_EQ(pdReal.slots.size(), 0U);
4047 ASSERT_EQ(realId.slots.size(), 1U);
4048 EXPECT_EQ(realId.slots[0].slot, 1U);
4049}
4052TEST_F(RpcTests, EnumerateIndicesReportsAllProbeOutcomes)
4053{
4054 transport = std::make_unique<TestRpcTransport>();
4055
4056 transport->responseHandler = [this](const Bytes& request) -> Bytes
4057 {
4058 const PNRPCHeader rpcRequest = PNRPCHeader::Parse(request);
4059
4060 const PNNRDData nrd = PNNRDData::Parse(rpcRequest.payload);
4061
4062 const PNIODHeader iod = PNIODHeader::Parse(nrd.payload);
4063
4064 switch (iod.index)
4065 {
4066 case 0x1001:
4067 return MakeNrdRpcResponseBytes(
4068 rpcRequest.operationNumber,
4069 MakeIodResponsePayload(0, 0, 1, iod.index, {0x01, 0x02, 0x03}));
4070
4071 case 0x1002:
4072 return MakeNrdRpcResponseBytes(
4073 rpcRequest.operationNumber,
4074 MakeIodResponsePayload(0, 0, 1, iod.index, {}));
4075
4076 case 0x1003:
4077 return MakeRpcResponseBytes(
4079 rpcRequest.operationNumber, MakePnioErrorNrdResponse(1));
4080
4081 case 0x1004:
4082 throw RPCError("RPC probe failure");
4083
4084 default:
4085 throw RPCError("unexpected test index");
4086 }
4087 };
4088
4089 conn = MakeTestTransportRpcConnection(std::move(transport));
4090
4091 InvokeSetRPCConLive(true);
4092 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4093
4094 const auto result = conn->EnumerateIndices(1, 2, std::vector<std::uint16_t>{0x1001, 0x1002, 0x1003, 0x1004, 0x1001});
4095
4096 ASSERT_EQ(result.size(), 4U);
4097
4098 EXPECT_EQ(result.at(0x1001).status, "readable");
4099 EXPECT_EQ(result.at(0x1001).size, 3U);
4100
4101 EXPECT_EQ(result.at(0x1002).status, "empty");
4102 EXPECT_EQ(result.at(0x1002).size, 0U);
4103
4104 EXPECT_EQ(result.at(0x1003).status, "error");
4105 EXPECT_FALSE(result.at(0x1003).error.empty());
4106
4107 EXPECT_EQ(result.at(0x1004).status, "error");
4108 EXPECT_EQ(result.at(0x1004).error, "RPC probe failure");
4109}
4111TEST_F(RpcTests, SendControlRejectsDisconnectedConnection)
4112{
4113 InvokeSetRPCConLive(false);
4114
4115 EXPECT_THROW(InvokeSendControl(static_cast<BlockType>(0x0118), ControlCommand::PrmBegin), RPCError);
4116}
4117
4119TEST_F(RpcTests, SendControlSendsWithoutWaiting)
4120{
4121 InvokeSetRPCConLive(true);
4122 InvokeSetRPCConArUuid(0x11);
4123 InvokeSetRPCConSessionKey(0x1234);
4124
4125 const Bytes subBlocks = {0xAA, 0xBB};
4126
4127 const Bytes result = InvokeSendControl(static_cast<BlockType>(BLOCK_PRM_BEGIN_REQ), ControlCommand::PrmBegin, false, subBlocks);
4128
4129 EXPECT_TRUE(result.empty());
4130 ASSERT_EQ(rawTransport->sentRequests.size(), 1U);
4131
4132 const PNRPCHeader request = PNRPCHeader::Parse(rawTransport->sentRequests.back());
4133
4134 EXPECT_EQ(request.packetType, PNRPCHeader::REQUEST);
4135 EXPECT_EQ(request.operationNumber, PNRPCHeader::CONTROL);
4136
4137 const PNNRDData nrd = PNNRDData::Parse(request.payload);
4138
4139 EXPECT_GE(nrd.payload.size(), static_cast<std::size_t>(32));
4140
4141 const auto begin = nrd.payload.end() - subBlocks.size();
4142
4143 EXPECT_EQ(Bytes(begin, nrd.payload.end()), subBlocks);
4144}
4145
4147TEST_F(RpcTests, SendControlReturnsResponsePayload)
4148{
4149 transport = std::make_unique<TestRpcTransport>();
4150
4151 const Bytes expectedPayload = {0x10, 0x20, 0x30};
4152
4153 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONTROL, expectedPayload));
4154
4155 conn = MakeTestTransportRpcConnection(std::move(transport));
4156
4157 InvokeSetRPCConLive(true);
4158 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4159
4160 const Bytes result = InvokeSendControl(static_cast<BlockType>(BLOCK_PRM_BEGIN_REQ), ControlCommand::PrmBegin);
4161
4162 EXPECT_EQ(result, expectedPayload);
4163}
4164
4166TEST_F(RpcTests, SendControlThrowsPnioErrorForDeviceError)
4167{
4168 transport = std::make_unique<TestRpcTransport>();
4169
4170 transport->responses.push_back(MakeRpcResponseBytes(PNRPCHeader::RESPONSE, PNRPCHeader::CONTROL, MakePnioErrorNrdResponse(1)));
4171
4172 conn = MakeTestTransportRpcConnection(std::move(transport));
4173
4174 InvokeSetRPCConLive(true);
4175 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4176
4177 EXPECT_THROW(InvokeSendControl(static_cast<BlockType>(BLOCK_PRM_BEGIN_REQ), ControlCommand::PrmBegin), PNIOError);
4178}
4180TEST_F(RpcTests, PrmBeginSendsExpectedControlCommand)
4181{
4182 transport = std::make_unique<TestRpcTransport>();
4183 rawTransport = transport.get();
4184
4185 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONTROL));
4186
4187 conn = MakeTestTransportRpcConnection(std::move(transport));
4188
4189 InvokeSetRPCConLive(true);
4190 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4191
4192 InvokeSetRPCConArUuid(0x11);
4193 InvokeSetRPCConSessionKey(0x1234);
4194
4195 EXPECT_NO_THROW(conn->PrmBegin());
4196
4197 ASSERT_EQ(rawTransport->receivedRequests.size(), 1U);
4198
4199 const PNRPCHeader request = PNRPCHeader::Parse(rawTransport->receivedRequests.back());
4200 const PNNRDData nrd = PNNRDData::Parse(request.payload);
4202
4203 EXPECT_EQ(static_cast<std::uint16_t>(block.controlCommand), CONTROL_CMD_PRM_BEGIN);
4204}
4205
4207TEST_F(RpcTests, PrmEndSendsExpectedControlCommand)
4208{
4209 transport = std::make_unique<TestRpcTransport>();
4210 rawTransport = transport.get();
4211
4212 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONTROL));
4213
4214 conn = MakeTestTransportRpcConnection(std::move(transport));
4215
4216 InvokeSetRPCConLive(true);
4217 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4218
4219 EXPECT_NO_THROW(conn->PrmEnd());
4220
4221 ASSERT_EQ(rawTransport->receivedRequests.size(), 1U);
4222
4223 const PNRPCHeader request = PNRPCHeader::Parse(rawTransport->receivedRequests.back());
4224 const PNNRDData nrd = PNNRDData::Parse(request.payload);
4226
4227 EXPECT_EQ(static_cast<std::uint16_t>(block.controlCommand), CONTROL_CMD_PRM_END);
4228}
4229
4231TEST_F(RpcTests, ReadyForRtClass3SendsExpectedControlCommand)
4232{
4233 transport = std::make_unique<TestRpcTransport>();
4234 rawTransport = transport.get();
4235
4236 transport->responses.push_back(MakeNrdRpcResponseBytes(PNRPCHeader::CONTROL));
4237
4238 conn = MakeTestTransportRpcConnection(std::move(transport));
4239
4240 InvokeSetRPCConLive(true);
4241 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4242
4243 EXPECT_NO_THROW(conn->ReadyForRtClass3());
4244
4245 ASSERT_EQ(rawTransport->receivedRequests.size(), 1U);
4246
4247 const PNRPCHeader request = PNRPCHeader::Parse(rawTransport->receivedRequests.back());
4248 const PNNRDData nrd = PNNRDData::Parse(request.payload);
4250
4251 EXPECT_EQ(static_cast<std::uint16_t>(block.controlCommand), CONTROL_CMD_READY_FOR_RT_CLASS_3);
4252}
4253
4255TEST_F(RpcTests, ParseRawRpcHeaderRejectsShortData)
4256{
4257 const Bytes data(PNRPCHeader::kFixedSize - 1, 0);
4258
4259 const auto result = rpc::raw::ParseRawRpcHeader(data);
4260
4261 EXPECT_FALSE(result.has_value());
4262}
4263
4265TEST_F(RpcTests, ParseRawRpcHeaderParsesBigEndian)
4266{
4267 const Bytes payload = {0xAA, 0xBB, 0xCC};
4268
4269 const Bytes data = MakeRawRpcHeader(
4270 false,
4272 0x11223344,
4273 0x55667788,
4274 0x99AA,
4275 payload);
4276
4277 const auto result = rpc::raw::ParseRawRpcHeader(data);
4278
4279 ASSERT_TRUE(result.has_value());
4280
4281 EXPECT_EQ(result->version, 0x04);
4282 EXPECT_EQ(result->packetType, PNRPCHeader::REQUEST);
4283 EXPECT_EQ(result->flags1, 0x01);
4284 EXPECT_EQ(result->flags2, 0x02);
4285
4286 EXPECT_EQ(result->drep[0], 0x00);
4287 EXPECT_EQ(result->drep[1], 0x00);
4288 EXPECT_EQ(result->drep[2], 0x00);
4289
4290 EXPECT_FALSE(result->isLittleEndian);
4291
4292 EXPECT_EQ(result->interfaceVersion, 0x11223344U);
4293 EXPECT_EQ(result->sequenceNumber, 0x55667788U);
4294 EXPECT_EQ(result->operationNumber, 0x99AA);
4295
4296 EXPECT_EQ(result->serialHigh, 0x5A);
4297 EXPECT_EQ(result->serialLow, 0x00);
4298 EXPECT_EQ(result->payload, payload);
4299
4300 for (std::size_t i = 0; i < uuidLenght; ++i)
4301 {
4302 EXPECT_EQ(result->objectUuid[i], static_cast<std::uint8_t>(0x10 + i));
4303 EXPECT_EQ(result->interfaceUuid[i], static_cast<std::uint8_t>(0x20 + i));
4304 EXPECT_EQ(result->activityUuid[i], static_cast<std::uint8_t>(0x30 + i));
4305 }
4306}
4307
4309TEST_F(RpcTests, ParseRawRpcHeaderParsesLittleEndian)
4310{
4311 const Bytes payload = {0x01, 0x02, 0x03, 0x04};
4312
4313 const Bytes data = MakeRawRpcHeader(true,
4315 0x11223344,
4316 0x55667788,
4317 0x99AA,
4318 payload);
4319
4320 const auto result = rpc::raw::ParseRawRpcHeader(data);
4321
4322 ASSERT_TRUE(result.has_value());
4323
4324 EXPECT_EQ(result->packetType, PNRPCHeader::RESPONSE);
4325 EXPECT_TRUE(result->isLittleEndian);
4326
4327 EXPECT_EQ(result->interfaceVersion, 0x11223344U);
4328 EXPECT_EQ(result->sequenceNumber, 0x55667788U);
4329 EXPECT_EQ(result->operationNumber, 0x99AA);
4330
4331 EXPECT_EQ(result->payload, payload);
4332}
4333
4335TEST_F(RpcTests, ParseRawRpcHeaderPreservesSerialHigh)
4336{
4337 Bytes data = MakeRawRpcHeader(false,
4339 0x01020304,
4340 0x05060708,
4341 0x090A,
4342 {});
4343
4344 data[7] = 0xAB;
4345
4346 const auto result = rpc::raw::ParseRawRpcHeader(data);
4347
4348 ASSERT_TRUE(result.has_value());
4349 EXPECT_EQ(result->serialHigh, 0xAB);
4350}
4351
4353TEST_F(RpcTests, ParseRawRpcHeaderExtractsPayloadAfterFixedHeader)
4354{
4355 const Bytes payload = {0xDE, 0xAD, 0xBE, 0xEF};
4356
4357 const Bytes data = MakeRawRpcHeader(false,
4359 1,
4360 2,
4361 3,
4362 payload);
4363
4364 ASSERT_EQ(data.size(), PNRPCHeader::kFixedSize + payload.size());
4365
4366 const auto result = rpc::raw::ParseRawRpcHeader(data);
4367
4368 ASSERT_TRUE(result.has_value());
4369
4370 EXPECT_EQ(result->payload, payload);
4371}
4372
4374TEST_F(RpcTests, ApplicationReadyThrowsWhenNotConnected)
4375{
4376 InvokeSetRPCConLive(false);
4377
4378 EXPECT_THROW(conn->ApplicationReady(1.0), RPCError);
4379
4380 EXPECT_EQ(rawTransport->receiveCount, 0U);
4381}
4382
4384TEST_F(RpcTests, ApplicationReadySkipsInvalidRawRpcPacket)
4385{
4386 InvokeSetRPCConLive(true);
4387 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4388
4389 rawcTransport->QueueIncomingDatagram(Bytes(PNRPCHeader::kFixedSize - 1, 0x00));
4390
4391 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(false));
4392
4393 const Bytes result = conn->ApplicationReady(1.5);
4394
4395 EXPECT_EQ(rawcTransport->receiveCount, 2U);
4396 EXPECT_FALSE(result.empty());
4397 EXPECT_EQ(rawcTransport->sentRequests.size(), 1U);
4398}
4400TEST_F(RpcTests, ApplicationReadySkipsNonRequestPacket)
4401{
4402 InvokeSetRPCConLive(true);
4403 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4404
4405 rawcTransport->QueueIncomingDatagram(MakeRawRpcHeader(false,
4407 0x11223344,
4408 0x55667788,
4410 MakeApplicationReadyNrd(
4411 MakeApplicationReadyControlBlock(),
4412 false)));
4413
4414 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(false));
4415
4416 const Bytes result = conn->ApplicationReady(2.0);
4417
4418 EXPECT_FALSE(result.empty());
4419 EXPECT_EQ(rawcTransport->receiveCount, 2U);
4420 EXPECT_EQ(rawcTransport->sentRequests.size(), 1U);
4421}
4422
4424TEST_F(RpcTests, ApplicationReadySkipsNonControlOperation)
4425{
4426 InvokeSetRPCConLive(true);
4427 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4428
4429 const Bytes controlBlock = MakeApplicationReadyControlBlock();
4430 const Bytes nrd = MakeApplicationReadyNrd(controlBlock, false);
4431
4432 rawcTransport->QueueIncomingDatagram(MakeRawRpcHeader(
4433 false,
4435 0x11223344,
4436 0x55667788,
4438 nrd));
4439
4440 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(false));
4441
4442 const Bytes result = conn->ApplicationReady(3.0);
4443
4444 EXPECT_FALSE(result.empty());
4445 EXPECT_EQ(rawcTransport->receiveCount, 2U);
4446 EXPECT_EQ(rawcTransport->sentRequests.size(), 1U);
4447}
4448
4450TEST_F(RpcTests, ApplicationReadySkipsShortNrdPayload)
4451{
4452 InvokeSetRPCConLive(true);
4453 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4454
4455 const Bytes shortNrd(19, 0x00);
4456
4457 rawcTransport->QueueIncomingDatagram(MakeRawRpcHeader(
4458 false,
4460 0x11223344,
4461 0x55667788,
4463 shortNrd));
4464
4465 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(false));
4466
4467 const Bytes result = conn->ApplicationReady(4.0);
4468
4469 EXPECT_FALSE(result.empty());
4470 EXPECT_EQ(rawcTransport->receiveCount, 2U);
4471 EXPECT_EQ(rawcTransport->sentRequests.size(), 1U);
4472}
4474TEST_F(RpcTests, ApplicationReadySkipsShortNrdBody)
4475{
4476 InvokeSetRPCConLive(true);
4477 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4478
4479 const Bytes shortNrdBody(31, 0x00);
4480 const Bytes nrd = MakeApplicationReadyNrd(shortNrdBody, false);
4481
4482 rawcTransport->QueueIncomingDatagram(MakeRawRpcHeader(
4483 false,
4485 0x11223344,
4486 0x55667788,
4488 nrd));
4489
4490 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(false));
4491
4492 const Bytes result = conn->ApplicationReady(5.0);
4493
4494 EXPECT_FALSE(result.empty());
4495 EXPECT_EQ(rawcTransport->receiveCount, 2U);
4496 EXPECT_EQ(rawcTransport->sentRequests.size(), 1U);
4497}
4499TEST_F(RpcTests, ApplicationReadySkipsWrongControlBlockType)
4500{
4501 InvokeSetRPCConLive(true);
4502 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4503
4504 constexpr std::uint16_t wrongBlockType = 0x0110;
4505
4506 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(false, wrongBlockType));
4507
4508 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(false));
4509
4510 const Bytes result = conn->ApplicationReady(6.0);
4511
4512 EXPECT_FALSE(result.empty());
4513 EXPECT_EQ(rawcTransport->receiveCount, 2U);
4514 EXPECT_EQ(rawcTransport->sentRequests.size(), 1U);
4515}
4516
4518TEST_F(RpcTests, ApplicationReadyProcessesBigEndianRequest)
4519{
4520 InvokeSetRPCConLive(true);
4521 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4522
4523 const auto expectedEndpoint = asio::ip::udp::endpoint(
4524 asio::ip::make_address("192.168.1.20"),
4525 0x1234);
4526
4527 rawcTransport->receiveEndpoint = expectedEndpoint;
4528
4529 const Bytes expectedResult = MakeApplicationReadyControlBlock();
4530
4531 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(false));
4532
4533 const Bytes result = conn->ApplicationReady(7.0);
4534
4535 ASSERT_EQ(result, expectedResult);
4536 ASSERT_EQ(rawcTransport->sentRequests.size(), 1U);
4537
4538 EXPECT_EQ(rawcTransport->lastSendToEndpoint, expectedEndpoint);
4539
4540 const auto response = raw::ParseRawRpcHeader(rawcTransport->sentRequests.front());
4541
4542 ASSERT_TRUE(response.has_value());
4543
4544 EXPECT_EQ(response->version, 0x04);
4545 EXPECT_EQ(response->packetType, PNRPCHeader::RESPONSE);
4546 EXPECT_FALSE(response->isLittleEndian);
4547 EXPECT_EQ(response->interfaceVersion, 0x11223344U);
4548 EXPECT_EQ(response->sequenceNumber, 0x55667788U);
4549 EXPECT_EQ(response->operationNumber, PNRPCHeader::CONTROL);
4550
4551 ASSERT_EQ(response->payload.size(), 20U + PNIODReleaseBlock::kSize);
4552
4553 const Bytes responseNrdBody(response->payload.begin() + 20, response->payload.end());
4554
4555 const PNIODReleaseBlock responseBlock = PNIODReleaseBlock::Parse(responseNrdBody);
4556
4557 EXPECT_EQ(static_cast<std::uint16_t>(responseBlock.blockHeader[0]) << 8 | responseBlock.blockHeader[1],
4559
4560 EXPECT_EQ(responseBlock.controlCommand, ControlCommand::Done);
4561
4562 EXPECT_EQ(responseBlock.sessionKey, conn->SessionKey());
4563 EXPECT_TRUE(InvokeGetRPCConLive());
4564}
4566TEST_F(RpcTests, ApplicationReadyProcessesLittleEndianRequest)
4567{
4568 InvokeSetRPCConLive(true);
4569 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4570
4571 const auto expectedEndpoint = asio::ip::udp::endpoint(
4572 asio::ip::make_address("10.0.0.20"),
4573 0x4321);
4574
4575 rawcTransport->receiveEndpoint = expectedEndpoint;
4576
4577 const Bytes expectedResult = MakeApplicationReadyControlBlock();
4578
4579 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(true));
4580
4581 const Bytes result = conn->ApplicationReady(8.0);
4582
4583 ASSERT_EQ(result, expectedResult);
4584 ASSERT_EQ(rawcTransport->sentRequests.size(), 1U);
4585
4586 const auto response = raw::ParseRawRpcHeader(rawcTransport->sentRequests.front());
4587
4588 ASSERT_TRUE(response.has_value());
4589
4590 EXPECT_EQ(response->packetType, PNRPCHeader::RESPONSE);
4591 // EXPECT_TRUE(response->isLittleEndian); Jaa... dit moet nog dynamisch op het response Peelen!
4592 EXPECT_EQ(response->interfaceVersion, 0x11223344U);
4593 EXPECT_EQ(response->sequenceNumber, 0x55667788U);
4594 EXPECT_EQ(response->operationNumber, PNRPCHeader::CONTROL);
4595
4596 ASSERT_EQ(response->payload.size(), 20U + PNIODReleaseBlock::kSize);
4597
4598 const Bytes responseNrdBody(response->payload.begin() + 20, response->payload.end());
4599
4600 const PNIODReleaseBlock responseBlock = PNIODReleaseBlock::Parse(responseNrdBody);
4601
4602 EXPECT_EQ(static_cast<std::uint16_t>(responseBlock.blockHeader[0]) << 8 | responseBlock.blockHeader[1],
4604
4605 EXPECT_EQ(responseBlock.controlCommand, ControlCommand::Done);
4606
4607 EXPECT_EQ(responseBlock.sessionKey, conn->SessionKey());
4608}
4610TEST_F(RpcTests, ApplicationReadyAcceptsUnexpectedControlCommand)
4611{
4612 InvokeSetRPCConLive(true);
4613 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4614
4615 rawcTransport->QueueIncomingDatagram(MakeApplicationReadyRequest(
4616 false,
4619
4620 const Bytes result = conn->ApplicationReady(9.0);
4621
4622 EXPECT_EQ(result, MakeApplicationReadyControlBlock(BLOCK_IOD_CONTROL_APP_READY_REQ, ControlCommand::ReadyForRtClass3));
4623
4624 EXPECT_EQ(rawcTransport->receiveCount, 1U);
4625 EXPECT_EQ(rawcTransport->sentRequests.size(), 1U);
4626}
4627
4629TEST_F(RpcTests, ApplicationReadyPropagatesReceiveException)
4630{
4631 InvokeSetRPCConLive(true);
4632 InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::now());
4633
4634 rawcTransport->receiveException = std::make_exception_ptr(RPCError("Receive failed"));
4635
4636 EXPECT_THROW(conn->ApplicationReady(1.0), RPCError);
4637
4638 EXPECT_EQ(rawcTransport->receiveCount, 1U);
4639 EXPECT_TRUE(rawcTransport->sentRequests.empty());
4640}
4642TEST_F(RpcTests, DisconnectSendsReleaseRequestAndClearsLive)
4643{
4644 InvokeSetRPCConLive(true);
4645
4646 const auto expectedArUuid = StringToUuidBytes("deadbeef-1234-5678-9abc-001122334455");
4647 const auto expectedSessionKey = static_cast<std::uint16_t>(0x3456);
4648
4649 InvokeSetRPCConArUuid(expectedArUuid);
4650 InvokeSetRPCConSessionKey(expectedSessionKey);
4651
4652 conn->Disconnect();
4653
4654 ASSERT_EQ(rawTransport->sentRequests.size(), 1U);
4655 EXPECT_FALSE(InvokeGetRPCConLive());
4656
4657 const PNRPCHeader request = PNRPCHeader::Parse(rawTransport->sentRequests.front());
4658
4659 EXPECT_EQ(request.packetType, PNRPCHeader::REQUEST);
4660 EXPECT_EQ(request.operationNumber, PNRPCHeader::RELEASE);
4661
4662 const PNNRDData nrd = PNNRDData::Parse(request.payload);
4663
4664 ASSERT_EQ(nrd.payload.size(), PNIODReleaseBlock::kSize);
4665
4667
4668 ASSERT_EQ(static_cast<std::uint16_t>(release.blockHeader[0] << 8U | release.blockHeader[1]),
4669 static_cast<std::uint16_t>(BlockType::ReleaseRequest));
4670
4671 EXPECT_EQ(release.padding1, 0U);
4672 EXPECT_EQ(release.arUuid, expectedArUuid);
4673 EXPECT_EQ(release.sessionKey, expectedSessionKey);
4674 EXPECT_EQ(release.padding2, 0U);
4675 EXPECT_EQ(release.controlCommand, ControlCommand::Release);
4676 EXPECT_EQ(release.controlBlockProperties, 0U);
4677}
4678
4680TEST_F(RpcTests, DisconnectBuildsCorrectReleaseBlock)
4681{
4682 InvokeSetRPCConLive(true);
4683
4684 const auto expectedArUuid = StringToUuidBytes("deadbeef-1234-5678-9abc-001122334455");
4685 const auto expectedSessionKey = static_cast<std::uint16_t>(0x3456);
4686
4687 InvokeSetRPCConArUuid(expectedArUuid);
4688 InvokeSetRPCConSessionKey(expectedSessionKey);
4689
4690 conn->Disconnect();
4691
4692 ASSERT_EQ(rawTransport->sentRequests.size(), 1U);
4693
4694 const PNRPCHeader request = PNRPCHeader::Parse(rawTransport->sentRequests.front());
4695
4696 const PNNRDData nrd = PNNRDData::Parse(request.payload);
4697
4699
4700 const Bytes blockHeaderBytes(release.blockHeader.begin(), release.blockHeader.end());
4701
4702 const PNBlockHeader block = PNBlockHeader::Parse(blockHeaderBytes);
4703
4704 EXPECT_EQ(block.blockType, BlockType::ReleaseRequest);
4705 EXPECT_EQ(block.blockLength, 28U);
4706 EXPECT_EQ(block.blockVersionHigh, 0x01U);
4707 EXPECT_EQ(block.blockVersionLow, 0x00U);
4708
4709 EXPECT_EQ(release.padding1, 0U);
4710 EXPECT_EQ(release.arUuid, expectedArUuid);
4711 EXPECT_EQ(release.sessionKey, expectedSessionKey);
4712 EXPECT_EQ(release.padding2, 0U);
4713 EXPECT_EQ(release.controlCommand, ControlCommand::Release);
4714 EXPECT_EQ(release.controlBlockProperties, 0U);
4715}
4716
4718TEST_F(RpcTests, DisconnectDoesNothingWhenNotLive)
4719{
4720 InvokeSetRPCConLive(false);
4721
4722 conn->Disconnect();
4723
4724 EXPECT_FALSE(InvokeGetRPCConLive());
4725 EXPECT_TRUE(rawTransport->sentRequests.empty());
4726}
4728TEST_F(RpcTests, DisconnectSwallowsSendExceptionAndClearsLive)
4729{
4730 InvokeSetRPCConLive(true);
4731
4732 rawTransport->sendException = std::make_exception_ptr(RPCError("simulated RELEASE send failure"));
4733
4734 EXPECT_NO_THROW(conn->Disconnect());
4735
4736 EXPECT_FALSE(InvokeGetRPCConLive());
4737 EXPECT_TRUE(rawTransport->sentRequests.empty());
4738}
4740TEST_F(RpcTests, DisconnectAlwaysClearsLive)
4741{
4742 InvokeSetRPCConLive(true);
4743
4744 rawTransport->sendException = std::make_exception_ptr(std::runtime_error("send failed"));
4745
4746 conn->Disconnect();
4747
4748 EXPECT_FALSE(InvokeGetRPCConLive());
4749}
4751TEST_F(RpcTests, CloseDisconnectsAndClosesBothTransports)
4752{
4753 InvokeSetRPCConLive(true);
4754
4755 conn->Close();
4756
4757 EXPECT_FALSE(InvokeGetRPCConLive());
4758
4759 EXPECT_EQ(rawTransport->sentRequests.size(), 1U);
4760
4761 EXPECT_EQ(rawTransport->closeCount, 1U);
4762 EXPECT_EQ(rawcTransport->closeCount, 1U);
4763}
4764
4766TEST_F(RpcTests, CloseClosesBothTransportsWhenNotLive)
4767{
4768 InvokeSetRPCConLive(false);
4769
4770 conn->Close();
4771
4772 EXPECT_FALSE(InvokeGetRPCConLive());
4773
4774 EXPECT_TRUE(rawTransport->sentRequests.empty());
4775
4776 EXPECT_EQ(rawTransport->closeCount, 1U);
4777 EXPECT_EQ(rawcTransport->closeCount, 1U);
4778}
4779} // namespace profinet::rpc
Device not found via DCP.
Definition exceptions.h:563
PNIO application error with error codes.
Definition exceptions.h:692
Failed to establish RPC connection.
Definition exceptions.h:615
DCE/RPC protocol errors.
Definition exceptions.h:575
RPC returned a fault response.
Definition exceptions.h:599
Parsed PROFINET device information from a DCP response.
Definition dcp.h:268
std::string name
Station name.
Definition dcp.h:282
Connection to a PROFINET IO-Device: establishes an AR (Application Relationship) and performs acyclic...
Definition rpc.h:138
static std::uint16_t ParseIocrResponse(const Bytes &responseData, int iocrType)
Parse an IOCRBlockRes from a CONNECT response.
Definition rpc.cpp:1132
std::uint16_t SessionKey() const
The session key for this connection.
Definition rpc.h:428
static Bytes BuildIocrBlock(IocrType iocrType, const IOCRSetup &setup)
Build an IOCRBlockReq (header + API object list) for the CONNECT request.
Definition rpc.cpp:703
const std::array< std::uint8_t, uuidLenght > & ArUuid() const
The AR UUID for this connection.
Definition rpc.h:421
Deterministic RPC transport used to test RPCCon without UDP.
Definition test_rpc.cpp:504
std::exception_ptr sendException
Optional exception rethrown by Send().
Definition test_rpc.cpp:557
std::function< Bytes(const Bytes &)> ResponseHandler
Definition test_rpc.cpp:506
std::deque< Bytes > incomingDatagrams
Incoming datagrams returned by Receive() in FIFO order.
Definition test_rpc.cpp:551
asio::ip::udp::endpoint receiveEndpoint
Last endpoint supplied by Receive().
Definition test_rpc.cpp:545
void Open(const std::string &address, std::uint16_t port, bool isListener=false) override
Open the transport and configure the remote endpoint.
Definition test_rpc.cpp:564
std::size_t receiveCount
Number of times Receive() was called.
Definition test_rpc.cpp:533
std::vector< Bytes > receivedRequests
Requests passed to SendReceive().
Definition test_rpc.cpp:515
std::deque< Bytes > responses
Queued responses returned by SendReceive().
Definition test_rpc.cpp:512
std::vector< Bytes > sentRequests
Requests passed to Send().
Definition test_rpc.cpp:518
std::exception_ptr receiveException
Optional exception rethrown by Receive().
Definition test_rpc.cpp:554
std::size_t closeCount
Number of times Close() was called.
Definition test_rpc.cpp:539
Bytes receiveData
Address supplied to Open().
Definition test_rpc.cpp:548
bool openedAsListener
Listener flag supplied to Open().
Definition test_rpc.cpp:527
void Send(const Bytes &request) override
Definition test_rpc.cpp:632
ResponseHandler responseHandler
Optional callback used to generate a response from each request.
Definition test_rpc.cpp:509
void QueueIncomingDatagram(const Bytes &data)
Queues one incoming datagram for Receive().
Definition test_rpc.cpp:597
Bytes SendReceive(const Bytes &request, std::chrono::milliseconds) override
Definition test_rpc.cpp:613
Bytes Receive(std::chrono::milliseconds, asio::ip::udp::endpoint &remoteEndpoint) override
Wait for an incoming byte array and capture the sender's endpoint.
Definition test_rpc.cpp:577
void SendTo(const Bytes &data, const asio::ip::udp::endpoint &targetEndpoint) override
Send a byte array of data to a specific remote Asio endpoint.
Definition test_rpc.cpp:606
asio::ip::udp::endpoint lastSendToEndpoint
Last endpoint supplied to SendTo().
Definition test_rpc.cpp:542
std::uint16_t openedPort
Port supplied to Open().
Definition test_rpc.cpp:524
std::string openedAddress
Address supplied to Open().
Definition test_rpc.cpp:521
void Close() noexcept override
Close the transport.
Definition test_rpc.cpp:642
std::size_t sendToCount
Number of times SendTo() was called.
Definition test_rpc.cpp:536
std::size_t openCount
Number of times Open() was called.
Definition test_rpc.cpp:530
Bytes MakeStationNameResponse(const MacAddress &deviceMac, const MacAddress &controllerMac, const std::string &stationName)
Builds a DCP response containing a station-name block.
Definition test_rpc.cpp:385
Bytes MakeModuleDiffPayload()
Builds a valid ModuleDiffBlock payload.
Definition test_rpc.cpp:840
void InvokeSetRPCConArUuid(const std::array< std::uint8_t, uuidLenght > &bytes)
Definition test_rpc.cpp:938
Bytes MakeApplicationReadyRequest(bool littleEndian, std::uint16_t blockType=BLOCK_IOD_CONTROL_APP_READY_REQ, ControlCommand controlCommand=ControlCommand::ApplicationReady)
Creates a raw DCE/RPC Application Ready request.
Bytes MakeImPayload(std::uint16_t index)
Creates a deterministic I&M payload of the required size.
Definition test_rpc.cpp:762
bool IsAllZeroUuid(const std::array< std::uint8_t, uuidLenght > &uuid)
Checks whether every byte in a UUID is zero.
Definition test_rpc.cpp:75
Bytes MakeDcpResponseFrame(const MacAddress &src, const MacAddress &dst, DcpServiceId serviceId, std::uint32_t xid, const Bytes &payload)
Builds an untagged Ethernet frame containing a DCP response.
Definition test_rpc.cpp:358
std::unique_ptr< RPCCon > conn
Definition test_rpc.cpp:918
Bytes MakeIocrResponseBlock(std::uint16_t iocrType, std::uint16_t iocrReference, std::uint16_t frameId)
Creates a serialized IOCR response block.
Definition test_rpc.cpp:899
TestRpcTransport * rawcTransport
Definition test_rpc.cpp:923
Bytes MakePnioErrorNrdResponse(std::uint32_t status, const Bytes &payload={})
Creates an NRD response containing a PROFINET error status.
Definition test_rpc.cpp:713
Bytes MakeCompleteEpmTower(const std::string &interfaceUuid, std::uint16_t major, std::uint16_t minor, std::uint16_t port, const std::array< std::uint8_t, 4 > &ipAddress)
Builds a complete EPM tower for an endpoint.
Definition test_rpc.cpp:187
Bytes MakeRpcResponseBytes(std::uint8_t packetType=PNRPCHeader::RESPONSE, std::uint16_t operationNumber=0, const Bytes &payload={})
Creates an empty successful RPC response.
Definition test_rpc.cpp:667
Bytes MakeEpmLookupBody(const std::array< std::uint8_t, uuidLenght > &objectUuid, const std::string &annotation, const Bytes &tower)
Builds a synthetic EPM lookup response body containing one entry.
Definition test_rpc.cpp:228
Bytes InvokeBuildAlarmCrBlock(int transport, int priority)
Calls the private RPCCon::BuildAlarmCrBlock helper.
Definition test_rpc.cpp:441
void InvokeSetRPCConLiveMonotonic(std::chrono::steady_clock::time_point time)
Definition test_rpc.cpp:962
void InvokeSetRPCConArUuid(std::uint8_t byte)
Definition test_rpc.cpp:933
Bytes MakeInterfaceTowerRhs(std::uint16_t minor)
Builds the RHS of an EPM interface tower floor.
Definition test_rpc.cpp:171
void InvokeSetRPCConLive(bool live)
Definition test_rpc.cpp:929
PNRPCHeader InvokeSendReceive(const PNRPCHeader &request)
Definition test_rpc.cpp:925
void ReplyToEpmLookup(asio::ip::udp::socket &server, const Bytes &response, Bytes &request)
Receives an EPM lookup request and sends a synthetic response.
Definition test_rpc.cpp:312
dcp::DCPDeviceDescription MakeRpcTestDeviceInfo()
Creates a deterministic DCP device description suitable for RPC constructor tests.
Definition test_rpc.cpp:59
PNRPCHeader CreateRpc(const Bytes &payload, RPCCon *conn=nullptr)
Calls the private RPC header construction helper.
Definition test_rpc.cpp:88
std::optional< MacAddress > InvokeGetRPCConSrcMac()
Definition test_rpc.cpp:975
Bytes MakeAlarmCrResponseBlock(std::uint16_t localAlarmReference)
Creates a valid AlarmCR response block.
Definition test_rpc.cpp:467
Bytes MakeInterfaceTowerLhs(const std::string &uuid, std::uint16_t major)
Builds the LHS of an EPM interface tower floor.
Definition test_rpc.cpp:153
PNNRDData InvokeCreateNrd(const Bytes &payload)
Calls the private RPCCon::CreateNrd helper.
Definition test_rpc.cpp:430
Bytes MakeDeviceResponse(const MacAddress &deviceMac, const MacAddress &controllerMac, const std::string &stationName, const Bytes &additionalBlocks={})
Builds a DCP response containing multiple station/device blocks.
Definition test_rpc.cpp:400
Bytes MakeEpmResponse(const Bytes &body)
Builds a minimal DCE/RPC response containing the supplied body.
Definition test_rpc.cpp:272
Bytes MakeDcpBlock(DcpOption option, std::uint8_t suboption, const Bytes &payload)
Builds a serialized DCP block.
Definition test_rpc.cpp:331
Bytes MakeEpmTower(const std::vector< Bytes > &floors)
Builds a complete EPM tower from encoded floors.
Definition test_rpc.cpp:133
Bytes InvokeBuildIocrBlock(IocrType iocrType, const IOCRSetup &setup)
Calls the IOCR block construction helper.
Definition test_rpc.cpp:103
Bytes MakeBlockHeader(BlockType blockType, std::uint16_t blockLength)
Creates an unrelated serialized block header.
Definition test_rpc.cpp:491
Bytes MakeRawRpcHeader(bool littleEndian, std::uint8_t packetType, std::uint32_t interfaceVersion, std::uint32_t sequenceNumber, std::uint16_t operationNumber, const Bytes &payload)
Creates a raw DCE/RPC message for unit-test input.
bool IsZeroUuid(const std::array< std::uint8_t, uuidLenght > &uuid)
Checks whether a UUID byte array contains only zero bytes.
Definition test_rpc.cpp:213
std::unique_ptr< RPCCon > MakeTestRpcConnection()
Creates a deterministic RPC connection for helper tests.
Definition test_rpc.cpp:458
std::map< dcp::BlockKey, std::vector< std::uint8_t > > MakeStationBlocks(const std::string &stationName)
Creates a map of DCP blocks containing only a station name.
Definition test_rpc.cpp:414
std::unique_ptr< RPCCon > MakeTestTransportRpcConnection(std::unique_ptr< IRpcTransport > transport, std::unique_ptr< IRpcTransport > ctransport=nullptr)
Creates a test RPC connection using the injected transport.
Definition test_rpc.cpp:652
Bytes MakeWriteMultipleResponsePayload()
Builds a minimal WriteMultiple response containing one successful entry.
Definition test_rpc.cpp:864
Bytes MakeRealIdentificationPayload()
Builds a v1.0 RealIdentificationData payload.
Definition test_rpc.cpp:823
Bytes MakeIodResponsePayload(std::uint32_t api, std::uint16_t slot, std::uint16_t subslot, std::uint16_t idx, const Bytes &payload)
Creates a valid IOD response block.
Definition test_rpc.cpp:734
void InvokeSetRPCConInfo(std::uint8_t deviceHigh, std::uint8_t deviceLow, std::uint8_t vendorHigh, std::uint8_t vendorLow)
Definition test_rpc.cpp:947
Bytes MakeDiagnosisPayload()
Builds a diagnosis block containing one ChannelDiagnosis entry.
Definition test_rpc.cpp:803
TestRpcTransport * rawTransport
Definition test_rpc.cpp:920
void TearDown() override
Definition test_rpc.cpp:51
void SetUp() override
Definition test_rpc.cpp:42
std::unique_ptr< TestRpcTransport > ctransport
Definition test_rpc.cpp:922
Bytes MakeApplicationReadyControlBlock(std::uint16_t blockType=BLOCK_IOD_CONTROL_APP_READY_REQ, ControlCommand controlCommand=ControlCommand::ApplicationReady)
Creates a serialized IOD control block.
std::uint16_t InvokeParseIocrResponse(const Bytes &responseData, int iocrType)
Definition test_rpc.cpp:913
Bytes InvokeSendControl(BlockType blockType, ControlCommand controlCommand, bool waitResponse=true, const Bytes &subBlocks={})
Definition test_rpc.cpp:979
std::unique_ptr< TestRpcTransport > transport
Definition test_rpc.cpp:919
Bytes MakeNrdRpcResponseBytes(std::uint16_t operationNumber, const Bytes &payload={})
Creates an RPC response containing an NRD response.
Definition test_rpc.cpp:700
void InvokeSetRPCConSrcMac(std::string mac)
Definition test_rpc.cpp:957
Bytes MakeEpmTowerFloor(const Bytes &lhs, const Bytes &rhs)
Builds one EPM tower floor from an LHS and RHS field.
Definition test_rpc.cpp:114
Bytes MakeNrdResponseBytes(const Bytes &payload={})
Creates a successful NRD response wrapping the supplied payload.
Definition test_rpc.cpp:682
Bytes MakeApplicationReadyNrd(const Bytes &block, bool littleEndian)
Creates an NRD payload containing an IOD control block.
int InvokeParseAlarmCrResponse(const Bytes &responseData)
Calls the private RPCCon::ParseAlarmCrResponse helper.
Definition test_rpc.cpp:450
void InvokeSetRPCConSessionKey(std::uint16_t byte)
Definition test_rpc.cpp:943
Lightweight cursor for parsing a big-endian byte buffer without copies.
Definition wire.h:192
void SkipTo(std::size_t offset)
Move the read position to an absolute byte offset.
Definition wire.h:343
std::uint32_t U32LittleEndian()
Read and consume a little-endian 32-bit value.
Definition wire.h:291
std::uint16_t U16LittleEndian()
Read and consume a little-endian 16-bit value.
Definition wire.h:279
GoogleMock implementation of IRawEthernetSocket.
constexpr DcpBlockKey MakeBlockKey(std::uint16_t option, std::uint8_t suboption)
Definition dcp.h:231
const std::string DCP_MULTICAST_MAC
DCP Identify/Get/Set multicast destination MAC.
Definition dcp.h:55
std::optional< RawRpcHeader > ParseRawRpcHeader(const Bytes &data)
Parses a raw network byte vector into a structured DCE/RPC v4 Connectionless Header.
Definition rpc.cpp:1858
const std::string UUID_PNIO_PARAMETER_SERVER
PROFINET IO-ParameterServer interface UUID.
Definition rpcTypes.h:43
std::array< std::uint8_t, uuidLenght > BuildObjectUuid(std::uint8_t b10, std::uint8_t b11, std::uint8_t b12, std::uint8_t b13, std::uint8_t b14, std::uint8_t b15)
Builds a PROFINET object UUID.
Definition rpc.cpp:56
constexpr std::uint16_t BLOCK_IOD_CONTROL_APP_READY_REQ
Block type: IODControlReqAppReady.
Definition rpcTypes.h:65
constexpr std::uint16_t BLOCK_PRM_BEGIN_REQ
Block type: PrmBeginReq.
Definition rpcTypes.h:71
std::string UuidBytesToString(const std::array< std::uint8_t, uuidLenght > &data)
Format a 16-byte DCE/RPC UUID as a canonical string.
Definition rpc.cpp:106
const std::string UUID_PNIO_SUPERVISOR
PROFINET IO-Supervisor interface UUID.
Definition rpcTypes.h:41
dcp::DCPDeviceDescription GetStationInfo(IRawEthernetSocket &sock, const MacAddress &src, const std::string &name, double timeoutSec=DEFAULT_TIMEOUT *2)
Resolve a device by PROFINET station name via DCP.
Definition rpc.cpp:509
constexpr std::uint16_t CONTROL_CMD_PRM_BEGIN
Control command bit: PrmBegin.
Definition rpcTypes.h:88
const std::string UUID_PNIO_CONTROLLER
PROFINET IO-Controller interface UUID.
Definition rpcTypes.h:39
const std::string UUID_EPM_V4
Endpoint Mapper interface UUID.
Definition rpcTypes.h:35
std::optional< EPMEndpoint > ParseEpmTower(const Bytes &towerData)
Parses a DCE/RPC EPM tower into a PROFINET endpoint.
Definition rpc.cpp:239
constexpr std::uint16_t CONTROL_CMD_PRM_END
Control command bit: PrmEnd.
Definition rpcTypes.h:76
const std::string UUID_PNIO_DEVICE
PROFINET IO-Device interface UUID.
Definition rpcTypes.h:37
constexpr double CONNECTION_TIMEOUT
Maximum idle time in seconds before an AR is considered to need re-connection.
Definition rpcTypes.h:58
constexpr std::uint32_t EPM_INQUIRY_INTERFACE
EPM inquiry type: specific interface.
Definition rpcTypes.h:53
constexpr std::uint16_t CONTROL_CMD_READY_FOR_RT_CLASS_3
Control command bit: ReadyForRTClass3.
Definition rpcTypes.h:86
constexpr std::uint16_t BLOCK_IOD_CONTROL_APP_READY_RES
Block type: IODControlResAppReady.
Definition rpcTypes.h:67
std::array< std::uint8_t, uuidLenght > StringToUuidBytes(const std::string &uuidStr)
Parse a canonical UUID string into 16 raw DCE/RPC UUID bytes.
Definition rpc.cpp:126
TEST_F(RpcTests, UuidBytesToStringStringToUuidBytesRoundtrip)
constexpr std::uint32_t EPM_LOOKUP
EPM operation: Lookup.
Definition rpcTypes.h:49
void PutFixed(std::vector< std::uint8_t > &out, const std::array< std::uint8_t, N > &data)
Append a fixed-size byte array to a buffer.
Definition wire.h:124
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
void PutLittleEndian(std::vector< std::uint8_t > &out, T value)
Append an unsigned integral value to a buffer in little-endian order.
Definition wire.h:64
void PutU32(std::vector< std::uint8_t > &out, std::uint32_t value)
Append a 32-bit value to a buffer in big-endian order.
Definition wire.h:105
void PutBigEndian(std::vector< std::uint8_t > &out, T value)
Append an unsigned integral value to a buffer in big-endian order.
Definition wire.h:45
BlockType
PROFINET block types.
Definition indices.h:89
@ ModuleDifference
Module difference block.
@ ReleaseRequest
IOD Release request.
@ AlarmCrResponse
AlarmCR block response.
constexpr int uuidLenght
Constant lenght of a UUID.
Definition util.h:40
constexpr int blockHeaderLength
Constant lenght of a blockHeader.
Definition protocol.h:58
constexpr std::uint16_t PD_REAL_DATA
PD Real Data.
Definition indices.h:815
constexpr std::uint16_t MODULE_DIFF_BLOCK
Module Diff Block.
Definition indices.h:803
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
constexpr std::uint16_t IP_ETHERTYPE
EtherType value identifying IP frames (0x8000).
Definition util.h:32
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
Definition util.cpp:122
IocrType
PROFINET IOCR types.
Definition indices.h:463
@ Output
Output IOCR.
@ Input
Input IOCR.
ControlCommand
PROFINET IOD control command values.
Definition indices.h:61
@ ApplicationReady
Application Ready command.
@ Release
Release command.
@ ReadyForRtClass3
Ready for RT Class 3 command.
@ PrmBegin
Prm Begin command.
constexpr std::uint16_t MODULE_STATE_PROPER_MODULE
Module State Proper Module.
Definition indices.h:645
constexpr std::uint16_t REAL_ID_API
Real ID API.
Definition indices.h:806
@ GetSet
DCP GET/SET request or response.
std::string ToHex(const std::uint8_t *data, std::size_t len)
Hex-encode a byte buffer.
Definition util.cpp:104
DcpOption
DCP options.
Definition protocol.h:116
@ Device
Device identification.
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
Definition util.h:30
constexpr std::uint16_t SUBMODULE_STATE_OK
Submodule State Ok.
Definition indices.h:658
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
DcpServiceId
DCP service identifiers.
Definition protocol.h:84
@ Identify
IDENTIFY service.
@ ResponseSuccess
Successful response.
Bytes buffer
asio::ip::udp::endpoint remoteEndpoint
Shared data types used by the PROFINET DCE/RPC layer.
PROFINET DCE/RPC protocol structures and IO-Device connection API.
Plain (untagged) Ethernet II frame header.
Definition protocol.h:479
MacAddress src
Source MAC address.
Definition protocol.h:484
std::uint16_t type
EtherType field.
Definition protocol.h:487
static EthernetHeader Parse(const Bytes &data)
Parse an Ethernet header and payload from raw frame bytes.
Definition protocol.h:495
Bytes ToBytes() const
Serialize this header and its payload back to raw frame bytes.
Definition protocol.h:513
MacAddress dst
Destination MAC address.
Definition protocol.h:481
Bytes payload
Frame payload following the header.
Definition protocol.h:490
AlarmCRBlockReq: requests establishment of the alarm connection (part of the CONNECT request).
Definition protocol.h:2115
static constexpr std::uint16_t DEFAULT_MAX_ALARM_DATA_LENGTH
Default maximum alarm data length in bytes.
Definition protocol.h:2127
static constexpr std::uint16_t DEFAULT_TAG_HEADER_LOW
Default low word of the alarm VLAN tag header.
Definition protocol.h:2133
static constexpr BlockType BLOCK_TYPE
Block type identifier for AlarmCRBlockReq.
Definition protocol.h:2118
static PNAlarmCRBlockReq Parse(const Bytes &data)
Parse an AlarmCRBlockReq from raw bytes.
Definition protocol.h:2172
static constexpr std::uint16_t DEFAULT_TAG_HEADER_HIGH
Default high word of the alarm VLAN tag header.
Definition protocol.h:2130
std::uint16_t alarmCrType
Alarm CR type (always 1: Alarm).
Definition protocol.h:2140
std::uint16_t etherTypeLT
EtherType used for alarm frames (0x8892).
Definition protocol.h:2143
std::uint32_t alarmCrProperties
Alarm CR property flags (transport, priority).
Definition protocol.h:2146
static constexpr std::size_t kSize
Size in bytes of this block.
Definition protocol.h:2167
static constexpr std::uint16_t DEFAULT_RTA_RETRIES
Default number of RTA retries.
Definition protocol.h:2124
std::uint16_t localAlarmReference
Local alarm reference chosen by the controller.
Definition protocol.h:2155
std::uint16_t alarmCrTagHeaderHigh
High word of the alarm VLAN tag header.
Definition protocol.h:2161
std::uint16_t maxAlarmDataLength
Maximum alarm data length in bytes.
Definition protocol.h:2158
std::uint16_t alarmCrTagHeaderLow
Low word of the alarm VLAN tag header.
Definition protocol.h:2164
std::uint16_t rtaRetries
Number of RTA retransmission attempts.
Definition protocol.h:2152
std::uint16_t rtaTimeoutFactor
RTA (real-time acyclic) retransmission timeout factor.
Definition protocol.h:2149
static constexpr std::uint16_t DEFAULT_RTA_TIMEOUT_FACTOR
Default RTA timeout factor.
Definition protocol.h:2121
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
static PNBlockHeader Parse(const Bytes &data)
Parse a block header from raw bytes.
Definition protocol.h:1328
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
static constexpr std::uint16_t IODReadResponseHeader
Block type: IODReadResponseHeader.
Definition protocol.h:1289
DCP request block: Option(1) + SubOption(1) + Length(2) + payload(Length).
Definition protocol.h:666
std::uint16_t length
Length in bytes of Payload.
Definition protocol.h:675
std::uint8_t suboption
DCP suboption code.
Definition protocol.h:672
Bytes payload
Block payload.
Definition protocol.h:678
DcpOption option
DCP option code.
Definition protocol.h:668
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:704
static constexpr std::pair< std::uint8_t, std::uint8_t > NAME_OF_STATION
Block option/suboption: station name.
Definition protocol.h:767
DCP (Discovery and Configuration Protocol) frame header.
Definition protocol.h:584
std::uint32_t xId
Transaction ID correlating requests and responses.
Definition protocol.h:600
Bytes ToBytes() const
Serialize this header and its block-list payload back to raw bytes.
Definition protocol.h:637
std::uint16_t respDelay
Response delay factor (requests) or reserved (responses).
Definition protocol.h:603
static PNDCPHeader Parse(const Bytes &data)
Parse a DCP header and block-list payload from raw bytes.
Definition protocol.h:617
DcpFrameId frameId
DCP frame ID identifying the service/multicast group.
Definition protocol.h:591
std::uint16_t length
Length in bytes of the block list that follows.
Definition protocol.h:606
DcpServiceId serviceId
DCP service ID (GET/SET/IDENTIFY/HELLO).
Definition protocol.h:594
DcpServiceType serviceType
DCP service type (REQUEST/RESPONSE/...).
Definition protocol.h:597
Bytes payload
DCP block list payload.
Definition protocol.h:609
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
IOD read/write request/response header (RPC body for READ/WRITE/IMPLICIT_READ).
Definition protocol.h:1190
std::uint32_t api
API number.
Definition protocol.h:1205
std::uint16_t slot
Slot number.
Definition protocol.h:1208
std::array< std::uint8_t, blockHeaderLength > blockHeader
Nested 6-byte block header (type + length + version).
Definition protocol.h:1196
Bytes ToBytes() const
Serialize this header and payload back to raw bytes.
Definition protocol.h:1262
static PNIODHeader Parse(const Bytes &data)
Parse an IOD header and payload from raw bytes.
Definition protocol.h:1237
std::uint16_t sequenceNumber
Sequence number for tracking retries.
Definition protocol.h:1199
static constexpr std::size_t kFixedSize
Size in bytes of the fixed portion of this header.
Definition protocol.h:1232
Bytes payload
Record data payload.
Definition protocol.h:1229
std::uint16_t subslot
Subslot number.
Definition protocol.h:1211
std::uint32_t length
Length in bytes of Payload.
Definition protocol.h:1220
std::uint16_t index
Record data index being read or written.
Definition protocol.h:1217
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
static constexpr std::size_t kSize
Size in bytes of this block.
Definition protocol.h:1472
std::uint16_t padding1
Reserved/padding.
Definition protocol.h:1454
static PNIODReleaseBlock Parse(const Bytes &data)
Parse a control block from raw bytes.
Definition protocol.h:1477
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
static constexpr std::size_t kSize
Size in bytes of this record.
Definition protocol.h:1572
static constexpr std::uint16_t IDX
Record data index for I&M0.
Definition protocol.h:1520
static constexpr std::uint16_t IDX
Record data index for I&M1.
Definition protocol.h:1627
static constexpr std::size_t kSize
Size in bytes of this record.
Definition protocol.h:1640
static constexpr std::uint16_t IDX
Record data index for I&M2.
Definition protocol.h:1671
static constexpr std::size_t kSize
Size in bytes of this record.
Definition protocol.h:1681
static constexpr std::uint16_t IDX
Record data index for I&M3.
Definition protocol.h:1712
static constexpr std::size_t kSize
Size in bytes of this record.
Definition protocol.h:1722
static constexpr std::uint16_t IDX
Record data index for I&M4.
Definition protocol.h:1752
static constexpr std::size_t kSize
Size in bytes of this record.
Definition protocol.h:1762
static constexpr std::uint16_t IDX
Record data index for I&M5.
Definition protocol.h:1792
static constexpr std::size_t kSize
Size in bytes of this record.
Definition protocol.h:1802
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
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
static constexpr std::size_t kFixedSize
Size in bytes of the fixed portion of this header.
Definition protocol.h:979
static constexpr std::uint16_t READ
Operation number: READ.
Definition protocol.h:902
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
static constexpr std::uint16_t WRITE
Operation number: WRITE.
Definition protocol.h:905
static std::array< std::uint8_t, uuidLenght > IfaceUuidDevice()
Interface UUID identifying the IO-Device role.
Definition protocol.h:983
std::uint32_t sequenceNumber
Sequence number within the activity.
Definition protocol.h:952
static constexpr std::uint8_t REJECT
Packet type: REJECT.
Definition protocol.h:881
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
static constexpr std::uint8_t RESPONSE
Packet type: RESPONSE.
Definition protocol.h:869
static constexpr std::uint16_t IMPLICIT_READ
Operation number: IMPLICIT_READ.
Definition protocol.h:911
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 lengthOfBody
Length in bytes of Payload.
Definition protocol.h:964
static constexpr std::uint8_t FAULT
Packet type: FAULT.
Definition protocol.h:872
All I&M records a device supports, read in one call.
Definition rpcTypes.h:451
std::optional< PNInM7 > im7
I&M7 (reserved), if supported.
Definition rpcTypes.h:467
std::optional< PNInM15 > im15
I&M15 (reserved), if supported.
Definition rpcTypes.h:483
std::optional< PNInM9 > im9
I&M9 (reserved), if supported.
Definition rpcTypes.h:471
std::optional< PNInM14 > im14
I&M14 (reserved), if supported.
Definition rpcTypes.h:481
std::optional< PNInM1 > im1
I&M1 (tag function/location), if supported.
Definition rpcTypes.h:455
std::optional< PNInM5 > im5
I&M5 (free-text annotation), if supported.
Definition rpcTypes.h:463
std::optional< PNInM10 > im10
I&M10 (reserved), if supported.
Definition rpcTypes.h:473
std::optional< PNInM3 > im3
I&M3 (free-text descriptor), if supported.
Definition rpcTypes.h:459
std::optional< PNInM6 > im6
I&M6 (reserved), if supported.
Definition rpcTypes.h:465
std::optional< PNInM13 > im13
I&M13 (reserved), if supported.
Definition rpcTypes.h:479
std::optional< PNInM11 > im11
I&M11 (reserved), if supported.
Definition rpcTypes.h:475
std::optional< PNInM2 > im2
I&M2 (installation date), if supported.
Definition rpcTypes.h:457
std::optional< PNInM12 > im12
I&M12 (reserved), if supported.
Definition rpcTypes.h:477
std::optional< PNInM4 > im4
I&M4 (PROFIsafe signature), if supported.
Definition rpcTypes.h:461
std::optional< PNInM0 > im0
I&M0 (mandatory identification data).
Definition rpcTypes.h:453
std::optional< PNInM8 > im8
I&M8 (reserved), if supported.
Definition rpcTypes.h:469
One RPC endpoint advertised by a device's Endpoint Mapper.
Definition IRPCCon.h:34
std::string InterfaceName() const
Human-readable interface name.
Definition rpc.cpp:211
std::string interfaceUuid
Interface UUID this endpoint serves.
Definition IRPCCon.h:36
Configuration for establishing cyclic IO alongside an AR.
Definition rpcTypes.h:149
std::uint16_t watchdogFactor
Watchdog factor (missed-frame tolerance before a timeout fault).
Definition rpcTypes.h:160
std::vector< IOSlot > slots
Slots to include in the cyclic data frames.
Definition rpcTypes.h:151
std::vector< std::string > Validate() const
Sanity-check this configuration.
Definition rpc.cpp:546
double CycleTimeMs() const
Compute the cycle time in milliseconds.
Definition rpcTypes.h:176
std::uint16_t reductionRatio
Reduction ratio relative to the send clock.
Definition rpcTypes.h:157
std::uint16_t sendClockFactor
Send clock base factor (31.25us units).
Definition rpcTypes.h:154
std::uint32_t CycleTimeUs() const
Compute the cycle time in microseconds from SendClockFactor/ReductionRatio.
Definition rpcTypes.h:169
Configuration options for an RPC connection.
Definition rpcTypes.h:516
double timeoutSec
Timeout in seconds for RPC operations.
Definition rpcTypes.h:518
std::uint16_t rpcPort
UDP port used for RPC communication.
Definition rpcTypes.h:521
std::uint16_t frameId
Ethernet FrameID.
Bytes frame
std::uint8_t suboption
Definition test_dcp.cpp:390
Bytes payload
Definition test_dcp.cpp:392
DcpOption option
Definition test_dcp.cpp:389