PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_dcp.cpp
Go to the documentation of this file.
1
3
4#include <gtest/gtest.h>
5
6#include <algorithm>
7
9#include "profinet/dcp.h"
10#include "profinet/exceptions.h"
11
12namespace profinet::dcp
13{
14using testing::Return;
15using testing::StrictMock;
16
17namespace
18{
27Bytes BuildDcpResponseFrame(const MacAddress& src,
28 const MacAddress& dst,
30 std::uint8_t suboption,
31 const Bytes& payload,
32 std::uint32_t xId = 0x12345678)
33{
34 PNDCPBlockRequest block;
35 block.option = option;
36 block.suboption = suboption;
37 block.length = static_cast<std::uint16_t>(payload.size());
38 block.payload = payload;
39
40 const Bytes blockBytes = block.ToBytes();
41
42 PNDCPHeader dcp;
43 dcp.frameId = DcpFrameId::GetSet;
44 dcp.serviceId = DcpServiceId::Get;
45 dcp.serviceType = DcpServiceType::ResponseSuccess;
46 dcp.xId = xId;
47 dcp.respDelay = 0;
48 dcp.length = static_cast<std::uint16_t>(blockBytes.size());
49 dcp.payload = blockBytes;
50
51 EthernetHeader eth;
52 eth.dst = dst;
53 eth.src = src;
54 eth.type = PROFINET_ETHERTYPE;
55 eth.payload = dcp.ToBytes();
56
57 return eth.ToBytes();
58}
59
67Bytes BuildSetResponseFrame(const MacAddress& src,
68 const MacAddress& dst,
70 bool shortPayload = false)
71{
72 const Bytes payload{
73 static_cast<std::uint8_t>(error),
74 0x00,
75 0x00,
76 };
77
78 Bytes responsePayload;
79 if (shortPayload)
80 {
81 responsePayload = {
82 static_cast<std::uint8_t>(DcpOption::Control),
83 static_cast<std::uint8_t>(DcpControlSuboption::Response),
84 0x00,
85 0x01,
86 static_cast<std::uint8_t>(error),
87 };
88 }
89 else
90 {
91 responsePayload = {
92 static_cast<std::uint8_t>(DcpOption::Control),
93 static_cast<std::uint8_t>(DcpControlSuboption::Response),
94 0x00,
95 0x03,
96 payload[0],
97 payload[1],
98 payload[2],
99 };
100 }
101
102 PNDCPHeader dcp;
103 dcp.frameId = DcpFrameId::GetSet;
104 dcp.serviceId = DcpServiceId::Set;
105 dcp.serviceType = DcpServiceType::ResponseSuccess;
106 dcp.xId = 0x12345678;
107 dcp.respDelay = 0;
108 dcp.length = static_cast<std::uint16_t>(responsePayload.size());
109 dcp.payload = responsePayload;
110
111 EthernetHeader eth;
112 eth.dst = dst;
113 eth.src = src;
114 eth.type = PROFINET_ETHERTYPE;
115 eth.payload = dcp.ToBytes();
116
117 return eth.ToBytes();
118}
119
131Bytes BuildDcpFrame(
132 const MacAddress& dst,
133 const MacAddress& src,
135 DcpServiceId serviceId,
136 DcpServiceType serviceType,
137 std::uint32_t xid,
138 const Bytes& payload)
139{
140 PNDCPHeader hdr;
141 hdr.frameId = frameId;
142 hdr.serviceId = serviceId;
143 hdr.serviceType = serviceType;
144 hdr.xId = xid;
145 hdr.respDelay = 0;
146 hdr.length = static_cast<std::uint16_t>(payload.size());
147 hdr.payload = payload;
148
149 EthernetHeader eth;
150 eth.dst = dst;
151 eth.src = src;
152 eth.type = PROFINET_ETHERTYPE;
153 eth.payload = hdr.ToBytes();
154
155 return eth.ToBytes();
156}
157
165Bytes BuildDcpBlock(
167 std::uint8_t suboption,
168 const Bytes& payload)
169{
170 PNDCPBlockRequest block;
171 block.option = option;
172 block.suboption = suboption;
173 block.length = static_cast<std::uint16_t>(payload.size());
174 block.payload = payload;
175
176 auto result = block.ToBytes();
177
178 if ((payload.size() % 2U) != 0U)
179 {
180 result.push_back(0x00);
181 }
182
183 return result;
184}
185
195Bytes BuildIdentifyResponse(
196 const MacAddress& dst,
197 const MacAddress& src,
198 DcpServiceType serviceType,
199 std::uint32_t xid,
200 const Bytes& payload)
201{
202 return BuildDcpFrame(
203 dst,
204 src,
207 serviceType,
208 xid,
209 payload);
210}
211
221Bytes BuildHelloFrame(
222 const MacAddress& dst,
223 const MacAddress& src,
224 DcpServiceId serviceId,
225 DcpServiceType serviceType,
226 const Bytes& payload)
227{
228 return BuildDcpFrame(
229 dst,
230 src,
232 serviceId,
233 serviceType,
234 0x12345678,
235 payload);
236}
237
239Bytes BuildDcpPayload(DcpServiceType serviceType, const Bytes& payload)
240{
241 PNDCPHeader hdr;
242 hdr.frameId = DcpFrameId::GetSet;
243 hdr.serviceId = DcpServiceId::Set;
244 hdr.serviceType = serviceType;
245 hdr.xId = 0x12345678;
246 hdr.respDelay = 0;
247 hdr.length = static_cast<std::uint16_t>(payload.size());
248 hdr.payload = payload;
249 return hdr.ToBytes();
250}
251
253Bytes BuildDcpResponse(DcpServiceType serviceType, const Bytes& payload)
254{
255 EthernetHeader eth;
256 eth.dst = MacAddress{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
257 eth.src = MacAddress{0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
258 eth.type = PNDCPHeader::ETHER_TYPE;
259 eth.payload = BuildDcpPayload(serviceType, payload);
260 return eth.ToBytes();
261}
262
264Bytes BuildVlanDcpResponse(DcpServiceType serviceType, const Bytes& payload)
265{
266 EthernetVLANHeader eth;
267 eth.dst = MacAddress{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
268 eth.src = MacAddress{0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
269 eth.tPid = VLAN_ETHERTYPE;
270 eth.tCI = 0x0001;
271 eth.type = PNDCPHeader::ETHER_TYPE;
272 eth.payload = BuildDcpPayload(serviceType, payload);
273 return eth.ToBytes();
274}
275
287Bytes BuildReadResponseFrame(const MacAddress& src,
288 const MacAddress& dst,
289 DcpServiceType serviceType,
290 std::uint32_t xid,
291 const Bytes& payload,
292 std::uint16_t dcpLength,
295{
296 PNDCPHeader dcp;
297 dcp.frameId = frameId;
298 dcp.serviceId = serviceId;
299 dcp.serviceType = serviceType;
300 dcp.xId = xid;
301 dcp.respDelay = 0;
302 dcp.length = dcpLength;
303 dcp.payload = payload;
304
305 EthernetHeader eth;
306 eth.dst = dst;
307 eth.src = src;
308 eth.type = PROFINET_ETHERTYPE;
309 eth.payload = dcp.ToBytes();
310
311 return eth.ToBytes();
312}
313
324Bytes BuildVlanReadResponseFrame(const MacAddress& src,
325 const MacAddress& dst,
326 DcpServiceType serviceType,
327 std::uint32_t xid,
328 const Bytes& payload,
329 std::uint16_t dcpLength,
330 std::uint16_t innerType = PROFINET_ETHERTYPE)
331{
332 PNDCPHeader dcp;
333 dcp.frameId = DcpFrameId::GetSet;
334 dcp.serviceId = DcpServiceId::Get;
335 dcp.serviceType = serviceType;
336 dcp.xId = xid;
337 dcp.respDelay = 0;
338 dcp.length = dcpLength;
339 dcp.payload = payload;
340 const Bytes dcpBytes = dcp.ToBytes();
341
342 Bytes vlanPayload{0x00, 0x00};
343
344 wire::PutBigEndian(vlanPayload, innerType);
345 wire::PutFixed(vlanPayload, dcpBytes);
346
347 EthernetHeader eth;
348 eth.dst = dst;
349 eth.src = src;
350 eth.type = VLAN_ETHERTYPE;
351 eth.payload = vlanPayload;
352
353 return eth.ToBytes();
354}
355
362Bytes BuildReadResponseBlock(DcpOption option,
363 std::uint8_t suboption,
364 const Bytes& payload)
365{
366 PNDCPBlockRequest block;
367 block.option = option;
368 block.suboption = suboption;
369 block.length = static_cast<std::uint16_t>(payload.size());
370 block.payload = payload;
371
372 Bytes result = block.ToBytes();
373
374 if (payload.size() % 2 != 0)
375 {
376 result.push_back(0);
377 }
378
379 return result;
380}
381
387struct TestDcpBlock
388{
390 std::uint8_t suboption;
391 std::uint16_t length;
393};
394
400TestDcpBlock ParseTestDcpBlock(const Bytes& data, std::size_t offset)
401{
402 EXPECT_LE(offset + 4U, data.size());
403 if (offset + 4U > data.size())
404 {
405 return {};
406 }
407
408 const auto option = static_cast<DcpOption>(data[offset]);
409 const auto suboption = data[offset + 1U];
410
411 wire::Reader r(data);
412 r.SkipTo(offset + 2U);
413 const auto length = r.U16();
414
415 EXPECT_LE(offset + 4U + length, data.size());
416 if (offset + 4U + length > data.size())
417 {
418 return {};
419 }
420 return {
421 .option = option,
422 .suboption = suboption,
423 .length = length,
424 .payload = Bytes(data.begin() + offset + 4U,
425 data.begin() + offset + 4U + length),
426 };
427}
436Bytes BuildReceiveHelloFrame(const MacAddress& src,
437 const MacAddress& dst,
438 DcpServiceId serviceId,
439 DcpServiceType serviceType,
440 const Bytes& payload)
441{
442 PNDCPHeader dcp;
443 dcp.frameId = DcpFrameId::Hello;
444 dcp.serviceId = serviceId;
445 dcp.serviceType = serviceType;
446 dcp.xId = 0x12345678;
447 dcp.respDelay = 0;
448 dcp.length = static_cast<std::uint16_t>(payload.size());
449 dcp.payload = payload;
450
451 EthernetHeader eth;
452 eth.dst = dst;
453 eth.src = src;
454 eth.type = PROFINET_ETHERTYPE;
455 eth.payload = dcp.ToBytes();
456
457 return eth.ToBytes();
458}
459
469Bytes BuildVlanReceiveHelloFrame(const MacAddress& src,
470 const MacAddress& dst,
471 DcpServiceId serviceId,
472 DcpServiceType serviceType,
473 const Bytes& payload,
474 std::uint16_t innerType = PROFINET_ETHERTYPE)
475{
476 PNDCPHeader dcp;
477 dcp.frameId = DcpFrameId::Hello;
478 dcp.serviceId = serviceId;
479 dcp.serviceType = serviceType;
480 dcp.xId = 0x12345678;
481 dcp.respDelay = 0;
482 dcp.length = static_cast<std::uint16_t>(payload.size());
483 dcp.payload = payload;
484
485 const Bytes dcpBytes = dcp.ToBytes();
486
487 Bytes vlanPayload{0x00, 0x00};
488
489 wire::PutBigEndian(vlanPayload, innerType);
490 wire::PutFixed(vlanPayload, dcpBytes);
491
492 EthernetHeader eth;
493 eth.dst = dst;
494 eth.src = src;
495 eth.type = VLAN_ETHERTYPE;
496 eth.payload = vlanPayload;
497
498 return eth.ToBytes();
499}
500
507Bytes BuildHelloBlock(DcpOption option,
508 std::uint8_t suboption,
509 const Bytes& payload)
510{
511 PNDCPBlockRequest block;
512 block.option = option;
513 block.suboption = suboption;
514 block.length = static_cast<std::uint16_t>(payload.size());
515 block.payload = payload;
516
517 Bytes result = block.ToBytes();
518
519 if (payload.size() % 2U != 0U)
520 {
521 result.push_back(0x00);
522 }
523
524 return result;
525}
526} // namespace
527
529TEST(DcpTest, DecodeDeviceRoleUnknown)
530{
531 const auto roles = DecodeDeviceRole(0x00);
532 ASSERT_EQ(roles.size(), 1U);
533 EXPECT_EQ(roles[0], "Unknown");
534}
535
537TEST(DcpTest, DecodeDeviceRoleIoDevice)
538{
539 const auto roles = DecodeDeviceRole(static_cast<std::uint8_t>(DcpDeviceRole::IoDevice));
540 ASSERT_EQ(roles.size(), 1U);
541 EXPECT_EQ(roles[0], "IoDevice");
542}
543
545TEST(DcpTest, DecodeDeviceRoleIoController)
546{
547 const auto roles = DecodeDeviceRole(static_cast<std::uint8_t>(DcpDeviceRole::IoController));
548 ASSERT_EQ(roles.size(), 1U);
549 EXPECT_EQ(roles[0], "IoController");
550}
551
553TEST(DcpTest, DecodeDeviceRoleIoMultidevice)
554{
555 const auto roles = DecodeDeviceRole(static_cast<std::uint8_t>(DcpDeviceRole::IoMultidevice));
556 ASSERT_EQ(roles.size(), 1U);
557 EXPECT_EQ(roles[0], "IoMultidevice");
558}
559
561TEST(DcpTest, DecodeDeviceRolePnSupervisor)
562{
563 const auto roles = DecodeDeviceRole(static_cast<std::uint8_t>(DcpDeviceRole::PnSupervisor));
564 ASSERT_EQ(roles.size(), 1U);
565 EXPECT_EQ(roles[0], "PnSupervisor");
566}
567
569TEST(DcpTest, DecodeDeviceRoleAllRoles)
570{
572 ASSERT_EQ(roles.size(), 4U);
573 EXPECT_EQ(roles[0], "IoDevice");
574 EXPECT_EQ(roles[1], "IoController");
575 EXPECT_EQ(roles[2], "IoMultidevice");
576 EXPECT_EQ(roles[3], "PnSupervisor");
577}
578
580TEST(DcpTest, GetBlockNameKnownOptionAndSuboption)
581{
582 EXPECT_EQ(GetBlockName(DcpOption::Ip, DcpIpSuboption::Mac), "Ip/Mac");
583}
584
586TEST(DcpTest, GetBlockNameKnownDeviceAndDhcpSuboptions)
587{
588 EXPECT_EQ(GetBlockName(DcpOption::Device, DcpDeviceSuboption::Name), "Device/Name");
589 EXPECT_EQ(GetBlockName(DcpOption::Dhcp, DcpDhcpSuboption::Hostname), "Dhcp/Hostname");
590}
591
593TEST(DcpTest, GetBlockNameKnownControlAndDeviceInitiativeSuboptions)
594{
595 EXPECT_EQ(GetBlockName(DcpOption::Control, DcpControlSuboption::Signal), "Control/Signal");
597}
598
600TEST(DcpTest, GetBlockNameKnownOptionUnknownSuboption)
601{
602 EXPECT_EQ(GetBlockName(DcpOption::Ip, 0xFE), "Ip/0xFE");
603}
604
606TEST(DcpTest, GetBlockNameKnownOptionWithoutSuboptionTable)
607{
608 EXPECT_EQ(GetBlockName(DcpOption::Reserved, 0x01), "Reserved/0x01");
609}
610
612TEST(DcpTest, GetBlockNameVendorOption)
613{
614 EXPECT_EQ(GetBlockName(0x80, 0x01), "Vendor-0x80/0x01");
615 EXPECT_EQ(GetBlockName(0xFE, 0x01), "Vendor-0xFE/0x01");
616}
617
619TEST(DcpTest, GetBlockNameUnknownOption)
620{
621 EXPECT_EQ(GetBlockName(0x00, 0x01), "Opt-0x00/0x01");
622 EXPECT_EQ(GetBlockName(0x7F, 0x01), "Opt-0x7F/0x01");
623}
624
626TEST(DcpTest, IpBlockInfoGetNameDefinedValues)
627{
628 EXPECT_EQ(GetIPBlockInfoName(DcpIpBlockInfo::NotSet), "IP not set");
629 EXPECT_EQ(GetIPBlockInfoName(DcpIpBlockInfo::Set), "IP set");
630 EXPECT_EQ(GetIPBlockInfoName(DcpIpBlockInfo::SetByDhcp), "IP set by DHCP");
631 EXPECT_EQ(GetIPBlockInfoName(DcpIpBlockInfo::NotSetConflict), "IP not set (address conflict detected)");
632 EXPECT_EQ(GetIPBlockInfoName(DcpIpBlockInfo::SetConflict), "IP set (address conflict detected)");
633 EXPECT_EQ(GetIPBlockInfoName(DcpIpBlockInfo::SetByDhcpConflict), "IP set by DHCP (address conflict detected)");
634}
635
637TEST(DcpTest, IpBlockInfoGetNameUnknown)
638{
639 EXPECT_EQ(GetIPBlockInfoName(static_cast<DcpIpBlockInfo>(0x1234)), "Unknown (0x1234)");
640}
641
643TEST(DcpTest, IpBlockInfoHasConflict)
644{
645 EXPECT_FALSE(HasConflict(DcpIpBlockInfo::NotSet));
646 EXPECT_FALSE(HasConflict(DcpIpBlockInfo::Set));
650}
651
653TEST(DcpTest, IpBlockInfoIsDhcp)
654{
655 EXPECT_FALSE(IsDhcp(DcpIpBlockInfo::NotSet));
656 EXPECT_FALSE(IsDhcp(DcpIpBlockInfo::Set));
657 EXPECT_TRUE(IsDhcp(DcpIpBlockInfo::SetByDhcp));
659 EXPECT_FALSE(IsDhcp(DcpIpBlockInfo::SetConflict));
661}
662
664TEST(DcpTest, BlockQualifierGetNameTemporary)
665{
667}
668
670TEST(DcpTest, BlockQualifierGetNamePermanent)
671{
673}
674
676TEST(DcpTest, BlockQualifierGetNameUnknown)
677{
678 EXPECT_EQ(GetDcpBlockQualifierDescription(static_cast<DcpBlockQualifier>(0x1234)), "Unknown (0x1234)");
679}
680
682TEST(DcpTest, ResetQualifierApplicationData)
683{
684 EXPECT_EQ(GetResetQualifierName(0x0002), "Reset application data");
685 EXPECT_EQ(GetResetQualifierName(0x0003), "Reset application data");
686}
687
689TEST(DcpTest, ResetQualifierCommunicationParameter)
690{
691 EXPECT_EQ(GetResetQualifierName(0x0004), "Reset communication parameter");
692 EXPECT_EQ(GetResetQualifierName(0x0005), "Reset communication parameter");
693}
694
696TEST(DcpTest, ResetQualifierEngineeringParameter)
697{
698 EXPECT_EQ(GetResetQualifierName(0x0006), "Reset engineering parameter");
699 EXPECT_EQ(GetResetQualifierName(0x0007), "Reset engineering parameter");
700}
701
703TEST(DcpTest, ResetQualifierAllStoredData)
704{
705 EXPECT_EQ(GetResetQualifierName(0x0008), "Reset all stored data");
706 EXPECT_EQ(GetResetQualifierName(0x0009), "Reset all stored data");
707}
708
710TEST(DcpTest, ResetQualifierEngineeringParameterSecondRange)
711{
712 EXPECT_EQ(GetResetQualifierName(0x000A), "Reset engineering parameter");
713 EXPECT_EQ(GetResetQualifierName(0x000B), "Reset engineering parameter");
714}
715
717TEST(DcpTest, ResetQualifierFactory)
718{
719 EXPECT_EQ(GetResetQualifierName(0x0010), "Reset to factory values");
720 EXPECT_EQ(GetResetQualifierName(0x0011), "Reset to factory values");
721}
722
724TEST(DcpTest, ResetQualifierRestoreData)
725{
726 EXPECT_EQ(GetResetQualifierName(0x0012), "Reset and restore data");
727 EXPECT_EQ(GetResetQualifierName(0x0013), "Reset and restore data");
728}
729
731TEST(DcpTest, ResetQualifierUnknown)
732{
733 EXPECT_EQ(GetResetQualifierName(0x1234), "Unknown (0x1234)");
734}
735
737TEST(DcpTest, DeviceInitiativeNoHello)
738{
739 EXPECT_EQ(GetDeviceInitiativeName(DcpDeviceInitiative::NoHello), "Device does not issue DCP-Hello after power on");
740}
741
743TEST(DcpTest, DeviceInitiativeIssueHello)
744{
745 EXPECT_EQ(GetDeviceInitiativeName(DcpDeviceInitiative::IssueHello), "Device issues DCP-Hello after power on");
746}
747
749TEST(DcpTest, DeviceInitiativeUnknown)
750{
751 EXPECT_EQ(GetDeviceInitiativeName(static_cast<DcpDeviceInitiative>(0x1234)), "Unknown (0x1234)");
752}
753
755TEST(DcpTest, DcpResponseCodeGetNameDefinedValues)
756{
757 EXPECT_EQ(GetDcpResponseCodeName(DcpResponseCode::NoError), "No error");
758 EXPECT_EQ(GetDcpResponseCodeName(DcpResponseCode::OptionNotSupported), "Option not supported");
759 EXPECT_EQ(GetDcpResponseCodeName(DcpResponseCode::SuboptionNotSupported), "Suboption not supported or no DataSet available");
760 EXPECT_EQ(GetDcpResponseCodeName(DcpResponseCode::SuboptionNotSet), "Suboption not set");
761 EXPECT_EQ(GetDcpResponseCodeName(DcpResponseCode::ResourceError), "Resource error");
762 EXPECT_EQ(GetDcpResponseCodeName(DcpResponseCode::SetNotPossible), "Set not possible");
763 EXPECT_EQ(GetDcpResponseCodeName(DcpResponseCode::InOperationSetNotPossible), "In operation, SET not possible");
764}
765
767TEST(DcpTest, DcpResponseCodeGetNameUnknown)
768{
769 EXPECT_EQ(GetDcpResponseCodeName(static_cast<DcpResponseCode>(0xFF)), "Unknown (0xFF)");
770}
771
773TEST(DcpTest, GetDcpBlockErrorNameDefinedValues)
774{
776 EXPECT_EQ(GetDcpBlockErrorDescription(DcpBlockError::OptionUnsupported), "Option not supported");
777 EXPECT_EQ(GetDcpBlockErrorDescription(DcpBlockError::SuboptionUnsupported), "Suboption not supported or no dataset available");
778 EXPECT_EQ(GetDcpBlockErrorDescription(DcpBlockError::SuboptionNotSet), "Suboption not set");
779 EXPECT_EQ(GetDcpBlockErrorDescription(DcpBlockError::Resource), "Resource error");
780 EXPECT_EQ(GetDcpBlockErrorDescription(DcpBlockError::SetNotPossible), "SET not possible by local reasons");
781 EXPECT_EQ(GetDcpBlockErrorDescription(DcpBlockError::InOperation), "In operation, SET not possible");
782}
783
785TEST(DcpTest, GetDcpBlockErrorNameUnknown)
786{
787 EXPECT_EQ(GetDcpBlockErrorDescription(static_cast<DcpBlockError>(0xFF)), "Unknown error (0xFF)");
788}
789
791TEST(DcpTest, DcpDhcpBlockParseHostname)
792{
793 const std::vector<std::uint8_t> data = {'p', 'l', 'c', '-', '1'};
794 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::Hostname, data);
795 EXPECT_EQ(block.suboption, DcpDhcpSuboption::Hostname);
796 EXPECT_EQ(block.suboptionName, "Hostname");
797 EXPECT_EQ(block.rawData, data);
798 ASSERT_TRUE(block.hostname.has_value());
799 EXPECT_EQ(*block.hostname, "plc-1");
800 EXPECT_FALSE(block.clientId.has_value());
801 EXPECT_FALSE(block.vendorSpecific.has_value());
802 EXPECT_FALSE(block.fqdn.has_value());
803 EXPECT_FALSE(block.uuid.has_value());
804}
805
807TEST(DcpTest, DcpDhcpBlockParseClientId)
808{
809 const std::vector<std::uint8_t> data = {0x01, 0x02, 0x03, 0x04};
810 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::ClientId, data);
811 EXPECT_EQ(block.suboptionName, "ClientId");
812 EXPECT_EQ(block.rawData, data);
813 ASSERT_TRUE(block.clientId.has_value());
814 EXPECT_EQ(*block.clientId, data);
815 EXPECT_FALSE(block.hostname.has_value());
816 EXPECT_FALSE(block.vendorSpecific.has_value());
817 EXPECT_FALSE(block.fqdn.has_value());
818 EXPECT_FALSE(block.uuid.has_value());
819}
820
822TEST(DcpTest, DcpDhcpBlockParseVendorSpecific)
823{
824 const std::vector<std::uint8_t> data = {0xAA, 0xBB, 0xCC, 0xDD};
825 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::VendorSpec, data);
826 EXPECT_EQ(block.suboptionName, "VendorSpec");
827 EXPECT_EQ(block.rawData, data);
828 ASSERT_TRUE(block.vendorSpecific.has_value());
829 EXPECT_EQ(*block.vendorSpecific, data);
830 EXPECT_FALSE(block.hostname.has_value());
831 EXPECT_FALSE(block.clientId.has_value());
832 EXPECT_FALSE(block.fqdn.has_value());
833 EXPECT_FALSE(block.uuid.has_value());
834}
835
837TEST(DcpTest, DcpDhcpBlockParseFqdn)
838{
839 const std::vector<std::uint8_t> data = {'p', 'l', 'c', '.', 'e', 'x', 'a', 'm', 'p', 'l', 'e'};
840 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::Fqdn, data);
841 EXPECT_EQ(block.suboptionName, "Fqdn");
842 EXPECT_EQ(block.rawData, data);
843 ASSERT_TRUE(block.fqdn.has_value());
844 EXPECT_EQ(*block.fqdn, "plc.example");
845 EXPECT_FALSE(block.hostname.has_value());
846 EXPECT_FALSE(block.clientId.has_value());
847 EXPECT_FALSE(block.vendorSpecific.has_value());
848 EXPECT_FALSE(block.uuid.has_value());
849}
850
852TEST(DcpTest, DcpDhcpBlockParseUuid)
853{
854 const std::vector<std::uint8_t> data = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
855 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::Uuid, data);
856 EXPECT_EQ(block.suboptionName, "Uuid");
857 EXPECT_EQ(block.rawData, data);
858 ASSERT_TRUE(block.uuid.has_value());
859 EXPECT_EQ(*block.uuid, "12345678-9abc-def0-1122-334455667788");
860}
861
863TEST(DcpTest, DcpDhcpBlockParseUuidTooShort)
864{
865 const std::vector<std::uint8_t> data = {0x12, 0x34, 0x56, 0x78};
866 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::Uuid, data);
867 EXPECT_EQ(block.suboptionName, "Uuid");
868 EXPECT_EQ(block.rawData, data);
869 EXPECT_FALSE(block.uuid.has_value());
870}
871
873TEST(DcpTest, DcpDhcpBlockParseUuidMinimumLength)
874{
875 const std::vector<std::uint8_t> data(16, 0xAB);
876 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::Uuid, data);
877 ASSERT_TRUE(block.uuid.has_value());
878 EXPECT_EQ(*block.uuid, "abababab-abab-abab-abab-abababababab");
879}
880
882TEST(DcpTest, DcpDhcpBlockParseUnknownSuboption)
883{
884 const std::vector<std::uint8_t> data = {0x10, 0x20, 0x30};
885 const auto block = DCPDHCPBlock::Parse(static_cast<DcpDhcpSuboption>(0x99), data);
886 EXPECT_EQ(block.suboption, static_cast<DcpDhcpSuboption>(0x99));
887 EXPECT_EQ(block.suboptionName, "0x99");
888 EXPECT_EQ(block.rawData, data);
889 EXPECT_FALSE(block.hostname.has_value());
890 EXPECT_FALSE(block.clientId.has_value());
891 EXPECT_FALSE(block.vendorSpecific.has_value());
892 EXPECT_FALSE(block.fqdn.has_value());
893 EXPECT_FALSE(block.uuid.has_value());
894}
895
897TEST(DcpTest, DcpDhcpBlockParseUnknownSuboptionEmptyPayload)
898{
899 const std::vector<std::uint8_t> data;
900 const auto block = DCPDHCPBlock::Parse(static_cast<DcpDhcpSuboption>(0x99), data);
901 EXPECT_EQ(block.suboptionName, "0x99");
902 EXPECT_TRUE(block.rawData.empty());
903 EXPECT_FALSE(block.hostname.has_value());
904 EXPECT_FALSE(block.clientId.has_value());
905 EXPECT_FALSE(block.vendorSpecific.has_value());
906 EXPECT_FALSE(block.fqdn.has_value());
907 EXPECT_FALSE(block.uuid.has_value());
908}
909
911TEST(DcpTest, DcpDhcpBlockKnownSuboptionNames)
912{
913 EXPECT_EQ(DCPDHCPBlock::Parse(static_cast<DcpDhcpSuboption>(0x36), {}).suboptionName, "ServerId");
914 EXPECT_EQ(DCPDHCPBlock::Parse(static_cast<DcpDhcpSuboption>(0x37), {}).suboptionName, "ParamReq");
915 EXPECT_EQ(DCPDHCPBlock::Parse(static_cast<DcpDhcpSuboption>(0x3C), {}).suboptionName, "ClassId");
916 EXPECT_EQ(DCPDHCPBlock::Parse(static_cast<DcpDhcpSuboption>(0xFF), {}).suboptionName, "Control");
917}
919TEST(DcpTest, ParseSetResponseRejectsTooShortFrame)
920{
921 EXPECT_THROW(ParseSetResponse(Bytes(13, 0)), DCPError);
922}
923
925TEST(DcpTest, ParseSetResponseRejectsMalformedEthernetHeader)
926{
927 Bytes data(14, 0);
928 EXPECT_THROW(ParseSetResponse(data), DCPError);
929}
930
932TEST(DcpTest, ParseSetResponseRejectsShortVlanFrame)
933{
934 Bytes data;
935 data.reserve(18);
936 data.insert(data.end(), 11U, 0U);
938
939 EXPECT_THROW(ParseSetResponse(data), DCPError);
940}
941
943TEST(DcpTest, ParseSetResponseAcceptsVlanFrame)
944{
945 EXPECT_EQ(ParseSetResponse(BuildVlanDcpResponse(DcpServiceType::ResponseSuccess, {})), DcpBlockError::Ok);
946}
947
949TEST(DcpTest, ParseSetResponseRejectsUnexpectedVlanInnerEtherType)
950{
951 Bytes data;
952 data.reserve(18);
953 data.insert(data.end(), 11U, 0U);
955 data.insert(data.end(), 2U, 0U);
956 data[16] = 0x08;
957 data[17] = 0x00;
958
959 EXPECT_THROW(ParseSetResponse(data), DCPError);
960}
961
963TEST(DcpTest, ParseSetResponseRejectsUnexpectedEtherType)
964{
965 Bytes data(14, 0);
966 data[12] = 0x08;
967 data[13] = 0x00;
968
969 EXPECT_THROW(ParseSetResponse(data), DCPError);
970}
971
973TEST(DcpTest, ParseSetResponseRejectsMalformedDcpHeader)
974{
975 Bytes data;
976 data.reserve(18);
977 data.insert(data.end(), 11U, 0U);
979
980 EXPECT_THROW(ParseSetResponse(data), DCPError);
981}
982
984TEST(DcpTest, ParseSetResponseRejectsUnsupportedService)
985{
986 const auto payload = BuildDcpResponse(DcpServiceType::ResponseUnsupported, {});
987 EXPECT_THROW(ParseSetResponse(payload), DCPError);
988}
989
991TEST(DcpTest, ParseSetResponseRejectsUnexpectedService)
992{
993 const auto payload = BuildDcpResponse(static_cast<DcpServiceType>(0x42), {});
994 EXPECT_THROW(ParseSetResponse(payload), DCPError);
995}
996
998TEST(DcpTest, ParseSetResponseWithoutResponseBlockReturnsSuccess)
999{
1000 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1001 EXPECT_EQ(ParseSetResponse(data), DcpBlockError::Ok);
1002}
1003
1005TEST(DcpTest, ParseSetResponseExtractsThreeByteBlockError)
1006{
1007 const auto block = Bytes{
1008 static_cast<std::uint8_t>(DcpOption::Control),
1009 static_cast<std::uint8_t>(DcpControlSuboption::Response),
1010 0x00,
1011 0x03,
1012 0x00,
1013 0x00,
1014 static_cast<std::uint8_t>(DcpBlockError::Resource)};
1015 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, block);
1016
1017 EXPECT_EQ(ParseSetResponse(data), DcpBlockError::Resource);
1018}
1019
1021TEST(DcpTest, ParseSetResponseExtractsShortBlockError)
1022{
1023 const auto block = Bytes{
1024 static_cast<std::uint8_t>(DcpOption::Control),
1025 static_cast<std::uint8_t>(DcpControlSuboption::Response),
1026 0x00,
1027 0x01,
1028 static_cast<std::uint8_t>(DcpBlockError::SetNotPossible)};
1029 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, block);
1030
1032}
1033
1035TEST(DcpTest, ParseSetResponseEmptyResponseBlockReturnsSuccess)
1036{
1037 const auto block = Bytes{static_cast<std::uint8_t>(DcpOption::Control), static_cast<std::uint8_t>(DcpControlSuboption::Response), 0x00, 0x00};
1038 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, block);
1039
1040 EXPECT_EQ(ParseSetResponse(data), DcpBlockError::Ok);
1041}
1042
1044TEST(DcpTest, ParseSetResponseSkipsNonResponseBlocks)
1045{
1046 const auto block = Bytes{
1047 static_cast<std::uint8_t>(DcpOption::Device),
1048 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
1049 0x00,
1050 0x03,
1051 'a',
1052 'b',
1053 'c',
1054 0x00,
1055 static_cast<std::uint8_t>(DcpOption::Control),
1056 static_cast<std::uint8_t>(DcpControlSuboption::Response),
1057 0x00,
1058 0x03,
1059 0x00,
1060 0x00,
1061 static_cast<std::uint8_t>(DcpBlockError::InOperation)};
1062 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, block);
1063
1065}
1066
1068TEST(DcpTest, ParseSetResponseUsesFirstResponseBlock)
1069{
1070 const auto block = Bytes{
1071 static_cast<std::uint8_t>(DcpOption::Control),
1072 static_cast<std::uint8_t>(DcpControlSuboption::Response),
1073 0x00,
1074 0x03,
1075 0x00,
1076 0x00,
1077 static_cast<std::uint8_t>(DcpBlockError::Resource),
1078 static_cast<std::uint8_t>(DcpOption::Control),
1079 static_cast<std::uint8_t>(DcpControlSuboption::Response),
1080 0x00,
1081 0x03,
1082 0x00,
1083 0x00,
1084 static_cast<std::uint8_t>(DcpBlockError::Ok)};
1085 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, block);
1086
1087 EXPECT_EQ(ParseSetResponse(data), DcpBlockError::Resource);
1088}
1089
1091TEST(DcpTest, RecvSetResponseReturnsSuccessfulResponse)
1092{
1093 StrictMock<MockRawEthernetSocket> sock;
1094 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1095 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1096
1097 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1098 EXPECT_CALL(sock, Recv()).WillOnce(Return(data));
1099
1100 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1101}
1102
1104TEST(DcpTest, RecvSetResponseIgnoresEmptyFrame)
1105{
1106 StrictMock<MockRawEthernetSocket> sock;
1107 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1108 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1109
1110 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1111 EXPECT_CALL(sock, Recv())
1112 .WillOnce(Return(Bytes{}))
1113 .WillOnce(Return(data));
1114
1115 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1116}
1117
1119TEST(DcpTest, RecvSetResponseIgnoresShortFrame)
1120{
1121 StrictMock<MockRawEthernetSocket> sock;
1122 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1123 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1124
1125 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1126 EXPECT_CALL(sock, Recv())
1127 .WillOnce(Return(Bytes(13, 0)))
1128 .WillOnce(Return(data));
1129
1130 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1131}
1132
1134TEST(DcpTest, RecvSetResponseIgnoresMalformedEthernetFrame)
1135{
1136 StrictMock<MockRawEthernetSocket> sock;
1137 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1138 Bytes malformed(14, 0);
1139 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1140
1141 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1142 EXPECT_CALL(sock, Recv())
1143 .WillOnce(Return(malformed))
1144 .WillOnce(Return(data));
1145
1146 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1147}
1148
1150TEST(DcpTest, RecvSetResponseIgnoresWrongDestinationMac)
1151{
1152 StrictMock<MockRawEthernetSocket> sock;
1153 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1154 auto unrelated = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1155 unrelated[0] ^= 0x01;
1156 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1157
1158 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1159 EXPECT_CALL(sock, Recv())
1160 .WillOnce(Return(unrelated))
1161 .WillOnce(Return(data));
1162
1163 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1164}
1165
1167TEST(DcpTest, RecvSetResponseIgnoresShortVlanPayload)
1168{
1169 StrictMock<MockRawEthernetSocket> sock;
1170 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1171 Bytes vlan;
1172 vlan.reserve(14U);
1173
1174 wire::PutFixed(vlan, srcMac);
1175 vlan.insert(vlan.end(), 6U, 0U);
1177 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1178
1179 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1180 EXPECT_CALL(sock, Recv())
1181 .WillOnce(Return(vlan))
1182 .WillOnce(Return(data));
1183
1184 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1185}
1186
1188TEST(DcpTest, RecvSetResponseIgnoresWrongVlanInnerEtherType)
1189{
1190 StrictMock<MockRawEthernetSocket> sock;
1191 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1192 auto vlan = BuildVlanDcpResponse(DcpServiceType::ResponseSuccess, {});
1193 vlan[16] = 0x08;
1194 vlan[17] = 0x00;
1195 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1196
1197 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1198 EXPECT_CALL(sock, Recv())
1199 .WillOnce(Return(vlan))
1200 .WillOnce(Return(data));
1201
1202 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1203}
1204
1206TEST(DcpTest, RecvSetResponseIgnoresWrongEtherType)
1207{
1208 StrictMock<MockRawEthernetSocket> sock;
1209 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1210 auto unrelated = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1211 unrelated[12] = 0x08;
1212 unrelated[13] = 0x00;
1213 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1214
1215 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1216 EXPECT_CALL(sock, Recv())
1217 .WillOnce(Return(unrelated))
1218 .WillOnce(Return(data));
1219
1220 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1221}
1222
1224TEST(DcpTest, RecvSetResponseAcceptsVlanResponse)
1225{
1226 StrictMock<MockRawEthernetSocket> sock;
1227 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1228 auto data = BuildVlanDcpResponse(DcpServiceType::ResponseSuccess, {});
1229
1230 std::ranges::copy(srcMac, data.begin());
1231
1232 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1233 EXPECT_CALL(sock, Recv()).WillOnce(Return(data));
1234
1235 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1236}
1237
1239TEST(DcpTest, RecvSetResponseIgnoresUnexpectedServiceType)
1240{
1241 StrictMock<MockRawEthernetSocket> sock;
1242 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1243 const auto unrelated = BuildDcpResponse(static_cast<DcpServiceType>(0x42), {});
1244 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, {});
1245
1246 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1247 EXPECT_CALL(sock, Recv())
1248 .WillOnce(Return(unrelated))
1249 .WillOnce(Return(data));
1250
1251 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::Ok);
1252}
1253
1255TEST(DcpTest, RecvSetResponsePropagatesDcpError)
1256{
1257 StrictMock<MockRawEthernetSocket> sock;
1258 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1259 const auto data = BuildDcpResponse(DcpServiceType::ResponseUnsupported, {});
1260
1261 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1262 EXPECT_CALL(sock, Recv()).WillOnce(Return(data));
1263
1264 EXPECT_THROW(RecvSetResponse(sock, srcMac, 1), DCPError);
1265}
1266
1268TEST(DcpTest, RecvSetResponseReturnsBlockError)
1269{
1270 StrictMock<MockRawEthernetSocket> sock;
1271 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1272 const auto block = Bytes{
1273 static_cast<std::uint8_t>(DcpOption::Control),
1274 static_cast<std::uint8_t>(DcpControlSuboption::Response),
1275 0x00,
1276 0x03,
1277 0x00,
1278 0x00,
1279 static_cast<std::uint8_t>(DcpBlockError::InOperation),
1280 0x00};
1281 const auto data = BuildDcpResponse(DcpServiceType::ResponseSuccess, block);
1282
1283 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1284 EXPECT_CALL(sock, Recv()).WillOnce(Return(data));
1285
1286 EXPECT_EQ(RecvSetResponse(sock, srcMac, 1), DcpBlockError::InOperation);
1287}
1288
1290TEST(DcpTest, RecvSetResponseTimesOut)
1291{
1292 StrictMock<MockRawEthernetSocket> sock;
1293 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1294
1295 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1296 EXPECT_CALL(sock, Recv()).WillRepeatedly(Return(Bytes{}));
1297
1298 EXPECT_THROW(RecvSetResponse(sock, srcMac, 0), DCPTimeoutError);
1299}
1300
1302TEST(DcpTest, DeviceRoleNameDefinedValues)
1303{
1304 EXPECT_EQ(DeviceRoleName(DcpDeviceRole::IoDevice), "IoDevice");
1305 EXPECT_EQ(DeviceRoleName(DcpDeviceRole::IoController), "IoController");
1306 EXPECT_EQ(DeviceRoleName(DcpDeviceRole::IoMultidevice), "IoMultidevice");
1307 EXPECT_EQ(DeviceRoleName(DcpDeviceRole::PnSupervisor), "PnSupervisor");
1308}
1309
1311TEST(DcpTest, DeviceRoleNameUnknown)
1312{
1313 EXPECT_EQ(DeviceRoleName(static_cast<DcpDeviceRole>(0x80)), "Unknown");
1314}
1315
1317TEST(DcpTest, GetBlockNameUnknownSuboptionForAllTypedOptionGroups)
1318{
1319 EXPECT_EQ(GetBlockName(DcpOption::Ip, 0xFE), "Ip/0xFE");
1320 EXPECT_EQ(GetBlockName(DcpOption::Device, 0xFE), "Device/0xFE");
1321 EXPECT_EQ(GetBlockName(DcpOption::Dhcp, 0xFE), "Dhcp/0xFE");
1322 EXPECT_EQ(GetBlockName(DcpOption::Control, 0xFE), "Control/0xFE");
1323 EXPECT_EQ(GetBlockName(DcpOption::DeviceInitiative, 0xFE), "DeviceInitiative/0xFE");
1324}
1325
1327TEST(DcpTest, GetBlockNameOptionsWithoutSuboptions)
1328{
1329 EXPECT_EQ(GetBlockName(DcpOption::Unknown, 0x01), "Opt-0x00/0x01");
1330 EXPECT_EQ(GetBlockName(DcpOption::Reserved, 0x01), "Reserved/0x01");
1331 EXPECT_EQ(GetBlockName(DcpOption::Nme, 0x01), "Nme/0x01");
1332 EXPECT_EQ(GetBlockName(DcpOption::All, 0x01), "All/0x01");
1333}
1334
1336TEST(DcpTest, GetBlockNameUint16OptionOverload)
1337{
1338 EXPECT_EQ(GetBlockName(static_cast<std::uint16_t>(0x80), 0x01), "Vendor-0x80/0x01");
1339}
1340
1342TEST(DcpTest, GetBlockNameDeviceInitiativeSuboption)
1343{
1344 EXPECT_EQ(GetBlockName(DcpOption::DeviceInitiative, DcpDeviceInitiativeSuboption::Initiative), "DeviceInitiative/Initiative");
1345}
1346
1348TEST(DcpTest, MakeBlockKeyRawOption)
1349{
1350 const auto key = MakeBlockKey(static_cast<std::uint16_t>(DcpOption::Device), static_cast<std::uint8_t>(DcpDeviceSuboption::Name));
1351
1352 EXPECT_EQ(key.option, DcpOption::Device);
1353 EXPECT_EQ(key.suboption, static_cast<std::uint8_t>(DcpDeviceSuboption::Name));
1354}
1355
1357TEST(DcpTest, MakeBlockKeyGenericSuboption)
1358{
1359 const auto key = MakeBlockKey(DcpOption::Device, static_cast<std::uint8_t>(DcpDeviceSuboption::Name));
1360
1361 EXPECT_EQ(key.option, DcpOption::Device);
1362 EXPECT_EQ(key.suboption, 0x02);
1363}
1364
1366TEST(DcpTest, MakeBlockKeyIpSuboption)
1367{
1369
1370 EXPECT_EQ(key.option, DcpOption::Ip);
1371 EXPECT_EQ(key.suboption, 0x01);
1372}
1373
1375TEST(DcpTest, MakeBlockKeyDeviceSuboption)
1376{
1378
1379 EXPECT_EQ(key.option, DcpOption::Device);
1380 EXPECT_EQ(key.suboption, 0x04);
1381}
1382
1384TEST(DcpTest, MakeBlockKeyControlSuboption)
1385{
1387
1388 EXPECT_EQ(key.option, DcpOption::Control);
1389 EXPECT_EQ(key.suboption, 0x03);
1390}
1391
1393TEST(DcpTest, MakeBlockKeyDhcpSuboption)
1394{
1396
1397 EXPECT_EQ(key.option, DcpOption::Dhcp);
1398 EXPECT_EQ(key.suboption, 0x0C);
1399}
1400
1402TEST(DcpTest, MakeBlockKeyDeviceInitiativeSuboption)
1403{
1405
1406 EXPECT_EQ(key.option, DcpOption::DeviceInitiative);
1407 EXPECT_EQ(key.suboption, 0x01);
1408}
1409
1411TEST(DcpTest, DeviceRoleBitwiseOr)
1412{
1413 const auto role = DcpDeviceRole::IoDevice |
1415
1416 EXPECT_EQ(static_cast<std::uint8_t>(role), 0x03);
1417}
1418
1420TEST(DcpTest, DeviceRoleBitwiseAndPresent)
1421{
1422 const auto role = DcpDeviceRole::IoDevice |
1424
1425 EXPECT_EQ(static_cast<std::uint8_t>(role & DcpDeviceRole::IoDevice), 0x01);
1426}
1427
1429TEST(DcpTest, DeviceRoleBitwiseAndAbsent)
1430{
1431 const auto role = DcpDeviceRole::IoDevice |
1433
1434 EXPECT_EQ(static_cast<std::uint8_t>(role & DcpDeviceRole::PnSupervisor), 0x00);
1435}
1436
1438TEST(DcpTest, ResetModeBitwiseOr)
1439{
1440 const auto mode = DcpResetMode::Communication |
1442
1443 EXPECT_EQ(static_cast<std::uint16_t>(mode), 0x0006);
1444}
1445
1447TEST(DcpTest, ResetModeBitwiseAndPresent)
1448{
1450
1451 EXPECT_EQ(static_cast<std::uint16_t>(mode & DcpResetMode::Communication), 0x0002);
1452}
1453
1455TEST(DcpTest, ResetModeBitwiseAndAbsent)
1456{
1458
1459 EXPECT_EQ(static_cast<std::uint16_t>(mode & DcpResetMode::Factory), 0x0000);
1460}
1461
1463TEST(DcpTest, IpBlockInfoBitHelpersHandleUnknownCombinations)
1464{
1465 const auto both = static_cast<DcpIpBlockInfo>(0x0082);
1466
1467 EXPECT_TRUE(HasConflict(both));
1468 EXPECT_TRUE(IsDhcp(both));
1469
1470 const auto conflictOnly = static_cast<DcpIpBlockInfo>(0x0080);
1471
1472 EXPECT_TRUE(HasConflict(conflictOnly));
1473 EXPECT_FALSE(IsDhcp(conflictOnly));
1474
1475 const auto dhcpOnly = static_cast<DcpIpBlockInfo>(0x0002);
1476
1477 EXPECT_FALSE(HasConflict(dhcpOnly));
1478 EXPECT_TRUE(IsDhcp(dhcpOnly));
1479}
1480
1482TEST(DcpTest, ResetQualifierUnknownEnumValue)
1483{
1484 EXPECT_EQ(GetResetQualifierName(static_cast<DcpResetQualifier>(0x1234)), "Unknown (0x1234)");
1485}
1486
1488TEST(DcpTest, ResetQualifierZeroIsUnknown)
1489{
1490 EXPECT_EQ(GetResetQualifierName(0x0000), "Unknown (0x0000)");
1491}
1492
1494TEST(DcpTest, DcpDhcpBlockParseUnknownEnumValue)
1495{
1496 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::Unknown, {0x01, 0x02});
1497
1498 EXPECT_EQ(block.suboption, DcpDhcpSuboption::Unknown);
1499
1500 EXPECT_EQ(block.suboptionName, "0x00");
1501 EXPECT_EQ(block.rawData, (Bytes{0x01, 0x02}));
1502}
1503
1505TEST(DcpTest, DcpDhcpBlockParseEmptyHostname)
1506{
1507 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::Hostname, {});
1508
1509 EXPECT_EQ(block.suboptionName, "Hostname");
1510
1511 ASSERT_TRUE(block.hostname.has_value());
1512 EXPECT_TRUE(block.hostname->empty());
1513}
1514
1516TEST(DcpTest, DcpDhcpBlockParseEmptyFqdn)
1517{
1518 const auto block = DCPDHCPBlock::Parse(DcpDhcpSuboption::Fqdn, {});
1519
1520 EXPECT_EQ(block.suboptionName, "Fqdn");
1521
1522 ASSERT_TRUE(block.fqdn.has_value());
1523 EXPECT_TRUE(block.fqdn->empty());
1524}
1525
1527TEST(DCPDeviceDescriptionTest, ToStringIncludesAllPopulatedFields)
1528{
1529 DCPDeviceDescription device(String2Mac("00:11:22:33:44:55"), {});
1530 device.name = "AUMA-001";
1531 device.mac = "00:11:22:33:44:55";
1532 device.deviceType = "Actuator";
1533 device.ip = "192.168.1.10";
1534 device.netmask = "255.255.255.0";
1535 device.gateway = "192.168.1.1";
1536 device.ipBlockInfo = DcpIpBlockInfo::SetByDhcp;
1537 device.ipConflict = true;
1538 device.ipSetByDhcp = true;
1539 device.deviceRoles = {"IO-Device", "IO-Controller"};
1540 device.deviceInstance = {2, 3};
1541 device.aliasName = "Valve-01";
1542 device.deviceInitiative = DcpDeviceInitiative::IssueHello;
1544 device.dhcpBlocks = {
1545 DCPDHCPBlock::Parse(DcpDhcpSuboption::Hostname, {'a', 'u', 'm', 'a'}),
1546 DCPDHCPBlock::Parse(DcpDhcpSuboption::Fqdn, {'a', 'u', 'm', 'a', '.', 'c', 'o', 'm'}),
1547 DCPDHCPBlock::Parse(DcpDhcpSuboption::Uuid, {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}),
1548 DCPDHCPBlock::Parse(DcpDhcpSuboption::ClientId, {0x01, 0x02, 0x03})};
1549 device.rawBlocks[MakeBlockKey(0x80, 0x01)] = {0xAA, 0xBB};
1550
1551 EXPECT_EQ(device.ToString(),
1552 "PROFINET Device: AUMA-001\n"
1553 " MAC: 00:11:22:33:44:55\n"
1554 " Type: Actuator\n"
1555 " IP: 192.168.1.10\n"
1556 " Netmask: 255.255.255.0\n"
1557 " Gateway: 192.168.1.1\n"
1558 " IP Info: IP set by DHCP\n"
1559 " Warning: IP address conflict detected\n"
1560 " IP Source: DHCP\n"
1561 " Vendor: Unknown (0x0000) (0x0000)\n"
1562 " Device: 0x0000\n"
1563 " Role: IO-Device, IO-Controller\n"
1564 " Instance: 2.3\n"
1565 " Alias: Valve-01\n"
1566 " Initiative: Device issues DCP-Hello after power on\n"
1567 " Supports: Ip/Mac, Dhcp/Hostname\n"
1568 " DHCP:\n"
1569 " Hostname: auma\n"
1570 " FQDN: auma.com\n"
1571 " UUID: 12345678-9abc-def0-1122-334455667788\n"
1572 " ClientId: 010203\n"
1573 " Unknown (128,1): aabb");
1574}
1575
1577TEST(DCPDeviceDescriptionTest, ToStringOmitsUnsetOptionalFields)
1578{
1579 DCPDeviceDescription device(String2Mac("00:11:22:33:44:55"), {});
1580 device.name = "Device";
1581 device.mac = "00:11:22:33:44:55";
1582 device.ip = "0.0.0.0";
1583 device.netmask = "0.0.0.0";
1584 device.gateway = "0.0.0.0";
1585
1586 EXPECT_EQ(device.ToString(),
1587 "PROFINET Device: Device\n"
1588 " MAC: 00:11:22:33:44:55\n"
1589 " IP: 0.0.0.0\n"
1590 " Netmask: 0.0.0.0\n"
1591 " Gateway: 0.0.0.0\n"
1592 " Vendor: Unknown (0x0000) (0x0000)\n"
1593 " Device: 0x0000");
1594}
1595
1597TEST(DCPDeviceDescriptionTest, ToStringFormatsDhcpBlockVariants)
1598{
1599 DCPDeviceDescription device(String2Mac("00:11:22:33:44:55"), {});
1600 device.name = "Device";
1601 device.dhcpBlocks = {
1602 DCPDHCPBlock::Parse(DcpDhcpSuboption::Hostname, {'h', 'o', 's', 't'}),
1604 DCPDHCPBlock::Parse(DcpDhcpSuboption::Uuid, {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}),
1606
1607 EXPECT_EQ(device.ToString(),
1608 "PROFINET Device: Device\n"
1609 " MAC: 00:11:22:33:44:55\n"
1610 " IP: 0.0.0.0\n"
1611 " Netmask: 0.0.0.0\n"
1612 " Gateway: 0.0.0.0\n"
1613 " Vendor: Unknown (0x0000) (0x0000)\n"
1614 " Device: 0x0000\n"
1615 " DHCP:\n"
1616 " Hostname: host\n"
1617 " FQDN: dev\n"
1618 " UUID: 00112233-4455-6677-8899-aabbccddeeff\n"
1619 " VendorSpec: cafe");
1620}
1621
1623TEST(DCPDeviceDescriptionTest, ConstructorDecodesAllSupportedBlocks)
1624{
1625 const MacAddress mac{
1626 0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
1627
1628 std::map<BlockKey, Bytes> blocks;
1629
1631 {'\0', 'A', 'c', 't', 'u', 'a', 't', 'o', 'r'};
1632
1634 {'\0', 'V', 'a', 'l', 'v', 'e', '1'};
1635
1637 0x00, 0x82,
1638 192, 168, 1, 10,
1639 255, 255, 255, 0,
1640 192, 168, 1, 1};
1641
1643 0x00, 0x01,
1644 0x00, 0x02};
1645
1647 static_cast<std::uint8_t>(DcpDeviceRole::IoDevice |
1649
1651 0x02, 0x03};
1652
1654 'A', 'l', 'i', 'a', 's'};
1655
1657 0x01, 0x01,
1658 0x02, 0x02,
1659 0x03};
1660
1662 'p', 'l', 'c'};
1663
1665 0x00, 0x01};
1666
1667 blocks[MakeBlockKey(0x80, 0x01)] = {
1668 0xAA, 0xBB};
1669
1670 const DCPDeviceDescription device(mac, blocks);
1671
1672 EXPECT_EQ(device.mac, "00:11:22:33:44:55");
1673 EXPECT_EQ(device.deviceType, "Actuator");
1674 EXPECT_EQ(device.name, "Valve1");
1675
1676 EXPECT_EQ(device.ip, "192.168.1.10");
1677 EXPECT_EQ(device.netmask, "255.255.255.0");
1678 EXPECT_EQ(device.gateway, "192.168.1.1");
1680 EXPECT_TRUE(device.ipConflict);
1681 EXPECT_TRUE(device.ipSetByDhcp);
1682
1683 EXPECT_EQ(device.VendorId(), 0x0001);
1684 EXPECT_EQ(device.DeviceId(), 0x0002);
1685
1686 EXPECT_EQ(device.deviceRole, static_cast<std::uint8_t>(DcpDeviceRole::IoDevice |
1688
1689 ASSERT_EQ(device.deviceRoles.size(), 2U);
1690 EXPECT_EQ(device.deviceRoles[0], "IoDevice");
1691 EXPECT_EQ(device.deviceRoles[1], "IoController");
1692
1693 EXPECT_EQ(device.deviceInstance.first, 2);
1694 EXPECT_EQ(device.deviceInstance.second, 3);
1695
1696 EXPECT_EQ(device.aliasName, "Alias");
1697
1698 ASSERT_EQ(device.supportedOptions.size(), 2U);
1699
1700 EXPECT_EQ(device.supportedOptions[0].option, DcpOption::Ip);
1701 EXPECT_EQ(device.supportedOptions[0].suboption, 0x01);
1702
1703 EXPECT_EQ(device.supportedOptions[1].option, DcpOption::Device);
1704 EXPECT_EQ(device.supportedOptions[1].suboption, 0x02);
1705
1706 ASSERT_EQ(device.dhcpBlocks.size(), 1U);
1707 EXPECT_EQ(device.dhcpBlocks[0].suboption, DcpDhcpSuboption::Hostname);
1708
1710
1711 EXPECT_TRUE(device.issuesHello);
1712
1713 ASSERT_EQ(device.rawBlocks.size(), 1U);
1714 EXPECT_EQ(device.rawBlocks.begin()->first.option, static_cast<DcpOption>(0x80));
1715
1716 EXPECT_EQ(device.rawBlocks.begin()->first.suboption, 0x01);
1717}
1718
1720TEST(DCPDeviceDescriptionTest, ConstructorIgnoresIncompleteOptionalBlocks)
1721{
1722 std::map<BlockKey, Bytes> blocks;
1723
1725
1727
1729 192, 168, 1, 10};
1730
1732 0x01, 0x02};
1733
1735
1737 0x01};
1738
1740 0x01};
1741
1743 0x00};
1744
1745 const DCPDeviceDescription device(String2Mac("00:11:22:33:44:55"), blocks);
1746
1747 EXPECT_TRUE(device.deviceType.empty());
1748 EXPECT_TRUE(device.name.empty());
1749
1750 EXPECT_EQ(device.ip, "0.0.0.0");
1751 EXPECT_EQ(device.netmask, "0.0.0.0");
1752 EXPECT_EQ(device.gateway, "0.0.0.0");
1753
1754 EXPECT_EQ(device.VendorId(), 0x0000);
1755 EXPECT_EQ(device.DeviceId(), 0x0000);
1756
1757 EXPECT_TRUE(device.deviceRoles.empty());
1758
1759 EXPECT_EQ(device.deviceInstance.first, 0);
1760 EXPECT_EQ(device.deviceInstance.second, 0);
1761
1762 EXPECT_TRUE(device.supportedOptions.empty());
1763
1765
1766 EXPECT_FALSE(device.issuesHello);
1767}
1768
1770TEST(DCPDeviceDescriptionTest, ConstructorHandlesLegacyTwelveByteIpBlock)
1771{
1772 std::map<BlockKey, Bytes> blocks;
1773
1776 10, 1, 2, 3,
1777 255, 255, 255, 0,
1778 10, 1, 2, 1};
1779
1780 const DCPDeviceDescription device(String2Mac("00:11:22:33:44:55"), blocks);
1781
1782 EXPECT_EQ(device.ip, "10.1.2.3");
1783 EXPECT_EQ(device.netmask, "255.255.255.0");
1784 EXPECT_EQ(device.gateway, "10.1.2.1");
1785 EXPECT_EQ(device.ipBlockInfo, DcpIpBlockInfo::NotSet);
1786 EXPECT_FALSE(device.ipConflict);
1787 EXPECT_FALSE(device.ipSetByDhcp);
1788}
1789
1791TEST(DCPDeviceDescriptionTest, ConstructorUsesLegacyIpLayoutForThirteenByteBlock)
1792{
1793 std::map<BlockKey, Bytes> blocks;
1794
1797 10, 1, 2, 3,
1798 255, 255, 255, 0,
1799 10, 1, 2, 1,
1800 0xFF};
1801
1802 const DCPDeviceDescription device(String2Mac("00:11:22:33:44:55"), blocks);
1803
1804 EXPECT_EQ(device.ip, "10.1.2.3");
1805 EXPECT_EQ(device.netmask, "255.255.255.0");
1806 EXPECT_EQ(device.gateway, "10.1.2.1");
1807 EXPECT_EQ(device.ipBlockInfo, DcpIpBlockInfo::NotSet);
1808}
1809
1811TEST(DcpTest, BuildSetParamRequestBuildsTemporaryOddLengthName)
1812{
1813 const MacAddress dst = String2Mac("00:11:22:33:44:55");
1814
1815 const MacAddress src = String2Mac("66:77:88:99:AA:BB");
1816
1817 const auto frame = BuildSetParamRequest(dst, src, "name", "abc", false);
1818
1819 const auto eth = EthernetHeader::Parse(frame);
1820
1821 EXPECT_EQ(eth.dst, dst);
1822 EXPECT_EQ(eth.src, src);
1823 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
1824
1825 const auto dcp = PNDCPHeader::Parse(eth.payload);
1826
1827 EXPECT_EQ(dcp.frameId, DcpFrameId::GetSet);
1828 EXPECT_EQ(dcp.serviceId, DcpServiceId::Set);
1829 EXPECT_EQ(dcp.serviceType, DcpServiceType::Request);
1830
1831 ASSERT_EQ(dcp.payload.size(), 10U);
1832
1833 EXPECT_EQ(dcp.payload[0], static_cast<std::uint8_t>(DcpOption::Device));
1834
1835 EXPECT_EQ(dcp.payload[1], static_cast<std::uint8_t>(DcpDeviceSuboption::Name));
1836
1837 EXPECT_EQ(dcp.payload[2], 0x00);
1838 EXPECT_EQ(dcp.payload[3], 0x05);
1839
1840 EXPECT_EQ(dcp.payload[4], 0x00);
1841 EXPECT_EQ(dcp.payload[5], 0x00);
1842
1843 EXPECT_EQ(dcp.payload[6], 'a');
1844 EXPECT_EQ(dcp.payload[7], 'b');
1845 EXPECT_EQ(dcp.payload[8], 'c');
1846 EXPECT_EQ(dcp.payload[9], 0x00);
1847}
1848
1850TEST(DcpTest, BuildSetParamRequestBuildsPermanentEvenLengthName)
1851{
1852 const auto frame = BuildSetParamRequest(String2Mac("00:11:22:33:44:55"),
1853 String2Mac("66:77:88:99:AA:BB"),
1854 "name",
1855 "abcd",
1856 true);
1857
1858 const auto eth = EthernetHeader::Parse(frame);
1859 const auto dcp = PNDCPHeader::Parse(eth.payload);
1860
1861 EXPECT_EQ(dcp.payload[4], 0x00);
1862 EXPECT_EQ(dcp.payload[5], 0x01);
1863
1864 EXPECT_EQ(dcp.payload[6], 'a');
1865 EXPECT_EQ(dcp.payload[7], 'b');
1866 EXPECT_EQ(dcp.payload[8], 'c');
1867 EXPECT_EQ(dcp.payload[9], 'd');
1868
1869 EXPECT_EQ(dcp.length, 10);
1870}
1871
1873TEST(DcpTest, BuildSetParamRequestRejectsIpParameter)
1874{
1875 EXPECT_THROW(BuildSetParamRequest(String2Mac("00:11:22:33:44:55"),
1876 String2Mac("66:77:88:99:AA:BB"),
1877 "IP",
1878 "192.168.1.10",
1879 false),
1880 DCPError);
1881}
1882
1884TEST(DcpTest, BuildSetParamRequestRejectsUnknownParameter)
1885{
1886 EXPECT_THROW(BuildSetParamRequest(String2Mac("00:11:22:33:44:55"),
1887 String2Mac("66:77:88:99:AA:BB"),
1888 "unknown",
1889 "value",
1890 false),
1891 DCPError);
1892}
1893
1895TEST(DcpTest, BuildSetParamRequestRejectsOverlongStationName)
1896{
1897 const std::string value(DCP_MAX_NAME_LENGTH + 1U, 'A');
1898
1899 EXPECT_THROW(BuildSetParamRequest(String2Mac("00:11:22:33:44:55"),
1900 String2Mac("66:77:88:99:AA:BB"),
1901 "name",
1902 value,
1903 false),
1905}
1906
1908TEST(DcpTest, BuildSetParamRequestAcceptsMaximumStationName)
1909{
1910 const std::string value(DCP_MAX_NAME_LENGTH, 'A');
1911
1912 EXPECT_NO_THROW(BuildSetParamRequest(String2Mac("00:11:22:33:44:55"),
1913 String2Mac("66:77:88:99:AA:BB"),
1914 "name",
1915 value,
1916 false));
1917}
1918
1920TEST(DcpTest, BuildDcpSignalRequestBuildsCorrectWireFormat)
1921{
1922 const MacAddress dst = String2Mac("00:11:22:33:44:55");
1923
1924 const MacAddress src = String2Mac("66:77:88:99:AA:BB");
1925
1926 const auto frame = BuildDcpSignalRequest(dst, src);
1927
1928 const auto eth = EthernetHeader::Parse(frame);
1929
1930 EXPECT_EQ(eth.dst, dst);
1931 EXPECT_EQ(eth.src, src);
1932 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
1933
1934 const auto dcp = PNDCPHeader::Parse(eth.payload);
1935
1936 EXPECT_EQ(dcp.frameId, DcpFrameId::GetSet);
1937 EXPECT_EQ(dcp.serviceId, DcpServiceId::Set);
1938 EXPECT_EQ(dcp.serviceType, DcpServiceType::Request);
1939
1940 ASSERT_EQ(dcp.payload.size(), 8U);
1941
1942 EXPECT_EQ(dcp.payload[0], static_cast<std::uint8_t>(DcpOption::Control));
1943
1944 EXPECT_EQ(dcp.payload[1], static_cast<std::uint8_t>(DcpControlSuboption::Signal));
1945
1946 EXPECT_EQ(dcp.payload[2], 0x00);
1947 EXPECT_EQ(dcp.payload[3], 0x04);
1948
1949 EXPECT_EQ(dcp.payload[4], 0x00);
1950 EXPECT_EQ(dcp.payload[5], 0x00);
1951 EXPECT_EQ(dcp.payload[6], 0x01);
1952 EXPECT_EQ(dcp.payload[7], 0x00);
1953}
1954
1956TEST(DcpTest, ParamsContainsSupportedParameters)
1957{
1958 const auto& params = Params();
1959
1960 ASSERT_EQ(params.size(), 2U);
1961
1962 EXPECT_EQ(params.at("name").option, DcpOption::Device);
1963
1964 EXPECT_EQ(params.at("name").suboption, static_cast<std::uint8_t>(DcpDeviceSuboption::Name));
1965
1966 EXPECT_EQ(params.at("ip").option, DcpOption::Ip);
1967
1968 EXPECT_EQ(params.at("ip").suboption, static_cast<std::uint8_t>(DcpIpSuboption::IpParameter));
1969}
1971TEST(DcpTest, GetParamThrowsForUnknownParameter)
1972{
1973 const MacAddress src = String2Mac("02:00:00:00:00:01");
1974 const MacAddress target = String2Mac("02:00:00:00:00:02");
1975
1977
1978 EXPECT_THROW(GetParam(sock, src, Mac2String(target), "does-not-exist", 0), DCPError);
1979}
1980
1982TEST(DcpTest, GetParamReturnsMatchingParameter)
1983{
1984 const MacAddress src = String2Mac("02:00:00:00:00:01");
1985 const MacAddress target = String2Mac("02:00:00:00:00:02");
1986
1988
1989 const BlockKey key = Params().at("name");
1990 const Bytes expected{'a', 'u', 'm', 'a'};
1991
1992 Bytes sentFrame;
1993 EXPECT_CALL(sock, SetTimeout(testing::_));
1994
1995 EXPECT_CALL(sock, Send(testing::_))
1996 .WillOnce(testing::SaveArg<0>(&sentFrame));
1997
1998 const Bytes response = BuildDcpResponseFrame(target, src, key.option, key.suboption, expected);
1999
2000 EXPECT_CALL(sock, Recv()) // testing::_, testing::_
2001 .WillOnce([&sentFrame, &target, &src, &key, &expected]()
2002 {
2003 EXPECT_FALSE(sentFrame.empty());
2004 if (sentFrame.empty())
2005 {
2006 return Bytes{};
2007 }
2008 const EthernetHeader ethernet = EthernetHeader::Parse(sentFrame);
2009
2010 const PNDCPHeader request = PNDCPHeader::Parse(ethernet.payload);
2011
2012 return BuildDcpResponseFrame(
2013 target,
2014 src,
2015 key.option,
2016 key.suboption,
2017 expected,
2018 request.xId);
2019 });
2020
2021 const auto result = GetParam(sock, src, Mac2String(target), "name", 1);
2022
2023 ASSERT_TRUE(result.has_value());
2024 EXPECT_EQ(*result, expected);
2025}
2026
2028TEST(DcpTest, GetParamReturnsNulloptWhenParameterIsMissing)
2029{
2030 const MacAddress src = String2Mac("02:00:00:00:00:01");
2031 const MacAddress target = String2Mac("02:00:00:00:00:02");
2032
2034
2035 const Bytes response = BuildDcpResponseFrame(target, src,
2036 DcpOption::Device, static_cast<std::uint8_t>(DcpDeviceSuboption::Type),
2037 {'S', 'G'});
2038
2039 Bytes sentFrame;
2040
2041 EXPECT_CALL(sock, Send(testing::_))
2042 .WillOnce(testing::SaveArg<0>(&sentFrame));
2043
2044 EXPECT_CALL(sock, Recv())
2045 .WillOnce([&sentFrame, &target, &src]()
2046 {
2047 EXPECT_FALSE(sentFrame.empty());
2048 if (sentFrame.empty())
2049 {
2050 return Bytes{};
2051 }
2052 const EthernetHeader ethernet = EthernetHeader::Parse(sentFrame);
2053
2054 const PNDCPHeader request = PNDCPHeader::Parse(ethernet.payload);
2055
2056 return BuildDcpResponseFrame(
2057 target,
2058 src,
2060 static_cast<std::uint8_t>(DcpDeviceSuboption::Type),
2061 {'S', 'G'},
2062 request.xId);
2063 });
2064
2065 const auto result = GetParam(sock, src, Mac2String(target), "name", 1);
2066
2067 EXPECT_FALSE(result.has_value());
2068}
2069
2071TEST(DcpTest, GetParamReturnsNulloptOnTimeout)
2072{
2073 const MacAddress src = String2Mac("02:00:00:00:00:01");
2074 const MacAddress target = String2Mac("02:00:00:00:00:02");
2075
2077
2078 EXPECT_CALL(sock, Send(testing::_));
2079
2080 const auto result = GetParam(sock, src, Mac2String(target), "name", 0);
2081
2082 EXPECT_FALSE(result.has_value());
2083}
2084
2086TEST(DcpTest, SetParamReturnsTrueForSuccessfulResponse)
2087{
2088 const MacAddress src = String2Mac("02:00:00:00:00:01");
2089 const MacAddress target = String2Mac("02:00:00:00:00:02");
2090
2092
2093 const Bytes response = BuildSetResponseFrame(target, src, DcpBlockError::Ok);
2094
2095 EXPECT_CALL(sock, Send(testing::_));
2096
2097 EXPECT_CALL(sock, Recv()) // testing::_, testing::_
2098 .WillOnce(testing::Return(response));
2099
2100 const auto start = std::chrono::steady_clock::now();
2101
2102 const bool result = SetParam(sock, src, Mac2String(target), "name", "AUMA", 1, true, 100);
2103
2104 EXPECT_TRUE(result);
2105
2106 // SetParam intentionally waits 100 msseconds after a successful SET.
2107 const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start);
2108
2109 EXPECT_GE(elapsed.count(), 90);
2110}
2111
2113TEST(DcpTest, SetParamThrowsForDeviceBlockError)
2114{
2115 const MacAddress src = String2Mac("02:00:00:00:00:01");
2116 const MacAddress target = String2Mac("02:00:00:00:00:02");
2117
2119
2120 const Bytes response = BuildSetResponseFrame(target, src, DcpBlockError::SetNotPossible, true);
2121
2122 EXPECT_CALL(sock, SetTimeout(testing::_));
2123 EXPECT_CALL(sock, Send(testing::_));
2124
2125 EXPECT_CALL(sock, Recv()) // testing::_, testing::_
2126 .WillOnce(testing::Return(response));
2127
2128 EXPECT_THROW(SetParam(sock, src, Mac2String(target), "name", "AUMA", 1, true, 100), DCPError);
2129}
2130
2132TEST(DcpTest, SetParamReturnsFalseOnTimeout)
2133{
2134 const MacAddress src = String2Mac("02:00:00:00:00:01");
2135 const MacAddress target = String2Mac("02:00:00:00:00:02");
2136
2138
2139 EXPECT_CALL(sock, Send(testing::_));
2140
2141 const bool result = SetParam(sock, src, Mac2String(target), "name", "AUMA", 0, true, 100);
2142
2143 EXPECT_FALSE(result);
2144}
2145
2147TEST(DcpTest, SetParamBuildsTemporaryRequest)
2148{
2149 const MacAddress src = String2Mac("02:00:00:00:00:01");
2150 const MacAddress target = String2Mac("02:00:00:00:00:02");
2152 Bytes sentFrame;
2153 EXPECT_CALL(sock, Send(testing::_))
2154 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2155 testing::Return()));
2156 const bool result = SetParam(sock,
2157 src,
2158 Mac2String(target),
2159 "name",
2160 "AUMA",
2161 0,
2162 false,
2163 100);
2164
2165 EXPECT_FALSE(result);
2166 ASSERT_FALSE(sentFrame.empty());
2167 EXPECT_GE(sentFrame.size(), 14U);
2168}
2169
2171TEST(DcpTest, SetIpReturnsFalseOnTimeout)
2172{
2173 const MacAddress src = String2Mac("02:00:00:00:00:01");
2174 const MacAddress target = String2Mac("02:00:00:00:00:02");
2176 Bytes sentFrame;
2177 EXPECT_CALL(sock, Send(testing::_))
2178 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2179 testing::Return()));
2180 const bool result = SetIp(sock,
2181 src,
2182 Mac2String(target),
2183 "192.168.1.10",
2184 "255.255.255.0",
2185 "192.168.1.1",
2186 true,
2187 0,
2188 100);
2189 EXPECT_FALSE(result);
2190 ASSERT_FALSE(sentFrame.empty());
2191 EXPECT_GE(sentFrame.size(), 14U);
2192}
2193
2195TEST(DcpTest, SetIpRejectsInvalidIpAddress)
2196{
2197 const MacAddress src = String2Mac("02:00:00:00:00:01");
2198 const MacAddress target = String2Mac("02:00:00:00:00:02");
2199
2201
2202 EXPECT_CALL(sock, Send(testing::_))
2203 .Times(0);
2204
2205 EXPECT_THROW(SetIp(sock, src, Mac2String(target), "999.999.999.999", "255.255.255.0", "192.168.1.1", true, 0, 100), std::exception);
2206}
2207
2209TEST(DcpTest, SetIpEncodesPermanentQualifier)
2210{
2211 const MacAddress src = String2Mac("02:00:00:00:00:01");
2212 const MacAddress target = String2Mac("02:00:00:00:00:02");
2213
2215 Bytes sentFrame;
2216
2217 EXPECT_CALL(sock, Send(testing::_))
2218 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2219 testing::Return()));
2220
2221 EXPECT_FALSE(SetIp(sock,
2222 src,
2223 Mac2String(target),
2224 "192.168.1.10",
2225 "255.255.255.0",
2226 "192.168.1.1",
2227 true,
2228 0,
2229 100));
2230
2231 ASSERT_FALSE(sentFrame.empty());
2232
2233 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
2234
2235 EXPECT_EQ(eth.dst, target);
2236 EXPECT_EQ(eth.src, src);
2237 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
2238
2239 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
2240
2241 EXPECT_EQ(dcp.frameId, DcpFrameId::GetSet);
2242 EXPECT_EQ(dcp.serviceId, DcpServiceId::Set);
2243 EXPECT_EQ(dcp.serviceType, DcpServiceType::Request);
2244
2245 ASSERT_GE(dcp.payload.size(), 18U);
2246
2247 EXPECT_EQ(dcp.payload[0], static_cast<std::uint8_t>(DcpOption::Ip));
2248
2249 EXPECT_EQ(dcp.payload[1], static_cast<std::uint8_t>(DcpIpSuboption::IpParameter));
2250
2251 // Block length = 2-byte qualifier + 12-byte IP data.
2252 EXPECT_EQ(dcp.payload[2], 0x00);
2253 EXPECT_EQ(dcp.payload[3], 0x0E);
2254
2255 // Permanent qualifier = 0x0001.
2256 EXPECT_EQ(dcp.payload[4], 0x00);
2257 EXPECT_EQ(dcp.payload[5], 0x01);
2258
2259 // 192.168.1.10
2260 EXPECT_EQ(dcp.payload[6], 192);
2261 EXPECT_EQ(dcp.payload[7], 168);
2262 EXPECT_EQ(dcp.payload[8], 1);
2263 EXPECT_EQ(dcp.payload[9], 10);
2264
2265 // 255.255.255.0
2266 EXPECT_EQ(dcp.payload[10], 255);
2267 EXPECT_EQ(dcp.payload[11], 255);
2268 EXPECT_EQ(dcp.payload[12], 255);
2269 EXPECT_EQ(dcp.payload[13], 0);
2270
2271 // 192.168.1.1
2272 EXPECT_EQ(dcp.payload[14], 192);
2273 EXPECT_EQ(dcp.payload[15], 168);
2274 EXPECT_EQ(dcp.payload[16], 1);
2275 EXPECT_EQ(dcp.payload[17], 1);
2276}
2277
2279TEST(DcpTest, SetIpEncodesTemporaryQualifier)
2280{
2281 const MacAddress src = String2Mac("02:00:00:00:00:01");
2282 const MacAddress target = String2Mac("02:00:00:00:00:02");
2283
2285 Bytes sentFrame;
2286
2287 EXPECT_CALL(sock, Send(testing::_))
2288 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2289 testing::Return()));
2290
2291 EXPECT_FALSE(SetIp(sock,
2292 src,
2293 Mac2String(target),
2294 "192.168.1.10",
2295 "255.255.255.0",
2296 "192.168.1.1",
2297 false,
2298 0,
2299 100));
2300
2301 ASSERT_FALSE(sentFrame.empty());
2302
2303 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
2304 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
2305
2306 ASSERT_GE(dcp.payload.size(), 6U);
2307
2308 // Temporary qualifier = 0x0000.
2309 EXPECT_EQ(dcp.payload[4], 0x00);
2310 EXPECT_EQ(dcp.payload[5], 0x00);
2311}
2312
2314TEST(DcpTest, SendRequestBuildsIdentifyRequest)
2315{
2316 const MacAddress src = String2Mac("02:00:00:00:00:01");
2317
2319
2320 const BlockKey key{
2322 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2323 };
2324
2325 const Bytes value{'A', 'U', 'M', 'A'};
2326
2327 Bytes sentFrame;
2328
2329 EXPECT_CALL(sock, Send(testing::_))
2330 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2331 testing::Return()));
2332
2333 SendRequest(sock, src, key, value);
2334
2335 ASSERT_FALSE(sentFrame.empty());
2336
2337 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
2338
2339 EXPECT_EQ(eth.dst, String2Mac(DCP_MULTICAST_MAC));
2340 EXPECT_EQ(eth.src, src);
2341 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
2342
2343 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
2344
2345 EXPECT_EQ(dcp.frameId, DcpFrameId::IdentifyRequest);
2346 EXPECT_EQ(dcp.serviceId, DcpServiceId::Identify);
2347 EXPECT_EQ(dcp.serviceType, DcpServiceType::Request);
2348 EXPECT_EQ(dcp.respDelay, 0x0080);
2349
2350 ASSERT_GE(dcp.payload.size(), 8U);
2351
2352 EXPECT_EQ(dcp.payload[0], static_cast<std::uint8_t>(DcpOption::Device));
2353
2354 EXPECT_EQ(dcp.payload[1], static_cast<std::uint8_t>(DcpDeviceSuboption::Name));
2355
2356 EXPECT_EQ(dcp.payload[2], 0x00);
2357 EXPECT_EQ(dcp.payload[3], 0x04);
2358
2359 EXPECT_EQ(Bytes(dcp.payload.begin() + 4, dcp.payload.begin() + 8), value);
2360}
2361
2363TEST(DcpTest, SetIpBuildsPermanentIpBlock)
2364{
2365 const MacAddress src = String2Mac("02:00:00:00:00:01");
2366 const MacAddress target = String2Mac("02:00:00:00:00:02");
2367
2369 Bytes sentFrame;
2370
2371 EXPECT_CALL(sock, Send(testing::_))
2372 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2373 testing::Return()));
2374
2375 EXPECT_FALSE(SetIp(sock, src, Mac2String(target), "192.168.1.10", "255.255.255.0", "192.168.1.1", true, 0, 100));
2376
2377 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
2378 EXPECT_EQ(eth.dst, target);
2379 EXPECT_EQ(eth.src, src);
2380 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
2381
2382 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
2383
2384 EXPECT_EQ(dcp.frameId, DcpFrameId::GetSet);
2385 EXPECT_EQ(dcp.serviceId, DcpServiceId::Set);
2386 EXPECT_EQ(dcp.serviceType, DcpServiceType::Request);
2387
2388 ASSERT_GE(dcp.payload.size(), 18U);
2389
2390 EXPECT_EQ(dcp.payload[0], static_cast<std::uint8_t>(DcpOption::Ip));
2391 EXPECT_EQ(dcp.payload[1], static_cast<std::uint8_t>(DcpIpSuboption::IpParameter));
2392
2393 // 14 bytes: qualifier + IPv4 address + netmask + gateway.
2394 EXPECT_EQ(dcp.payload[2], 0x00);
2395 EXPECT_EQ(dcp.payload[3], 0x0E);
2396
2397 // Permanent.
2398 EXPECT_EQ(dcp.payload[4], 0x00);
2399 EXPECT_EQ(dcp.payload[5], 0x01);
2400
2401 EXPECT_EQ(Bytes(dcp.payload.begin() + 6, dcp.payload.begin() + 10), (Bytes{192, 168, 1, 10}));
2402
2403 EXPECT_EQ(Bytes(dcp.payload.begin() + 10, dcp.payload.begin() + 14), (Bytes{255, 255, 255, 0}));
2404
2405 EXPECT_EQ(Bytes(dcp.payload.begin() + 14, dcp.payload.begin() + 18), (Bytes{192, 168, 1, 1}));
2406}
2407
2409TEST(DcpTest, ReadResponseSetsExpectedSocketTimeout)
2410{
2411 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2412
2414
2415 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2416
2417 const auto result = ReadResponse(sock, myMac, 0, true);
2418 EXPECT_TRUE(result.empty());
2419}
2420
2422TEST(DcpTest, ReadResponseReturnsValidResponse)
2423{
2424 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2425 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2426
2427 const BlockKey key{
2429 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2430 };
2431
2432 const Bytes expectedPayload{'A', 'U', 'M', 'A'};
2433 const Bytes block = BuildReadResponseBlock(key.option,
2434 key.suboption,
2435 expectedPayload);
2436
2437 const Bytes frame = BuildReadResponseFrame(deviceMac,
2438 myMac,
2440 0x12345678,
2441 block,
2442 static_cast<std::uint16_t>(block.size()));
2443
2445
2446 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2447
2448 EXPECT_CALL(sock, Recv())
2449 .WillOnce(testing::Return(frame));
2450
2451 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true, 0x12345678);
2452
2453 ASSERT_EQ(result.size(), 1U);
2454
2455 const auto deviceIt = result.find(deviceMac);
2456 ASSERT_NE(deviceIt, result.end());
2457
2458 const auto blockIt = deviceIt->second.find(key);
2459 ASSERT_NE(blockIt, deviceIt->second.end());
2460
2461 EXPECT_EQ(blockIt->second, expectedPayload);
2462}
2463
2465TEST(DcpTest, ReadResponseAcceptsAnyXidWhenExpectedXidIsNotSpecified)
2466{
2467 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2468 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2469
2470 const BlockKey key{
2472 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2473 };
2474
2475 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, {'A', 'U', 'M', 'A'});
2476
2477 const Bytes frame = BuildReadResponseFrame(deviceMac,
2478 myMac,
2480 0xCAFEBABE,
2481 block,
2482 static_cast<std::uint16_t>(block.size()));
2483
2485
2486 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2487
2488 EXPECT_CALL(sock, Recv())
2489 .WillOnce(testing::Return(frame));
2490
2491 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true, std::nullopt);
2492
2493 ASSERT_EQ(result.size(), 1U);
2494 EXPECT_EQ(result.at(deviceMac).at(key),
2495 (Bytes{'A', 'U', 'M', 'A'}));
2496}
2497
2499TEST(DcpTest, ReadResponseIgnoresUnexpectedXid)
2500{
2501 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2502 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2503
2504 const BlockKey key{
2506 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2507 };
2508
2509 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, {'A', 'U', 'M', 'A'});
2510
2511 const Bytes frame = BuildReadResponseFrame(deviceMac,
2512 myMac,
2514 0x11111111,
2515 block,
2516 static_cast<std::uint16_t>(block.size()));
2517
2519
2520 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2521
2522 EXPECT_CALL(sock, Recv())
2523 .WillOnce(testing::Return(frame))
2524 .WillRepeatedly(testing::Return(Bytes{}));
2525
2526 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true, 0x22222222);
2527
2528 EXPECT_TRUE(result.empty());
2529}
2530
2532TEST(DcpTest, ReadResponseIgnoresEmptyData)
2533{
2534 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2535
2537
2538 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2539
2540 EXPECT_CALL(sock, Recv())
2541 .WillRepeatedly(testing::Return(Bytes{}));
2542
2543 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2544
2545 EXPECT_TRUE(result.empty());
2546}
2547
2549TEST(DcpTest, ReadResponseIgnoresShortEthernetFrame)
2550{
2551 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2552
2554
2555 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2556
2557 EXPECT_CALL(sock, Recv())
2558 .WillOnce(testing::Return(Bytes(13, 0x00)))
2559 .WillRepeatedly(testing::Return(Bytes{}));
2560
2561 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2562
2563 EXPECT_TRUE(result.empty());
2564}
2565
2567TEST(DcpTest, ReadResponseIgnoresMalformedEthernetFrame)
2568{
2569 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2570
2572
2573 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2574
2575 EXPECT_CALL(sock, Recv())
2576 .WillOnce(testing::Return(Bytes(14, 0x00)))
2577 .WillRepeatedly(testing::Return(Bytes{}));
2578
2579 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2580
2581 EXPECT_TRUE(result.empty());
2582}
2583
2585TEST(DcpTest, ReadResponseIgnoresWrongDestination)
2586{
2587 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2588 const MacAddress otherMac = String2Mac("02:00:00:00:00:03");
2589 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2590
2591 const Bytes frame = BuildReadResponseFrame(deviceMac,
2592 otherMac,
2594 0x12345678,
2595 {},
2596 0);
2597
2599
2600 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2601
2602 EXPECT_CALL(sock, Recv())
2603 .WillOnce(testing::Return(frame))
2604 .WillRepeatedly(testing::Return(Bytes{}));
2605
2606 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2607
2608 EXPECT_TRUE(result.empty());
2609}
2610
2612TEST(DcpTest, ReadResponseIgnoresShortVlanPayload)
2613{
2614 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2615 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2616
2617 EthernetHeader eth;
2618 eth.dst = myMac;
2619 eth.src = deviceMac;
2620 eth.type = VLAN_ETHERTYPE;
2621 eth.payload = {0x00, 0x00, 0x88};
2622
2623 const Bytes frame = eth.ToBytes();
2624
2626
2627 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2628
2629 EXPECT_CALL(sock, Recv())
2630 .WillOnce(testing::Return(frame))
2631 .WillRepeatedly(testing::Return(Bytes{}));
2632
2633 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2634
2635 EXPECT_TRUE(result.empty());
2636}
2637
2639TEST(DcpTest, ReadResponseIgnoresWrongVlanInnerEtherType)
2640{
2641 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2642 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2643
2644 const Bytes frame = BuildVlanReadResponseFrame(deviceMac,
2645 myMac,
2647 0x12345678,
2648 {},
2649 0,
2650 0x0800);
2651
2653
2654 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2655
2656 EXPECT_CALL(sock, Recv())
2657 .WillOnce(testing::Return(frame))
2658 .WillRepeatedly(testing::Return(Bytes{}));
2659
2660 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2661
2662 EXPECT_TRUE(result.empty());
2663}
2664
2666TEST(DcpTest, ReadResponseIgnoresWrongEtherType)
2667{
2668 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2669 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2670
2671 EthernetHeader eth;
2672 eth.dst = myMac;
2673 eth.src = deviceMac;
2674 eth.type = 0x0800;
2675 eth.payload = {0x01, 0x02, 0x03, 0x04};
2676
2677 const Bytes frame = eth.ToBytes();
2678
2680
2681 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2682
2683 EXPECT_CALL(sock, Recv())
2684 .WillOnce(testing::Return(frame))
2685 .WillRepeatedly(testing::Return(Bytes{}));
2686
2687 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2688
2689 EXPECT_TRUE(result.empty());
2690}
2691
2693TEST(DcpTest, ReadResponseIgnoresMalformedDcpHeader)
2694{
2695 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2696 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2697
2698 EthernetHeader eth;
2699 eth.dst = myMac;
2700 eth.src = deviceMac;
2702 eth.payload = {0x01, 0x02, 0x03};
2703
2704 const Bytes frame = eth.ToBytes();
2705
2707
2708 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2709
2710 EXPECT_CALL(sock, Recv())
2711 .WillOnce(testing::Return(frame))
2712 .WillRepeatedly(testing::Return(Bytes{}));
2713
2714 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2715
2716 EXPECT_TRUE(result.empty());
2717}
2718
2720TEST(DcpTest, ReadResponseIgnoresUnsuccessfulServiceType)
2721{
2722 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2723 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2724
2725 const Bytes frame = BuildReadResponseFrame(deviceMac,
2726 myMac,
2728 0x12345678,
2729 {},
2730 0);
2731
2733
2734 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2735
2736 EXPECT_CALL(sock, Recv())
2737 .WillOnce(testing::Return(frame))
2738 .WillRepeatedly(testing::Return(Bytes{}));
2739
2740 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2741
2742 EXPECT_TRUE(result.empty());
2743}
2744
2746TEST(DcpTest, ReadResponseParsesValidVlanResponse)
2747{
2748 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2749 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2750
2751 const BlockKey key{
2753 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2754 };
2755
2756 const Bytes expected{'A', 'U', 'M', 'A'};
2757 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, expected);
2758
2759 const Bytes frame = BuildVlanReadResponseFrame(deviceMac,
2760 myMac,
2762 0x12345678,
2763 block,
2764 static_cast<std::uint16_t>(block.size()));
2765
2767
2768 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2769
2770 EXPECT_CALL(sock, Recv())
2771 .WillOnce(testing::Return(frame))
2772 .WillRepeatedly(testing::Return(Bytes{}));
2773
2774 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true, 0x12345678);
2775
2776 ASSERT_EQ(result.size(), 1U);
2777 EXPECT_EQ(result.at(deviceMac).at(key), expected);
2778}
2779
2781TEST(DcpTest, ReadResponseUsesEmptyBlockRegionForShortDcpLength)
2782{
2783 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2784 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2785
2786 const BlockKey key{
2788 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2789 };
2790
2791 const Bytes block = BuildReadResponseBlock(key.option,
2792 key.suboption,
2793 {'A', 'U', 'M', 'A'});
2794
2795 ASSERT_FALSE(block.empty());
2796
2797 const Bytes frame = BuildReadResponseFrame(deviceMac,
2798 myMac,
2800 0x12345678,
2801 block,
2802 6);
2803
2805
2806 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2807
2808 // no need fo .WillRepeatedly(testing::Return(Bytes{}));, once is set
2809 EXPECT_CALL(sock, Recv())
2810 .WillOnce(testing::Return(frame));
2811
2812 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2813
2814 ASSERT_EQ(result.size(), 1U);
2815
2816 EXPECT_TRUE(result.at(deviceMac).empty());
2817}
2818
2820TEST(DcpTest, ReadResponseLimitsBlockRegionToDcpLength)
2821{
2822 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2823 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2824
2825 const BlockKey firstKey{
2827 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2828 };
2829
2830 const BlockKey secondKey{
2832 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Type),
2833 };
2834
2835 const Bytes firstBlock = BuildReadResponseBlock(firstKey.option, firstKey.suboption, {'A', 'U', 'M', 'A'});
2836
2837 const Bytes secondBlock = BuildReadResponseBlock(secondKey.option, secondKey.suboption, {'S', 'G', 'x'});
2838
2839 Bytes payload = firstBlock;
2840 payload.insert(payload.end(), secondBlock.begin(), secondBlock.end());
2841
2842 const Bytes frame = BuildReadResponseFrame(deviceMac,
2843 myMac,
2845 0x12345678,
2846 payload,
2847 static_cast<std::uint16_t>(firstBlock.size()));
2848
2850
2851 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2852
2853 // no need for .WillRepeatedly(testing::Return(Bytes{}));, once is set true
2854 EXPECT_CALL(sock, Recv())
2855 .WillOnce(testing::Return(frame));
2856
2857 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2858
2859 ASSERT_EQ(result.size(), 1U);
2860
2861 const auto& blocks = result.at(deviceMac);
2862
2863 ASSERT_EQ(blocks.size(), 1U);
2864 EXPECT_EQ(blocks.at(firstKey), (Bytes{'A', 'U', 'M', 'A'}));
2865
2866 EXPECT_EQ(blocks.find(secondKey), blocks.end());
2867}
2868
2870TEST(DcpTest, ReadResponseProcessesMultipleResponsesWhenOnceIsFalse)
2871{
2872 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2873 const MacAddress device1 = String2Mac("02:00:00:00:00:02");
2874 const MacAddress device2 = String2Mac("02:00:00:00:00:03");
2875
2876 const BlockKey key{
2878 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2879 };
2880
2881 const Bytes block1 = BuildReadResponseBlock(key.option, key.suboption, {'D', 'e', 'v', '1'});
2882
2883 const Bytes block2 = BuildReadResponseBlock(key.option, key.suboption, {'D', 'e', 'v', '2'});
2884
2885 const Bytes frame1 = BuildReadResponseFrame(device1,
2886 myMac,
2888 0x11111111,
2889 block1,
2890 static_cast<std::uint16_t>(block1.size()));
2891
2892 const Bytes frame2 = BuildReadResponseFrame(device2,
2893 myMac,
2895 0x22222222,
2896 block2,
2897 static_cast<std::uint16_t>(block2.size()));
2898
2900
2901 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2902
2903 EXPECT_CALL(sock, Recv())
2904 .WillOnce(testing::Return(frame1))
2905 .WillOnce(testing::Return(frame2))
2906 .WillRepeatedly(testing::Return(Bytes{}));
2907
2908 const ResponseMap result = ReadResponse(sock, myMac, 1.0, false);
2909
2910 ASSERT_EQ(result.size(), 2U);
2911
2912 EXPECT_EQ(result.at(device1).at(key), (Bytes{'D', 'e', 'v', '1'}));
2913
2914 EXPECT_EQ(result.at(device2).at(key), (Bytes{'D', 'e', 'v', '2'}));
2915}
2916
2918TEST(DcpTest, ReadResponseStopsAfterFirstValidResponseWhenOnceIsTrue)
2919{
2920 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2921 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2922
2923 const BlockKey key{
2925 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2926 };
2927
2928 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, {'D', 'e', 'v', '1'});
2929
2930 const Bytes frame = BuildReadResponseFrame(deviceMac,
2931 myMac,
2933 0x11111111,
2934 block,
2935 static_cast<std::uint16_t>(block.size()));
2936
2938
2939 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2940
2941 // One Recv() is allowed, because we pass this to ReadResponse
2942 EXPECT_CALL(sock, Recv())
2943 .WillOnce(testing::Return(frame));
2944
2945 const ResponseMap result = ReadResponse(sock, myMac, 0.01, true);
2946
2947 ASSERT_EQ(result.size(), 1U);
2948 EXPECT_EQ(result.at(deviceMac).at(key), (Bytes{'D', 'e', 'v', '1'}));
2949}
2950
2952TEST(DcpTest, ReadResponseContinuesAfterIgnoredFrames)
2953{
2954 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
2955 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
2956 const MacAddress otherMac = String2Mac("02:00:00:00:00:03");
2957
2958 const BlockKey key{
2960 .suboption = static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2961 };
2962
2963 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, {'A', 'U', 'M', 'A'});
2964
2965 const Bytes wrongDestination = BuildReadResponseFrame(deviceMac,
2966 otherMac,
2968 0x11111111,
2969 block,
2970 static_cast<std::uint16_t>(block.size()));
2971
2972 const Bytes unsuccessful = BuildReadResponseFrame(deviceMac,
2973 myMac,
2975 0x22222222,
2976 block,
2977 static_cast<std::uint16_t>(block.size()));
2978
2979 const Bytes requestBlock = BuildDcpBlock(DcpOption::Device,
2980 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
2981 {});
2982
2983 const Bytes request = BuildDcpFrame(myMac,
2984 deviceMac,
2988 0x44444444,
2989 requestBlock);
2990
2991 const Bytes valid = BuildReadResponseFrame(deviceMac,
2992 myMac,
2994 0x33333333,
2995 block,
2996 static_cast<std::uint16_t>(block.size()));
2997
2999
3000 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3001
3002 EXPECT_CALL(sock, Recv())
3003 .WillOnce(testing::Return(Bytes{}))
3004 .WillOnce(testing::Return(Bytes(13, 0x00)))
3005 .WillOnce(testing::Return(wrongDestination))
3006 .WillOnce(testing::Return(unsuccessful))
3007 .WillOnce(testing::Return(request))
3008 .WillOnce(testing::Return(valid))
3009 .WillRepeatedly(testing::Return(Bytes{}));
3010
3011 const ResponseMap result = ReadResponse(sock, myMac, 0.01);
3012
3013 ASSERT_EQ(result.size(), 1U);
3014 EXPECT_EQ(result.at(deviceMac).at(key), (Bytes{'A', 'U', 'M', 'A'}));
3015}
3016
3018TEST(DcpTest, ReadResponseReturnsEmptyMapOnTimeout)
3019{
3020 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3021
3023
3024 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3025
3026 const ResponseMap result = ReadResponse(sock, myMac, 0, true);
3027
3028 EXPECT_TRUE(result.empty());
3029}
3030
3032TEST(DcpTest, SendHelloBuildsCompleteHelloFrame)
3033{
3034 const MacAddress src = String2Mac("02:00:00:00:00:01");
3035
3037 Bytes sentFrame;
3038
3039 EXPECT_CALL(sock, Send(testing::_))
3040 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3041 testing::Return()));
3042
3043 SendHello(sock,
3044 src,
3045 "valve2",
3046 "192.168.1.10",
3047 "255.255.255.0",
3048 "192.168.1.1",
3049 {0x013F, 0x0002},
3051
3052 ASSERT_FALSE(sentFrame.empty());
3053
3054 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
3055
3056 EXPECT_EQ(eth.dst, String2Mac(DCP_HELLO_MULTICAST_MAC));
3057
3058 EXPECT_EQ(eth.src, src);
3059 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
3060
3061 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
3062
3063 EXPECT_EQ(dcp.frameId, DcpFrameId::Hello);
3064 EXPECT_EQ(dcp.serviceId, DcpServiceId::Hello);
3065 EXPECT_EQ(dcp.serviceType, DcpServiceType::Request);
3066 EXPECT_EQ(dcp.respDelay, 0);
3067
3068 ASSERT_FALSE(dcp.payload.empty());
3069 EXPECT_EQ(dcp.length, dcp.payload.size());
3070
3071 std::size_t offset = 0;
3072
3073 // Name block.
3074 const TestDcpBlock nameBlock = ParseTestDcpBlock(dcp.payload, offset);
3075
3076 EXPECT_EQ(nameBlock.option, DcpOption::Device);
3077 EXPECT_EQ(nameBlock.suboption, static_cast<std::uint8_t>(DcpDeviceSuboption::Name));
3078 EXPECT_EQ(nameBlock.length, 6);
3079 EXPECT_EQ(nameBlock.payload, (Bytes{'v', 'a', 'l', 'v', 'e', '2'}));
3080
3081 offset += 4U + nameBlock.length;
3082 EXPECT_EQ(offset % 2U, 0U);
3083
3084 // IP block.
3085 const TestDcpBlock ipBlock = ParseTestDcpBlock(dcp.payload, offset);
3086
3087 EXPECT_EQ(ipBlock.option, DcpOption::Ip);
3088 EXPECT_EQ(ipBlock.suboption, static_cast<std::uint8_t>(DcpIpSuboption::IpParameter));
3089 EXPECT_EQ(ipBlock.length, 12);
3090
3091 // clang-format off
3092 EXPECT_EQ(ipBlock.payload,
3093 (Bytes{
3094 192, 168,1,10,
3095 255, 255,255,0,
3096 192, 168,1,1,
3097 }));
3098 // clang-format on
3099 offset += 4U + ipBlock.length;
3100
3101 // Device ID block.
3102 const TestDcpBlock deviceIdBlock = ParseTestDcpBlock(dcp.payload, offset);
3103
3104 EXPECT_EQ(deviceIdBlock.option, DcpOption::Device);
3105 EXPECT_EQ(deviceIdBlock.suboption, static_cast<std::uint8_t>(DcpDeviceSuboption::DeviceId));
3106 EXPECT_EQ(deviceIdBlock.length, 4);
3107
3108 EXPECT_EQ(deviceIdBlock.payload, (Bytes{0x01, 0x3F, 0x00, 0x02}));
3109
3110 offset += 4U + deviceIdBlock.length;
3111
3112 // Device Role block.
3113 const TestDcpBlock roleBlock = ParseTestDcpBlock(dcp.payload, offset);
3114
3115 EXPECT_EQ(roleBlock.option, DcpOption::Device);
3116 EXPECT_EQ(roleBlock.suboption, static_cast<std::uint8_t>(DcpDeviceSuboption::Role));
3117 EXPECT_EQ(roleBlock.length, 2);
3118
3119 EXPECT_EQ(roleBlock.payload, (Bytes{
3120 static_cast<std::uint8_t>(DcpDeviceRole::IoDevice),
3121 0x00,
3122 }));
3123
3124 offset += 4U + roleBlock.length;
3125
3126 // Device Initiative block.
3127 const TestDcpBlock initiativeBlock = ParseTestDcpBlock(dcp.payload, offset);
3128
3129 EXPECT_EQ(initiativeBlock.option, DcpOption::DeviceInitiative);
3130
3131 EXPECT_EQ(initiativeBlock.suboption, static_cast<std::uint8_t>(DcpDeviceInitiativeSuboption::Initiative));
3132
3133 EXPECT_EQ(initiativeBlock.length, 2);
3134
3135 EXPECT_EQ(initiativeBlock.payload, (Bytes{
3136 0x00,
3137 static_cast<std::uint8_t>(DcpDeviceInitiative::IssueHello),
3138 }));
3139
3140 offset += 4U + initiativeBlock.length;
3141
3142 EXPECT_EQ(offset, dcp.payload.size());
3143}
3144
3146TEST(DcpTest, SendHelloAddsPaddingForOddLengthStationName)
3147{
3148 const MacAddress src = String2Mac("02:00:00:00:00:01");
3149
3151 Bytes sentFrame;
3152
3153 EXPECT_CALL(sock, Send(testing::_))
3154 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3155 testing::Return()));
3156
3157 SendHello(sock,
3158 src,
3159 "valve",
3160 "192.168.1.10",
3161 "255.255.255.0",
3162 "192.168.1.1",
3163 {0x013F, 0x0002},
3165
3166 ASSERT_FALSE(sentFrame.empty());
3167
3168 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
3169 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
3170
3171 // The name block is 4-byte header + 5-byte payload = 9 bytes.
3172 ASSERT_GE(dcp.payload.size(), 10U);
3173
3174 EXPECT_EQ(dcp.payload[0], static_cast<std::uint8_t>(DcpOption::Device));
3175
3176 EXPECT_EQ(dcp.payload[1], static_cast<std::uint8_t>(DcpDeviceSuboption::Name));
3177
3178 EXPECT_EQ(dcp.payload[2], 0x00);
3179 EXPECT_EQ(dcp.payload[3], 0x05);
3180
3181 EXPECT_EQ(Bytes(dcp.payload.begin() + 4, dcp.payload.begin() + 9), (Bytes{'v', 'a', 'l', 'v', 'e'}));
3182
3183 // Explicit padding added by SendHello().
3184 EXPECT_EQ(dcp.payload[9], 0x00);
3185
3186 // The next block must start immediately after the padding byte.
3187 EXPECT_EQ(dcp.payload[10], static_cast<std::uint8_t>(DcpOption::Ip));
3188}
3189
3191TEST(DcpTest, SendHelloEncodesCombinedDeviceRole)
3192{
3193 const MacAddress src = String2Mac("02:00:00:00:00:01");
3194
3196
3198 Bytes sentFrame;
3199
3200 EXPECT_CALL(sock, Send(testing::_))
3201 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3202 testing::Return()));
3203
3204 SendHello(sock,
3205 src,
3206 "device",
3207 "10.250.21.110",
3208 "255.255.255.0",
3209 "10.250.21.1",
3210 {0x00B0, 0x0193},
3211 role);
3212
3213 ASSERT_FALSE(sentFrame.empty());
3214
3215 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
3216 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
3217
3218 // Name block: 4 + 6 + no padding.
3219 constexpr std::size_t roleOffset = 4U + 6U +
3220 4U + 12U +
3221 4U + 4U;
3222
3223 ASSERT_GE(dcp.payload.size(), roleOffset + 6U);
3224
3225 EXPECT_EQ(dcp.payload[roleOffset], static_cast<std::uint8_t>(DcpOption::Device));
3226
3227 EXPECT_EQ(dcp.payload[roleOffset + 1U], static_cast<std::uint8_t>(DcpDeviceSuboption::Role));
3228
3229 EXPECT_EQ(dcp.payload[roleOffset + 2U], 0x00);
3230 EXPECT_EQ(dcp.payload[roleOffset + 3U], 0x02);
3231
3232 EXPECT_EQ(dcp.payload[roleOffset + 4U], static_cast<std::uint8_t>(role));
3233
3234 EXPECT_EQ(dcp.payload[roleOffset + 5U], 0x00);
3235}
3236
3238TEST(DcpTest, SendHelloEncodesDeviceId)
3239{
3240 const MacAddress src = String2Mac("02:00:00:00:00:01");
3241
3243 Bytes sentFrame;
3244
3245 EXPECT_CALL(sock, Send(testing::_))
3246 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3247 testing::Return()));
3248
3249 SendHello(sock,
3250 src,
3251 "device",
3252 "10.250.21.110",
3253 "255.255.255.0",
3254 "10.250.21.1",
3255 {0x1234, 0xABCD},
3257
3258 ASSERT_FALSE(sentFrame.empty());
3259
3260 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
3261 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
3262
3263 std::size_t offset = 0;
3264
3265 const TestDcpBlock nameBlock = ParseTestDcpBlock(dcp.payload, offset);
3266
3267 offset += 4U + nameBlock.length;
3268
3269 if (nameBlock.length % 2U != 0U)
3270 {
3271 ++offset;
3272 }
3273
3274 const TestDcpBlock ipBlock = ParseTestDcpBlock(dcp.payload, offset);
3275
3276 offset += 4U + ipBlock.length;
3277
3278 const TestDcpBlock deviceIdBlock = ParseTestDcpBlock(dcp.payload, offset);
3279
3280 EXPECT_EQ(deviceIdBlock.payload, (Bytes{0x12, 0x34, 0xAB, 0xCD}));
3281}
3282
3284TEST(DcpTest, SendHelloRejectsInvalidIpAddress)
3285{
3286 const MacAddress src = String2Mac("02:00:00:00:00:01");
3287
3289
3290 EXPECT_CALL(sock, Send(testing::_))
3291 .Times(0);
3292
3293 EXPECT_THROW(SendHello(sock,
3294 src,
3295 "device",
3296 "999.999.999.999",
3297 "255.255.255.0",
3298 "192.168.1.1",
3299 {0x013F, 0x0002},
3301 std::exception);
3302}
3303
3305TEST(DcpTest, SendHelloRejectsInvalidNetmask)
3306{
3307 const MacAddress src = String2Mac("02:00:00:00:00:01");
3308
3310
3311 EXPECT_CALL(sock, Send(testing::_))
3312 .Times(0);
3313
3314 EXPECT_THROW(SendHello(sock,
3315 src,
3316 "device",
3317 "192.168.1.10",
3318 "999.999.999.999",
3319 "192.168.1.1",
3320 {0x013F, 0x0002},
3322 std::exception);
3323}
3324
3326TEST(DcpTest, SendHelloRejectsInvalidGateway)
3327{
3328 const MacAddress src = String2Mac("02:00:00:00:00:01");
3329
3331
3332 EXPECT_CALL(sock, Send(testing::_))
3333 .Times(0);
3334
3335 EXPECT_THROW(SendHello(sock,
3336 src,
3337 "device",
3338 "192.168.1.10",
3339 "255.255.255.0",
3340 "999.999.999.999",
3341 {0x013F, 0x0002},
3343 std::exception);
3344}
3345
3347TEST(DcpTest, ReceiveHelloSetsExpectedSocketTimeout)
3348{
3349 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3350
3352
3353 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3354
3355 const auto devices = ReceiveHello(sock, myMac, 0, {});
3356
3357 EXPECT_TRUE(devices.empty());
3358}
3359
3361TEST(DcpTest, ReceiveHelloReturnsValidDevice)
3362{
3363 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3364 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3365
3366 Bytes payload;
3367
3368 const Bytes nameBlock = BuildHelloBlock(DcpOption::Device,
3369 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
3370 {'v', 'a', 'l', 'v', 'e', '2'});
3371 //clang-format off
3372 const Bytes ipBlock = BuildHelloBlock(DcpOption::Ip,
3373 static_cast<std::uint8_t>(DcpIpSuboption::IpParameter),
3374 {192, 168, 1, 10,
3375 255, 255, 255, 0,
3376 192, 168, 1, 1});
3377 //clang-format on
3378 const Bytes deviceIdBlock = BuildHelloBlock(DcpOption::Device,
3379 static_cast<std::uint8_t>(DcpDeviceSuboption::DeviceId),
3380 {0x01, 0x3F, 0x00, 0x02});
3381
3382 const Bytes roleBlock = BuildHelloBlock(DcpOption::Device,
3383 static_cast<std::uint8_t>(DcpDeviceSuboption::Role),
3384 {
3385 static_cast<std::uint8_t>(DcpDeviceRole::IoDevice),
3386 0x00,
3387 });
3388
3389 payload.insert(payload.end(), nameBlock.begin(), nameBlock.end());
3390 payload.insert(payload.end(), ipBlock.begin(), ipBlock.end());
3391 payload.insert(payload.end(), deviceIdBlock.begin(), deviceIdBlock.end());
3392 payload.insert(payload.end(), roleBlock.begin(), roleBlock.end());
3393
3394 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3395 myMac,
3398 payload);
3399
3401
3402 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3403
3404 EXPECT_CALL(sock, Recv())
3405 .WillOnce(testing::Return(frame))
3406 .WillRepeatedly(testing::Return(Bytes{}));
3407
3408 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3409
3410 ASSERT_EQ(devices.size(), 1U);
3411
3412 EXPECT_EQ(devices.front().mac, Mac2String(deviceMac));
3413 EXPECT_EQ(devices.front().name, "valve2");
3414 EXPECT_EQ(devices.front().ip, "192.168.1.10");
3415 EXPECT_EQ(devices.front().netmask, "255.255.255.0");
3416 EXPECT_EQ(devices.front().gateway, "192.168.1.1");
3417 EXPECT_EQ(devices.front().VendorId(), 0x013F);
3418 EXPECT_EQ(devices.front().DeviceId(), 0x0002);
3419
3420 ASSERT_EQ(devices.front().deviceRoles.size(), 1U);
3421 EXPECT_EQ(devices.front().deviceRoles.front(), "IoDevice");
3422}
3423
3425TEST(DcpTest, ReceiveHelloInvokesCallbackForValidDevice)
3426{
3427 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3428 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3429
3430 const Bytes payload = BuildHelloBlock(DcpOption::Device,
3431 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
3432 {'d', 'e', 'v', '1'});
3433
3434 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3435 myMac,
3438 payload);
3439
3441
3442 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3443
3444 EXPECT_CALL(sock, Recv())
3445 .WillOnce(testing::Return(frame))
3446 .WillRepeatedly(testing::Return(Bytes{}));
3447
3448 bool callbackCalled = false;
3449 DCPDeviceDescription* callbackDevice = nullptr;
3450
3451 const auto devices = ReceiveHello(sock, myMac, 0.01, [&](const DCPDeviceDescription& device)
3452 {
3453 callbackCalled = true;
3454 callbackDevice = const_cast<DCPDeviceDescription*>(&device);
3455
3456 EXPECT_EQ(device.mac, Mac2String(deviceMac));
3457 EXPECT_EQ(device.name, "dev1");
3458 });
3459
3460 ASSERT_EQ(devices.size(), 1U);
3461 EXPECT_TRUE(callbackCalled);
3462 ASSERT_NE(callbackDevice, nullptr);
3463}
3464
3466TEST(DcpTest, ReceiveHelloDoesNotInvokeEmptyCallback)
3467{
3468 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3469 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3470
3471 const Bytes payload = BuildHelloBlock(DcpOption::Device,
3472 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
3473 {'d', 'e', 'v', '1'});
3474
3475 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3476 myMac,
3479 payload);
3480
3482
3483 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3484
3485 EXPECT_CALL(sock, Recv())
3486 .WillOnce(testing::Return(frame))
3487 .WillRepeatedly(testing::Return(Bytes{}));
3488
3489 const auto devices = ReceiveHello(sock, myMac, 0.01, nullptr);
3490
3491 ASSERT_EQ(devices.size(), 1U);
3492}
3493
3495TEST(DcpTest, ReceiveHelloParsesValidVlanHello)
3496{
3497 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3498 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3499
3500 const Bytes payload = BuildHelloBlock(DcpOption::Device,
3501 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
3502 {'v', 'a', 'l', 'v', 'e'});
3503
3504 const Bytes frame = BuildVlanReceiveHelloFrame(deviceMac,
3505 myMac,
3508 payload);
3509
3511
3512 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3513
3514 EXPECT_CALL(sock, Recv())
3515 .WillOnce(testing::Return(frame))
3516 .WillRepeatedly(testing::Return(Bytes{}));
3517
3518 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3519
3520 ASSERT_EQ(devices.size(), 1U);
3521 EXPECT_EQ(devices.front().mac, Mac2String(deviceMac));
3522 EXPECT_EQ(devices.front().name, "valve");
3523}
3524
3526TEST(DcpTest, ReceiveHelloIgnoresEmptyData)
3527{
3528 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3529
3531
3532 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3533
3534 EXPECT_CALL(sock, Recv())
3535 .WillOnce(testing::Return(Bytes{}))
3536 .WillRepeatedly(testing::Return(Bytes{}));
3537
3538 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3539
3540 EXPECT_TRUE(devices.empty());
3541}
3542
3544TEST(DcpTest, ReceiveHelloIgnoresShortEthernetFrame)
3545{
3546 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3547
3549
3550 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3551
3552 EXPECT_CALL(sock, Recv())
3553 .WillOnce(testing::Return(Bytes(13, 0x00)))
3554 .WillRepeatedly(testing::Return(Bytes{}));
3555
3556 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3557
3558 EXPECT_TRUE(devices.empty());
3559}
3560
3562TEST(DcpTest, ReceiveHelloIgnoresMalformedEthernetFrame)
3563{
3564 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3565
3567
3568 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3569
3570 EXPECT_CALL(sock, Recv())
3571 .WillOnce(testing::Return(Bytes(14, 0x00)))
3572 .WillRepeatedly(testing::Return(Bytes{}));
3573
3574 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3575
3576 EXPECT_TRUE(devices.empty());
3577}
3578
3580TEST(DcpTest, ReceiveHelloIgnoresShortVlanPayload)
3581{
3582 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3583 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3584
3585 EthernetHeader eth;
3586 eth.dst = myMac;
3587 eth.src = deviceMac;
3588 eth.type = VLAN_ETHERTYPE;
3589 eth.payload = {0x00, 0x00, 0x88};
3590
3591 const Bytes frame = eth.ToBytes();
3592
3594
3595 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3596
3597 EXPECT_CALL(sock, Recv())
3598 .WillOnce(testing::Return(frame))
3599 .WillRepeatedly(testing::Return(Bytes{}));
3600
3601 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3602
3603 EXPECT_TRUE(devices.empty());
3604}
3605
3607TEST(DcpTest, ReceiveHelloIgnoresWrongVlanInnerEtherType)
3608{
3609 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3610 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3611
3612 const Bytes frame = BuildVlanReceiveHelloFrame(deviceMac,
3613 myMac,
3616 {},
3617 0x0800);
3618
3620
3621 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3622
3623 EXPECT_CALL(sock, Recv())
3624 .WillOnce(testing::Return(frame))
3625 .WillRepeatedly(testing::Return(Bytes{}));
3626
3627 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3628
3629 EXPECT_TRUE(devices.empty());
3630}
3631
3633TEST(DcpTest, ReceiveHelloIgnoresWrongEtherType)
3634{
3635 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3636 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3637
3638 EthernetHeader eth;
3639 eth.dst = myMac;
3640 eth.src = deviceMac;
3641 eth.type = 0x0800;
3642 eth.payload = Bytes(20, 0x00);
3643
3644 const Bytes frame = eth.ToBytes();
3645
3647
3648 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3649
3650 EXPECT_CALL(sock, Recv())
3651 .WillOnce(testing::Return(frame))
3652 .WillRepeatedly(testing::Return(Bytes{}));
3653
3654 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3655
3656 EXPECT_TRUE(devices.empty());
3657}
3658
3660TEST(DcpTest, ReceiveHelloIgnoresShortPnioPayload)
3661{
3662 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3663 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3664
3665 EthernetHeader eth;
3666 eth.dst = myMac;
3667 eth.src = deviceMac;
3669 eth.payload = Bytes(9, 0x00);
3670
3671 const Bytes frame = eth.ToBytes();
3672
3674
3675 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3676
3677 EXPECT_CALL(sock, Recv())
3678 .WillOnce(testing::Return(frame))
3679 .WillRepeatedly(testing::Return(Bytes{}));
3680
3681 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3682
3683 EXPECT_TRUE(devices.empty());
3684}
3685
3687TEST(DcpTest, ReceiveHelloIgnoresMalformedDcpHeader)
3688{
3689 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3690 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3691
3692 EthernetHeader eth;
3693 eth.dst = myMac;
3694 eth.src = deviceMac;
3696 eth.payload = Bytes(10, 0x00);
3697
3698 const Bytes frame = eth.ToBytes();
3699
3701
3702 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3703
3704 EXPECT_CALL(sock, Recv())
3705 .WillOnce(testing::Return(frame))
3706 .WillRepeatedly(testing::Return(Bytes{}));
3707
3708 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3709
3710 EXPECT_TRUE(devices.empty());
3711}
3712
3714TEST(DcpTest, ReceiveHelloIgnoresNonHelloServiceId)
3715{
3716 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3717 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3718
3719 const Bytes payload = BuildHelloBlock(DcpOption::Device,
3720 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
3721 {'d', 'e', 'v'});
3722
3723 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3724 myMac,
3727 payload);
3728
3730
3731 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3732
3733 EXPECT_CALL(sock, Recv())
3734 .WillOnce(testing::Return(frame))
3735 .WillRepeatedly(testing::Return(Bytes{}));
3736
3737 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3738
3739 EXPECT_TRUE(devices.empty());
3740}
3741
3743TEST(DcpTest, ReceiveHelloIgnoresNonRequestServiceType)
3744{
3745 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3746 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3747
3748 const Bytes payload = BuildHelloBlock(DcpOption::Device,
3749 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
3750 {'d', 'e', 'v'});
3751
3752 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3753 myMac,
3756 payload);
3757
3759
3760 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3761
3762 EXPECT_CALL(sock, Recv())
3763 .WillOnce(testing::Return(frame))
3764 .WillRepeatedly(testing::Return(Bytes{}));
3765
3766 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3767
3768 EXPECT_TRUE(devices.empty());
3769}
3770
3772TEST(DcpTest, ReceiveHelloContinuesAfterIgnoredFrame)
3773{
3774 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3775 const MacAddress deviceMac = String2Mac("02:00:00:00:00:02");
3776
3777 const Bytes invalidFrame = BuildReceiveHelloFrame(deviceMac,
3778 myMac,
3781 {});
3782
3783 const Bytes validPayload = BuildHelloBlock(DcpOption::Device,
3784 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
3785 {'v', 'a', 'l', 'i', 'd'});
3786
3787 const Bytes validFrame = BuildReceiveHelloFrame(deviceMac,
3788 myMac,
3791 validPayload);
3792
3794
3795 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3796
3797 EXPECT_CALL(sock, Recv())
3798 .WillOnce(testing::Return(invalidFrame))
3799 .WillOnce(testing::Return(validFrame))
3800 .WillRepeatedly(testing::Return(Bytes{}));
3801
3802 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3803
3804 ASSERT_EQ(devices.size(), 1U);
3805 EXPECT_EQ(devices.front().name, "valid");
3806}
3807
3809TEST(DcpTest, ReceiveHelloReturnsMultipleDevices)
3810{
3811 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3812 const MacAddress device1 = String2Mac("02:00:00:00:00:02");
3813 const MacAddress device2 = String2Mac("02:00:00:00:00:03");
3814
3815 const Bytes payload1 = BuildHelloBlock(DcpOption::Device,
3816 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
3817 {'d', 'e', 'v', '1'});
3818
3819 const Bytes payload2 = BuildHelloBlock(DcpOption::Device,
3820 static_cast<std::uint8_t>(DcpDeviceSuboption::Name),
3821 {'d', 'e', 'v', '2'});
3822
3823 const Bytes frame1 = BuildReceiveHelloFrame(device1,
3824 myMac,
3827 payload1);
3828
3829 const Bytes frame2 = BuildReceiveHelloFrame(device2,
3830 myMac,
3833 payload2);
3834
3836
3837 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3838
3839 EXPECT_CALL(sock, Recv())
3840 .WillOnce(testing::Return(frame1))
3841 .WillOnce(testing::Return(frame2))
3842 .WillRepeatedly(testing::Return(Bytes{}));
3843
3844 const auto devices = ReceiveHello(sock, myMac, 0.01, {});
3845
3846 ASSERT_EQ(devices.size(), 2U);
3847
3848 EXPECT_EQ(devices[0].mac, Mac2String(device1));
3849 EXPECT_EQ(devices[0].name, "dev1");
3850
3851 EXPECT_EQ(devices[1].mac, Mac2String(device2));
3852 EXPECT_EQ(devices[1].name, "dev2");
3853}
3854
3856TEST(DcpTest, ReceiveHelloReturnsEmptyOnTimeout)
3857{
3858 const MacAddress myMac = String2Mac("02:00:00:00:00:01");
3859
3861
3862 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3863
3864 const auto devices = ReceiveHello(sock, myMac, 0, {});
3865
3866 EXPECT_TRUE(devices.empty());
3867}
3868
3870TEST(DcpTest, SignalDeviceReturnsTrueForSuccessfulResponse)
3871{
3872 const MacAddress src = String2Mac("02:00:00:00:00:01");
3873 const MacAddress target = String2Mac("02:00:00:00:00:02");
3874
3876 Bytes sentFrame;
3877
3878 const Bytes response = BuildSetResponseFrame(target, src, DcpBlockError::Ok);
3879
3880 EXPECT_CALL(sock, Send(testing::_))
3881 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3882 testing::Return()));
3883
3884 EXPECT_CALL(sock, Recv())
3885 .WillOnce(testing::Return(response));
3886
3887 const bool result = SignalDevice(sock, src, Mac2String(target), 3000, 1.0);
3888
3889 EXPECT_TRUE(result);
3890
3891 ASSERT_FALSE(sentFrame.empty());
3892
3893 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
3894
3895 EXPECT_EQ(eth.dst, target);
3896 EXPECT_EQ(eth.src, src);
3897 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
3898
3899 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
3900
3901 EXPECT_EQ(dcp.frameId, DcpFrameId::GetSet);
3902 EXPECT_EQ(dcp.serviceId, DcpServiceId::Set);
3903 EXPECT_EQ(dcp.serviceType, DcpServiceType::Request);
3904}
3905
3907TEST(DcpTest, SignalDeviceThrowsForBlockError)
3908{
3909 const MacAddress src = String2Mac("02:00:00:00:00:01");
3910 const MacAddress target = String2Mac("02:00:00:00:00:02");
3911
3913
3914 const Bytes response = BuildSetResponseFrame(target, src, DcpBlockError::SetNotPossible, true);
3915
3916 EXPECT_CALL(sock, Send(testing::_));
3917
3918 EXPECT_CALL(sock, Recv())
3919 .WillOnce(testing::Return(response));
3920
3921 EXPECT_THROW(SignalDevice(sock, src, Mac2String(target), 3000, 1.0),
3922 DCPError);
3923}
3924
3926TEST(DcpTest, SignalDeviceReturnsFalseOnTimeout)
3927{
3928 const MacAddress src = String2Mac("02:00:00:00:00:01");
3929 const MacAddress target = String2Mac("02:00:00:00:00:02");
3930
3932 Bytes sentFrame;
3933
3934 EXPECT_CALL(sock, Send(testing::_))
3935 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3936 testing::Return()));
3937
3938 const bool result = SignalDevice(sock, src, Mac2String(target), 3000, 0.0);
3939
3940 EXPECT_FALSE(result);
3941
3942 ASSERT_FALSE(sentFrame.empty());
3943
3944 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
3945
3946 EXPECT_EQ(eth.dst, target);
3947 EXPECT_EQ(eth.src, src);
3948 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
3949}
3950
3952TEST(DcpTest, SignalDeviceRejectsInvalidTargetMac)
3953{
3954 const MacAddress src = String2Mac("02:00:00:00:00:01");
3955
3957
3958 EXPECT_CALL(sock, Send(testing::_))
3959 .Times(0);
3960
3961 EXPECT_THROW(SignalDevice(sock, src, "not-a-mac-address", 3000, 0.0),
3962 std::exception);
3963}
3964
3966TEST(DcpTest, ResetToFactoryReturnsTrueForSuccessfulResponse)
3967{
3968 const MacAddress src = String2Mac("02:00:00:00:00:01");
3969 const MacAddress target = String2Mac("02:00:00:00:00:02");
3970
3972 Bytes sentFrame;
3973
3974 const Bytes response = BuildSetResponseFrame(target, src, DcpBlockError::Ok);
3975
3976 EXPECT_CALL(sock, Send(testing::_))
3977 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3978 testing::Return()));
3979
3980 EXPECT_CALL(sock, Recv())
3981 .WillOnce(testing::Return(response));
3982
3983 const auto start = std::chrono::steady_clock::now();
3984
3985 const bool result = ResetToFactory(sock, src, Mac2String(target), DcpResetMode::Communication, 1);
3986
3987 EXPECT_TRUE(result);
3988
3989 // ResetToFactory deliberately waits two seconds after a successful response.
3990 const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start);
3991
3992 EXPECT_GE(elapsed.count(), 90);
3993
3994 ASSERT_FALSE(sentFrame.empty());
3995
3996 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
3997
3998 EXPECT_EQ(eth.dst, target);
3999 EXPECT_EQ(eth.src, src);
4000 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
4001
4002 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
4003
4004 EXPECT_EQ(dcp.frameId, DcpFrameId::GetSet);
4005 EXPECT_EQ(dcp.serviceId, DcpServiceId::Set);
4006 EXPECT_EQ(dcp.serviceType, DcpServiceType::Request);
4007 EXPECT_EQ(dcp.respDelay, 0);
4008
4009 ASSERT_GE(dcp.payload.size(), 6U);
4010
4011 EXPECT_EQ(dcp.payload[0], static_cast<std::uint8_t>(DcpOption::Control));
4012
4013 EXPECT_EQ(dcp.payload[1], static_cast<std::uint8_t>(DcpControlSuboption::ResetToFactory));
4014
4015 // Block length = two-byte reset-mode qualifier.
4016 EXPECT_EQ(dcp.payload[2], 0x00);
4017 EXPECT_EQ(dcp.payload[3], 0x02);
4018
4019 // DcpResetMode::Communication = 0x0002.
4020 EXPECT_EQ(dcp.payload[4], 0x00);
4021 EXPECT_EQ(dcp.payload[5], 0x02);
4022
4023 EXPECT_EQ(dcp.length, 6U);
4024}
4025
4027TEST(DcpTest, ResetToFactoryEncodesCombinedResetMode)
4028{
4029 const MacAddress src = String2Mac("02:00:00:00:00:01");
4030 const MacAddress target = String2Mac("02:00:00:00:00:02");
4031
4035
4037 Bytes sentFrame;
4038
4039 EXPECT_CALL(sock, Send(testing::_))
4040 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
4041 testing::Return()));
4042
4043 const bool result = ResetToFactory(sock, src, Mac2String(target), mode, 0.0);
4044
4045 EXPECT_FALSE(result);
4046
4047 ASSERT_FALSE(sentFrame.empty());
4048
4049 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
4050
4051 const PNDCPHeader dcp = PNDCPHeader::Parse(eth.payload);
4052
4053 ASSERT_GE(dcp.payload.size(), 6U);
4054 wire::Reader r(dcp.payload);
4055 r.SkipTo(4);
4056
4057 const auto encodedMode = r.U16();
4058
4059 EXPECT_EQ(encodedMode, static_cast<std::uint16_t>(mode));
4060
4061 // Communication (0x0002) |
4062 // Application (0x0004) |
4063 // Engineering (0x0008) = 0x000E.
4064 EXPECT_EQ(encodedMode, 0x000E);
4065}
4066
4068TEST(DcpTest, ResetToFactoryReturnsFalseOnTimeout)
4069{
4070 const MacAddress src = String2Mac("02:00:00:00:00:01");
4071 const MacAddress target = String2Mac("02:00:00:00:00:02");
4072
4074 Bytes sentFrame;
4075
4076 EXPECT_CALL(sock, Send(testing::_))
4077 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
4078 testing::Return()));
4079
4080 const bool result = ResetToFactory(sock, src, Mac2String(target), DcpResetMode::Communication, 0.0);
4081
4082 EXPECT_FALSE(result);
4083
4084 ASSERT_FALSE(sentFrame.empty());
4085
4086 const EthernetHeader eth = EthernetHeader::Parse(sentFrame);
4087
4088 EXPECT_EQ(eth.dst, target);
4089 EXPECT_EQ(eth.src, src);
4090 EXPECT_EQ(eth.type, PROFINET_ETHERTYPE);
4091}
4092
4094TEST(DcpTest, ResetToFactoryThrowsForBlockError)
4095{
4096 const MacAddress src = String2Mac("02:00:00:00:00:01");
4097 const MacAddress target = String2Mac("02:00:00:00:00:02");
4098
4100
4101 const Bytes response = BuildSetResponseFrame(target, src, DcpBlockError::SetNotPossible, true);
4102
4103 EXPECT_CALL(sock, Send(testing::_));
4104
4105 EXPECT_CALL(sock, Recv())
4106 .WillOnce(testing::Return(response));
4107
4108 EXPECT_THROW(ResetToFactory(sock, src, Mac2String(target), DcpResetMode::Communication, 1.0),
4109 DCPError);
4110}
4111
4113TEST(DcpTest, ResetToFactoryRejectsInvalidTargetMac)
4114{
4115 const MacAddress src = String2Mac("02:00:00:00:00:01");
4116
4118
4119 EXPECT_CALL(sock, Send(testing::_))
4120 .Times(0);
4121
4122 EXPECT_THROW(ResetToFactory(sock, src, "not-a-mac-address", DcpResetMode::Communication, 0.0),
4123 std::exception);
4124}
4125
4127TEST(DcpTest, GetParamSendsCorrectGetRequest)
4128{
4129 const MacAddress src = String2Mac("02:00:00:00:00:01");
4130 const MacAddress target = String2Mac("02:00:00:00:00:02");
4131
4133 Bytes sentFrame;
4134
4135 EXPECT_CALL(sock, Send(testing::_))
4136 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
4137 testing::Return()));
4138
4139 const auto result = GetParam(sock, src, Mac2String(target), "name", 0);
4140
4141 EXPECT_FALSE(result.has_value());
4142
4143 ASSERT_FALSE(sentFrame.empty());
4144
4145 const EthernetHeader ethernet = EthernetHeader::Parse(sentFrame);
4146
4147 EXPECT_EQ(ethernet.dst, target);
4148 EXPECT_EQ(ethernet.src, src);
4149 EXPECT_EQ(ethernet.type, PROFINET_ETHERTYPE);
4150
4151 const PNDCPHeader dcp = PNDCPHeader::Parse(ethernet.payload);
4152
4153 EXPECT_EQ(dcp.frameId, DcpFrameId::GetSet);
4154 EXPECT_EQ(dcp.serviceId, DcpServiceId::Get);
4155 EXPECT_EQ(dcp.serviceType, DcpServiceType::Request);
4156 EXPECT_NE(dcp.xId, 0U);
4157
4158 ASSERT_EQ(dcp.payload.size(), 4U);
4159
4160 EXPECT_EQ(dcp.payload[0], static_cast<std::uint8_t>(DcpOption::Device));
4161 EXPECT_EQ(dcp.payload[1], static_cast<std::uint8_t>(DcpDeviceSuboption::Name));
4162
4163 // GET block has no value payload.
4164 EXPECT_EQ(dcp.payload[2], 0x00);
4165 EXPECT_EQ(dcp.payload[3], 0x00);
4166}
4167} // namespace profinet::dcp
GoogleMock implementation of IRawEthernetSocket.
DCP protocol errors.
Definition exceptions.h:539
DCP operation timed out.
Definition exceptions.h:551
Input validation error.
Definition exceptions.h:627
Parsed PROFINET device information from a DCP response.
Definition dcp.h:268
std::pair< std::uint8_t, std::uint8_t > deviceInstance
Device instance (high, low) pair.
Definition dcp.h:327
bool ipConflict
Whether an IP address conflict was detected.
Definition dcp.h:297
std::string netmask
IPv4 subnet mask, or "0.0.0.0" if unset.
Definition dcp.h:288
std::string name
Station name.
Definition dcp.h:282
std::uint16_t VendorId() const
Combine VendorHigh/VendorLow into a 16-bit vendor ID.
Definition dcp.h:350
DcpIpBlockInfo ipBlockInfo
IP block info flags (see IPBlockInfo).
Definition dcp.h:294
std::uint8_t deviceRole
Raw device role bitmask.
Definition dcp.h:321
std::string gateway
IPv4 default gateway, or "0.0.0.0" if unset.
Definition dcp.h:291
std::uint16_t DeviceId() const
Combine DeviceHigh/DeviceLow into a 16-bit device ID.
Definition dcp.h:357
std::string aliasName
Device alias name, if set.
Definition dcp.h:330
std::map< BlockKey, std::vector< std::uint8_t > > rawBlocks
Any parsed blocks not covered by the named fields above.
Definition dcp.h:346
DcpDeviceInitiative deviceInitiative
Raw device-initiative value (see DeviceInitiative).
Definition dcp.h:340
std::vector< BlockKey > supportedOptions
List of (option, suboption) blocks the device supports.
Definition dcp.h:333
bool issuesHello
Whether the device issues a DCP-Hello after power-on.
Definition dcp.h:343
std::string mac
Device's MAC address, formatted as a string.
Definition dcp.h:276
std::string ip
IPv4 address, or "0.0.0.0" if unset.
Definition dcp.h:285
bool ipSetByDhcp
Whether the IP address was assigned via DHCP.
Definition dcp.h:300
std::vector< DCPDHCPBlock > dhcpBlocks
Parsed DHCP blocks from the response, if any.
Definition dcp.h:336
std::vector< std::string > deviceRoles
Decoded device role names (see DecodeDeviceRole()).
Definition dcp.h:324
std::string deviceType
Device type string (e.g. model name).
Definition dcp.h:279
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::uint16_t U16()
Read and consume a big-endian 16-bit value.
Definition wire.h:258
PROFINET DCP discovery and device configuration interface.
Declares exceptions and error types used by the PROFINET IO controller stack.
GoogleMock implementation of IRawEthernetSocket.
bool HasConflict(DcpIpBlockInfo info)
Whether an address conflict was detected.
Definition dcp.cpp:424
std::optional< std::vector< std::uint8_t > > GetParam(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string &param, int timeoutSec=DEFAULT_TIMEOUT)
Read a named parameter from a device.
Definition dcp.cpp:1005
Bytes BuildSetParamRequest(const MacAddress &dst, const MacAddress &src, const std::string &param, const std::string &value, bool permanent)
Build a serialized DCP SET request for a named parameter.
Definition dcp.cpp:883
std::string GetResetQualifierName(std::uint16_t qualifier)
Get the human-readable name for a reset qualifier value.
Definition dcp.cpp:447
void SendHello(IRawEthernetSocket &sock, const MacAddress &src, const std::string &stationName, const std::string &ip="0.0.0.0", const std::string &netmask="0.0.0.0", const std::string &gateway="0.0.0.0", std::pair< std::uint16_t, std::uint16_t > deviceId={0, 0}, DcpDeviceRole deviceRole=DcpDeviceRole::IoDevice)
Send a DCP Hello multicast announcement (device self-announce after power-on).
Definition dcp.cpp:1342
Bytes BuildDcpSignalRequest(const MacAddress &dst, const MacAddress &src)
Build a serialized DCP Signal request.
Definition dcp.cpp:937
bool SetIp(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string &ip, const std::string &netmask, const std::string &gateway, bool permanent=false, int timeoutSec=DEFAULT_TIMEOUT, int threadSleepMs=DEFAULT_THREAD_SLEEP_MS)
Set a device's IP configuration via DCP.
Definition dcp.cpp:1120
const std::map< std::string, BlockKey > & Params()
Table of supported parameter names, mapping to their (option, suboption).
Definition dcp.cpp:590
std::string DeviceRoleName(DcpDeviceRole role)
Get the human-readable name of a DCP device role.
Definition dcp.cpp:118
bool SignalDevice(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, int durationMs=FLASH_DURATION_MS, double timeoutSec=DEFAULT_TIMEOUT)
Flash a device's identification LEDs.
Definition dcp.cpp:1519
constexpr DcpBlockKey MakeBlockKey(std::uint16_t option, std::uint8_t suboption)
Definition dcp.h:231
TEST(DcpTest, DecodeDeviceRoleUnknown)
Definition test_dcp.cpp:529
DcpBlockError RecvSetResponse(IRawEthernetSocket &sock, const MacAddress &srcMac, int timeoutSec)
Receive and parse a DCP SET response, filtering out unrelated frames.
Definition dcp.cpp:253
std::string GetDeviceInitiativeName(DcpDeviceInitiative value)
Definition dcp.cpp:488
std::string GetDcpBlockQualifierDescription(DcpBlockQualifier qualifier)
Get the human-readable description of a DCP block qualifier.
Definition dcp.cpp:434
bool IsDhcp(DcpIpBlockInfo info)
Whether the address was assigned via DHCP.
Definition dcp.cpp:429
void SendRequest(IRawEthernetSocket &sock, const MacAddress &src, BlockKey blockType, const std::vector< std::uint8_t > &value)
Send a DCP Identify request filtered to a specific (option, suboption).
Definition dcp.cpp:1195
const std::string DCP_MULTICAST_MAC
DCP Identify/Get/Set multicast destination MAC.
Definition dcp.h:55
const std::string DCP_HELLO_MULTICAST_MAC
DCP Hello multicast destination MAC.
Definition dcp.h:57
std::string GetIPBlockInfoName(DcpIpBlockInfo info)
Get the human-readable name for an IP block info value.
Definition dcp.cpp:402
std::vector< std::string > DecodeDeviceRole(std::uint8_t roleByte)
Decode a device role bitmask into a list of role names.
Definition dcp.cpp:97
std::map< MacAddress, std::map< BlockKey, std::vector< std::uint8_t > > > ResponseMap
Result of ReadResponse(): MAC address -> parsed (option,suboption) -> payload blocks.
Definition dcp.h:483
std::string GetBlockName(std::uint16_t option, std::uint8_t suboption)
Get the human-readable name for a (option, suboption) DCP block.
Definition dcp.cpp:196
ResponseMap ReadResponse(IRawEthernetSocket &sock, const MacAddress &myMac, double timeoutSec=DEFAULT_RESPONSE_TIMEOUT, bool once=false, std::optional< std::uint32_t > expectedXid=std::nullopt)
Read and parse DCP Identify responses.
Definition dcp.cpp:1218
std::string GetDcpBlockErrorDescription(DcpBlockError error)
Get the human-readable name for a DCP SET block-level error code.
Definition dcp.cpp:524
std::string GetDcpResponseCodeName(DcpResponseCode value)
Get the human-readable name for a device-response value.
Definition dcp.cpp:501
bool SetParam(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string &param, const std::string &value, int timeoutSec=DEFAULT_TIMEOUT, bool permanent=false, int threadSleepMs=DEFAULT_THREAD_SLEEP_MS)
Write a named parameter to a device.
Definition dcp.cpp:1096
DcpBlockError ParseSetResponse(const Bytes &data)
Parses the raw byte response payload received from a SET operation.
Definition dcp.cpp:319
std::vector< DCPDeviceDescription > ReceiveHello(IRawEthernetSocket &sock, const MacAddress &myMac, double timeoutSec=DEFAULT_HELLO_TIMEOUT, const std::function< void(const DCPDeviceDescription &)> &callback=nullptr)
Listen for DCP Hello announcements from other devices.
Definition dcp.cpp:1424
constexpr std::size_t DCP_MAX_NAME_LENGTH
Maximum allowed length in bytes for a station name.
Definition dcp.h:60
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 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
DcpBlockQualifier
DCP block qualifier values.
Definition protocol.h:430
@ Temporary
Change applies until the next power cycle only.
@ Permanent
Change is persisted permanently.
@ IpParameter
IP address, subnet mask and gateway.
@ Initiative
Device-initiative setting.
DcpDhcpSuboption
DCP DHCP suboptions.
Definition protocol.h:222
@ Fqdn
Fully qualified domain name.
@ Unknown
Not set / initialize value.
@ ClientId
Client identifier.
@ VendorSpec
Vendor-specific option.
constexpr std::uint16_t VLAN_ETHERTYPE
EtherType value identifying an 802.1Q VLAN tag (0x8100).
Definition util.h:34
@ Signal
Signal / flash LEDs.
@ Response
Control response.
@ ResetToFactory
Reset to factory.
DcpBlockError
DCP block-level SET response errors.
Definition protocol.h:331
@ SetNotPossible
SET not possible for local reasons.
@ OptionUnsupported
Option not supported.
@ SuboptionUnsupported
Suboption not supported or no dataset available.
@ SuboptionNotSet
Suboption not set.
@ Resource
Resource error.
@ InOperation
SET not possible because the device is in operation.
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
DcpResetMode
Reset mode bitmask.
Definition protocol.h:289
@ Communication
Reset communication parameters.
@ Factory
Reset to factory defaults.
@ Engineering
Reset engineering parameters.
@ Application
Reset application data.
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
Definition util.cpp:122
DcpFrameId
PROFINET DCP frame identifiers.
Definition protocol.h:65
@ IdentifyResponse
DCP Identify response.
@ IdentifyRequest
DCP Identify request.
@ GetSet
DCP GET/SET request or response.
@ Hello
DCP Hello announcement.
DcpOption
DCP options.
Definition protocol.h:116
@ Nme
Network Management.
@ Unknown
Not set / initialize value.
@ All
Wildcard: all options.
@ DeviceInitiative
Device initiative.
@ Device
Device identification.
@ Ip
IP configuration.
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
Definition util.h:30
@ DeviceId
Vendor and device ID.
@ Instance
Device instance.
@ Options
Supported options.
DcpDeviceInitiative
DCP device-initiative values.
Definition protocol.h:321
@ NoHello
Device does not issue DCP-Hello after power-on.
@ IssueHello
Device issues DCP-Hello after power-on.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
DcpResetQualifier
Reset qualifier values for ResetToFactory().
Definition protocol.h:381
DcpServiceId
DCP service identifiers.
Definition protocol.h:84
@ Hello
HELLO service.
@ Identify
IDENTIFY service.
std::string Mac2String(const MacAddress &mac)
Format a MAC address as a colon-separated string.
Definition util.cpp:188
DcpServiceType
DCP service types.
Definition protocol.h:103
@ ResponseUnsupported
Service unsupported response.
@ ResponseSuccess
Successful response.
DcpIpBlockInfo
PROFINET DCP IP block status values. IP block status flags (IEC 61158-6-10 IPBlockInfo).
Definition protocol.h:440
@ NotSetConflict
IP address not set; address conflict detected.
@ Set
IP address set statically.
@ SetByDhcp
IP address set via DHCP.
@ SetConflict
IP address set statically; address conflict detected.
@ SetByDhcpConflict
IP address set via DHCP; address conflict detected.
@ NotSet
IP address not set.
DcpDeviceRole
Device role bitmask.
Definition protocol.h:263
@ PnSupervisor
PN-Supervisor.
@ IoController
IO-Controller.
@ IoMultidevice
IO-Multidevice.
DcpResponseCode
DCP GET/SET response codes.
Definition protocol.h:356
@ InOperationSetNotPossible
SET not possible while device is in operation.
@ SetNotPossible
SET not possible.
@ ResourceError
Resource error.
@ SuboptionNotSet
Suboption not set.
@ SuboptionNotSupported
Suboption not supported or no dataset available.
@ OptionNotSupported
Option not supported.
asio::error_code error
Identifies a PROFINET DCP block by option and suboption.
Definition protocol.h:465
std::uint8_t suboption
DCP suboption code.
Definition protocol.h:470
DcpOption option
DCP option.
Definition protocol.h:467
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
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
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
static constexpr std::uint16_t ETHER_TYPE
EtherType used by DCP frames.
Definition protocol.h:587
Bytes payload
DCP block list payload.
Definition protocol.h:609
static DCPDHCPBlock Parse(DcpDhcpSuboption suboption, const std::vector< std::uint8_t > &data)
Parse a DHCP suboption's payload into a DCPDHCPBlock.
Definition dcp.cpp:553
std::uint16_t frameId
Ethernet FrameID.
Bytes frame
std::uint8_t suboption
Definition test_dcp.cpp:390
Bytes payload
Definition test_dcp.cpp:392
std::uint16_t length
Definition test_dcp.cpp:391
DcpOption option
Definition test_dcp.cpp:389