4#include <gtest/gtest.h>
15using testing::StrictMock;
32 std::uint32_t xId = 0x12345678)
34 PNDCPBlockRequest block;
37 block.length =
static_cast<std::uint16_t
>(
payload.size());
40 const Bytes blockBytes = block.ToBytes();
48 dcp.length =
static_cast<std::uint16_t
>(blockBytes.size());
49 dcp.payload = blockBytes;
55 eth.payload = dcp.ToBytes();
70 bool shortPayload =
false)
73 static_cast<std::uint8_t
>(
error),
78 Bytes responsePayload;
86 static_cast<std::uint8_t
>(
error),
106 dcp.xId = 0x12345678;
108 dcp.length =
static_cast<std::uint16_t
>(responsePayload.size());
109 dcp.payload = responsePayload;
115 eth.payload = dcp.ToBytes();
117 return eth.ToBytes();
142 hdr.serviceId = serviceId;
143 hdr.serviceType = serviceType;
146 hdr.length =
static_cast<std::uint16_t
>(
payload.size());
153 eth.payload = hdr.ToBytes();
155 return eth.ToBytes();
170 PNDCPBlockRequest block;
173 block.length =
static_cast<std::uint16_t
>(
payload.size());
176 auto result = block.ToBytes();
178 if ((
payload.size() % 2U) != 0U)
180 result.push_back(0x00);
195Bytes BuildIdentifyResponse(
202 return BuildDcpFrame(
221Bytes BuildHelloFrame(
228 return BuildDcpFrame(
244 hdr.serviceType = serviceType;
245 hdr.xId = 0x12345678;
247 hdr.length =
static_cast<std::uint16_t
>(
payload.size());
249 return hdr.ToBytes();
256 eth.dst =
MacAddress{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
257 eth.src =
MacAddress{0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
259 eth.payload = BuildDcpPayload(serviceType,
payload);
260 return eth.ToBytes();
266 EthernetVLANHeader eth;
267 eth.dst =
MacAddress{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
268 eth.src =
MacAddress{0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
272 eth.payload = BuildDcpPayload(serviceType,
payload);
273 return eth.ToBytes();
292 std::uint16_t dcpLength,
298 dcp.serviceId = serviceId;
299 dcp.serviceType = serviceType;
302 dcp.length = dcpLength;
309 eth.payload = dcp.ToBytes();
311 return eth.ToBytes();
329 std::uint16_t dcpLength,
335 dcp.serviceType = serviceType;
338 dcp.length = dcpLength;
340 const Bytes dcpBytes = dcp.ToBytes();
342 Bytes vlanPayload{0x00, 0x00};
351 eth.payload = vlanPayload;
353 return eth.ToBytes();
366 PNDCPBlockRequest block;
369 block.length =
static_cast<std::uint16_t
>(
payload.size());
372 Bytes result = block.ToBytes();
400TestDcpBlock ParseTestDcpBlock(
const Bytes& data, std::size_t offset)
402 EXPECT_LE(offset + 4U, data.size());
403 if (offset + 4U > data.size())
409 const auto suboption = data[offset + 1U];
411 wire::Reader r(data);
412 r.SkipTo(offset + 2U);
413 const auto length = r.U16();
415 EXPECT_LE(offset + 4U +
length, data.size());
416 if (offset + 4U +
length > data.size())
424 .payload =
Bytes(data.begin() + offset + 4U,
425 data.begin() + offset + 4U +
length),
444 dcp.serviceId = serviceId;
445 dcp.serviceType = serviceType;
446 dcp.xId = 0x12345678;
448 dcp.length =
static_cast<std::uint16_t
>(
payload.size());
455 eth.payload = dcp.ToBytes();
457 return eth.ToBytes();
478 dcp.serviceId = serviceId;
479 dcp.serviceType = serviceType;
480 dcp.xId = 0x12345678;
482 dcp.length =
static_cast<std::uint16_t
>(
payload.size());
485 const Bytes dcpBytes = dcp.ToBytes();
487 Bytes vlanPayload{0x00, 0x00};
496 eth.payload = vlanPayload;
498 return eth.ToBytes();
511 PNDCPBlockRequest block;
514 block.length =
static_cast<std::uint16_t
>(
payload.size());
517 Bytes result = block.ToBytes();
521 result.push_back(0x00);
529TEST(DcpTest, DecodeDeviceRoleUnknown)
532 ASSERT_EQ(roles.size(), 1U);
533 EXPECT_EQ(roles[0],
"Unknown");
537TEST(DcpTest, DecodeDeviceRoleIoDevice)
540 ASSERT_EQ(roles.size(), 1U);
541 EXPECT_EQ(roles[0],
"IoDevice");
545TEST(DcpTest, DecodeDeviceRoleIoController)
548 ASSERT_EQ(roles.size(), 1U);
549 EXPECT_EQ(roles[0],
"IoController");
553TEST(DcpTest, DecodeDeviceRoleIoMultidevice)
556 ASSERT_EQ(roles.size(), 1U);
557 EXPECT_EQ(roles[0],
"IoMultidevice");
561TEST(DcpTest, DecodeDeviceRolePnSupervisor)
564 ASSERT_EQ(roles.size(), 1U);
565 EXPECT_EQ(roles[0],
"PnSupervisor");
569TEST(DcpTest, DecodeDeviceRoleAllRoles)
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");
580TEST(DcpTest, GetBlockNameKnownOptionAndSuboption)
586TEST(DcpTest, GetBlockNameKnownDeviceAndDhcpSuboptions)
593TEST(DcpTest, GetBlockNameKnownControlAndDeviceInitiativeSuboptions)
600TEST(DcpTest, GetBlockNameKnownOptionUnknownSuboption)
606TEST(DcpTest, GetBlockNameKnownOptionWithoutSuboptionTable)
612TEST(DcpTest, GetBlockNameVendorOption)
614 EXPECT_EQ(
GetBlockName(0x80, 0x01),
"Vendor-0x80/0x01");
615 EXPECT_EQ(
GetBlockName(0xFE, 0x01),
"Vendor-0xFE/0x01");
619TEST(DcpTest, GetBlockNameUnknownOption)
626TEST(DcpTest, IpBlockInfoGetNameDefinedValues)
637TEST(DcpTest, IpBlockInfoGetNameUnknown)
643TEST(DcpTest, IpBlockInfoHasConflict)
653TEST(DcpTest, IpBlockInfoIsDhcp)
664TEST(DcpTest, BlockQualifierGetNameTemporary)
670TEST(DcpTest, BlockQualifierGetNamePermanent)
676TEST(DcpTest, BlockQualifierGetNameUnknown)
682TEST(DcpTest, ResetQualifierApplicationData)
689TEST(DcpTest, ResetQualifierCommunicationParameter)
696TEST(DcpTest, ResetQualifierEngineeringParameter)
703TEST(DcpTest, ResetQualifierAllStoredData)
710TEST(DcpTest, ResetQualifierEngineeringParameterSecondRange)
717TEST(DcpTest, ResetQualifierFactory)
724TEST(DcpTest, ResetQualifierRestoreData)
731TEST(DcpTest, ResetQualifierUnknown)
737TEST(DcpTest, DeviceInitiativeNoHello)
743TEST(DcpTest, DeviceInitiativeIssueHello)
749TEST(DcpTest, DeviceInitiativeUnknown)
755TEST(DcpTest, DcpResponseCodeGetNameDefinedValues)
767TEST(DcpTest, DcpResponseCodeGetNameUnknown)
773TEST(DcpTest, GetDcpBlockErrorNameDefinedValues)
785TEST(DcpTest, GetDcpBlockErrorNameUnknown)
791TEST(DcpTest, DcpDhcpBlockParseHostname)
793 const std::vector<std::uint8_t> data = {
'p',
'l',
'c',
'-',
'1'};
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());
807TEST(DcpTest, DcpDhcpBlockParseClientId)
809 const std::vector<std::uint8_t> data = {0x01, 0x02, 0x03, 0x04};
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());
822TEST(DcpTest, DcpDhcpBlockParseVendorSpecific)
824 const std::vector<std::uint8_t> data = {0xAA, 0xBB, 0xCC, 0xDD};
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());
837TEST(DcpTest, DcpDhcpBlockParseFqdn)
839 const std::vector<std::uint8_t> data = {
'p',
'l',
'c',
'.',
'e',
'x',
'a',
'm',
'p',
'l',
'e'};
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());
852TEST(DcpTest, DcpDhcpBlockParseUuid)
854 const std::vector<std::uint8_t> data = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
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");
863TEST(DcpTest, DcpDhcpBlockParseUuidTooShort)
865 const std::vector<std::uint8_t> data = {0x12, 0x34, 0x56, 0x78};
867 EXPECT_EQ(block.suboptionName,
"Uuid");
868 EXPECT_EQ(block.rawData, data);
869 EXPECT_FALSE(block.uuid.has_value());
873TEST(DcpTest, DcpDhcpBlockParseUuidMinimumLength)
875 const std::vector<std::uint8_t> data(16, 0xAB);
877 ASSERT_TRUE(block.uuid.has_value());
878 EXPECT_EQ(*block.uuid,
"abababab-abab-abab-abab-abababababab");
882TEST(DcpTest, DcpDhcpBlockParseUnknownSuboption)
884 const std::vector<std::uint8_t> data = {0x10, 0x20, 0x30};
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());
897TEST(DcpTest, DcpDhcpBlockParseUnknownSuboptionEmptyPayload)
899 const std::vector<std::uint8_t> 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());
911TEST(DcpTest, DcpDhcpBlockKnownSuboptionNames)
919TEST(DcpTest, ParseSetResponseRejectsTooShortFrame)
925TEST(DcpTest, ParseSetResponseRejectsMalformedEthernetHeader)
932TEST(DcpTest, ParseSetResponseRejectsShortVlanFrame)
936 data.insert(data.end(), 11U, 0U);
943TEST(DcpTest, ParseSetResponseAcceptsVlanFrame)
949TEST(DcpTest, ParseSetResponseRejectsUnexpectedVlanInnerEtherType)
953 data.insert(data.end(), 11U, 0U);
955 data.insert(data.end(), 2U, 0U);
963TEST(DcpTest, ParseSetResponseRejectsUnexpectedEtherType)
973TEST(DcpTest, ParseSetResponseRejectsMalformedDcpHeader)
977 data.insert(data.end(), 11U, 0U);
984TEST(DcpTest, ParseSetResponseRejectsUnsupportedService)
991TEST(DcpTest, ParseSetResponseRejectsUnexpectedService)
998TEST(DcpTest, ParseSetResponseWithoutResponseBlockReturnsSuccess)
1005TEST(DcpTest, ParseSetResponseExtractsThreeByteBlockError)
1007 const auto block =
Bytes{
1021TEST(DcpTest, ParseSetResponseExtractsShortBlockError)
1023 const auto block =
Bytes{
1035TEST(DcpTest, ParseSetResponseEmptyResponseBlockReturnsSuccess)
1044TEST(DcpTest, ParseSetResponseSkipsNonResponseBlocks)
1046 const auto block =
Bytes{
1068TEST(DcpTest, ParseSetResponseUsesFirstResponseBlock)
1070 const auto block =
Bytes{
1091TEST(DcpTest, RecvSetResponseReturnsSuccessfulResponse)
1093 StrictMock<MockRawEthernetSocket> sock;
1094 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1097 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1098 EXPECT_CALL(sock, Recv()).WillOnce(Return(data));
1104TEST(DcpTest, RecvSetResponseIgnoresEmptyFrame)
1106 StrictMock<MockRawEthernetSocket> sock;
1107 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1110 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1111 EXPECT_CALL(sock, Recv())
1112 .WillOnce(Return(
Bytes{}))
1113 .WillOnce(Return(data));
1119TEST(DcpTest, RecvSetResponseIgnoresShortFrame)
1121 StrictMock<MockRawEthernetSocket> sock;
1122 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1125 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1126 EXPECT_CALL(sock, Recv())
1127 .WillOnce(Return(
Bytes(13, 0)))
1128 .WillOnce(Return(data));
1134TEST(DcpTest, RecvSetResponseIgnoresMalformedEthernetFrame)
1136 StrictMock<MockRawEthernetSocket> sock;
1137 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1138 Bytes malformed(14, 0);
1141 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1142 EXPECT_CALL(sock, Recv())
1143 .WillOnce(Return(malformed))
1144 .WillOnce(Return(data));
1150TEST(DcpTest, RecvSetResponseIgnoresWrongDestinationMac)
1152 StrictMock<MockRawEthernetSocket> sock;
1153 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1155 unrelated[0] ^= 0x01;
1158 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1159 EXPECT_CALL(sock, Recv())
1160 .WillOnce(Return(unrelated))
1161 .WillOnce(Return(data));
1167TEST(DcpTest, RecvSetResponseIgnoresShortVlanPayload)
1169 StrictMock<MockRawEthernetSocket> sock;
1170 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1175 vlan.insert(vlan.end(), 6U, 0U);
1179 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1180 EXPECT_CALL(sock, Recv())
1181 .WillOnce(Return(vlan))
1182 .WillOnce(Return(data));
1188TEST(DcpTest, RecvSetResponseIgnoresWrongVlanInnerEtherType)
1190 StrictMock<MockRawEthernetSocket> sock;
1191 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1197 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1198 EXPECT_CALL(sock, Recv())
1199 .WillOnce(Return(vlan))
1200 .WillOnce(Return(data));
1206TEST(DcpTest, RecvSetResponseIgnoresWrongEtherType)
1208 StrictMock<MockRawEthernetSocket> sock;
1209 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1211 unrelated[12] = 0x08;
1212 unrelated[13] = 0x00;
1215 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1216 EXPECT_CALL(sock, Recv())
1217 .WillOnce(Return(unrelated))
1218 .WillOnce(Return(data));
1224TEST(DcpTest, RecvSetResponseAcceptsVlanResponse)
1226 StrictMock<MockRawEthernetSocket> sock;
1227 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1230 std::ranges::copy(srcMac, data.begin());
1232 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1233 EXPECT_CALL(sock, Recv()).WillOnce(Return(data));
1239TEST(DcpTest, RecvSetResponseIgnoresUnexpectedServiceType)
1241 StrictMock<MockRawEthernetSocket> sock;
1242 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1243 const auto unrelated = BuildDcpResponse(
static_cast<DcpServiceType>(0x42), {});
1246 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1247 EXPECT_CALL(sock, Recv())
1248 .WillOnce(Return(unrelated))
1249 .WillOnce(Return(data));
1255TEST(DcpTest, RecvSetResponsePropagatesDcpError)
1257 StrictMock<MockRawEthernetSocket> sock;
1258 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1261 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1262 EXPECT_CALL(sock, Recv()).WillOnce(Return(data));
1268TEST(DcpTest, RecvSetResponseReturnsBlockError)
1270 StrictMock<MockRawEthernetSocket> sock;
1271 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1272 const auto block =
Bytes{
1283 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1284 EXPECT_CALL(sock, Recv()).WillOnce(Return(data));
1290TEST(DcpTest, RecvSetResponseTimesOut)
1292 StrictMock<MockRawEthernetSocket> sock;
1293 const MacAddress srcMac{0x01, 0x0E, 0xCF, 0x00, 0x00, 0x00};
1295 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
1296 EXPECT_CALL(sock, Recv()).WillRepeatedly(Return(
Bytes{}));
1302TEST(DcpTest, DeviceRoleNameDefinedValues)
1311TEST(DcpTest, DeviceRoleNameUnknown)
1317TEST(DcpTest, GetBlockNameUnknownSuboptionForAllTypedOptionGroups)
1327TEST(DcpTest, GetBlockNameOptionsWithoutSuboptions)
1336TEST(DcpTest, GetBlockNameUint16OptionOverload)
1338 EXPECT_EQ(
GetBlockName(
static_cast<std::uint16_t
>(0x80), 0x01),
"Vendor-0x80/0x01");
1342TEST(DcpTest, GetBlockNameDeviceInitiativeSuboption)
1348TEST(DcpTest, MakeBlockKeyRawOption)
1357TEST(DcpTest, MakeBlockKeyGenericSuboption)
1362 EXPECT_EQ(key.suboption, 0x02);
1366TEST(DcpTest, MakeBlockKeyIpSuboption)
1371 EXPECT_EQ(key.suboption, 0x01);
1375TEST(DcpTest, MakeBlockKeyDeviceSuboption)
1380 EXPECT_EQ(key.suboption, 0x04);
1384TEST(DcpTest, MakeBlockKeyControlSuboption)
1389 EXPECT_EQ(key.suboption, 0x03);
1393TEST(DcpTest, MakeBlockKeyDhcpSuboption)
1398 EXPECT_EQ(key.suboption, 0x0C);
1402TEST(DcpTest, MakeBlockKeyDeviceInitiativeSuboption)
1407 EXPECT_EQ(key.suboption, 0x01);
1416 EXPECT_EQ(
static_cast<std::uint8_t
>(role), 0x03);
1420TEST(DcpTest, DeviceRoleBitwiseAndPresent)
1429TEST(DcpTest, DeviceRoleBitwiseAndAbsent)
1443 EXPECT_EQ(
static_cast<std::uint16_t
>(mode), 0x0006);
1447TEST(DcpTest, ResetModeBitwiseAndPresent)
1455TEST(DcpTest, ResetModeBitwiseAndAbsent)
1463TEST(DcpTest, IpBlockInfoBitHelpersHandleUnknownCombinations)
1468 EXPECT_TRUE(
IsDhcp(both));
1473 EXPECT_FALSE(
IsDhcp(conflictOnly));
1478 EXPECT_TRUE(
IsDhcp(dhcpOnly));
1482TEST(DcpTest, ResetQualifierUnknownEnumValue)
1488TEST(DcpTest, ResetQualifierZeroIsUnknown)
1494TEST(DcpTest, DcpDhcpBlockParseUnknownEnumValue)
1500 EXPECT_EQ(block.suboptionName,
"0x00");
1501 EXPECT_EQ(block.rawData, (
Bytes{0x01, 0x02}));
1505TEST(DcpTest, DcpDhcpBlockParseEmptyHostname)
1509 EXPECT_EQ(block.suboptionName,
"Hostname");
1511 ASSERT_TRUE(block.hostname.has_value());
1512 EXPECT_TRUE(block.hostname->empty());
1516TEST(DcpTest, DcpDhcpBlockParseEmptyFqdn)
1520 EXPECT_EQ(block.suboptionName,
"Fqdn");
1522 ASSERT_TRUE(block.fqdn.has_value());
1523 EXPECT_TRUE(block.fqdn->empty());
1527TEST(DCPDeviceDescriptionTest, ToStringIncludesAllPopulatedFields)
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";
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";
1544 device.dhcpBlocks = {
1547 DCPDHCPBlock::Parse(
DcpDhcpSuboption::Uuid, {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}),
1549 device.rawBlocks[
MakeBlockKey(0x80, 0x01)] = {0xAA, 0xBB};
1551 EXPECT_EQ(device.ToString(),
1552 "PROFINET Device: AUMA-001\n"
1553 " MAC: 00:11:22:33:44:55\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"
1563 " Role: IO-Device, IO-Controller\n"
1565 " Alias: Valve-01\n"
1566 " Initiative: Device issues DCP-Hello after power on\n"
1567 " Supports: Ip/Mac, Dhcp/Hostname\n"
1571 " UUID: 12345678-9abc-def0-1122-334455667788\n"
1572 " ClientId: 010203\n"
1573 " Unknown (128,1): aabb");
1577TEST(DCPDeviceDescriptionTest, ToStringOmitsUnsetOptionalFields)
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";
1586 EXPECT_EQ(device.ToString(),
1587 "PROFINET Device: Device\n"
1588 " MAC: 00:11:22:33:44:55\n"
1590 " Netmask: 0.0.0.0\n"
1591 " Gateway: 0.0.0.0\n"
1592 " Vendor: Unknown (0x0000) (0x0000)\n"
1597TEST(DCPDeviceDescriptionTest, ToStringFormatsDhcpBlockVariants)
1600 device.
name =
"Device";
1601 device.dhcpBlocks = {
1604 DCPDHCPBlock::Parse(
DcpDhcpSuboption::Uuid, {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}),
1607 EXPECT_EQ(device.ToString(),
1608 "PROFINET Device: Device\n"
1609 " MAC: 00:11:22:33:44:55\n"
1611 " Netmask: 0.0.0.0\n"
1612 " Gateway: 0.0.0.0\n"
1613 " Vendor: Unknown (0x0000) (0x0000)\n"
1618 " UUID: 00112233-4455-6677-8899-aabbccddeeff\n"
1619 " VendorSpec: cafe");
1623TEST(DCPDeviceDescriptionTest, ConstructorDecodesAllSupportedBlocks)
1626 0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
1628 std::map<BlockKey, Bytes> blocks;
1631 {
'\0',
'A',
'c',
't',
'u',
'a',
't',
'o',
'r'};
1634 {
'\0',
'V',
'a',
'l',
'v',
'e',
'1'};
1654 'A',
'l',
'i',
'a',
's'};
1672 EXPECT_EQ(device.
mac,
"00:11:22:33:44:55");
1674 EXPECT_EQ(device.
name,
"Valve1");
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");
1683 EXPECT_EQ(device.
VendorId(), 0x0001);
1684 EXPECT_EQ(device.
DeviceId(), 0x0002);
1716 EXPECT_EQ(device.
rawBlocks.begin()->first.suboption, 0x01);
1720TEST(DCPDeviceDescriptionTest, ConstructorIgnoresIncompleteOptionalBlocks)
1722 std::map<BlockKey, Bytes> blocks;
1748 EXPECT_TRUE(device.
name.empty());
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");
1754 EXPECT_EQ(device.
VendorId(), 0x0000);
1755 EXPECT_EQ(device.
DeviceId(), 0x0000);
1770TEST(DCPDeviceDescriptionTest, ConstructorHandlesLegacyTwelveByteIpBlock)
1772 std::map<BlockKey, Bytes> blocks;
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");
1791TEST(DCPDeviceDescriptionTest, ConstructorUsesLegacyIpLayoutForThirteenByteBlock)
1793 std::map<BlockKey, Bytes> blocks;
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");
1811TEST(DcpTest, BuildSetParamRequestBuildsTemporaryOddLengthName)
1821 EXPECT_EQ(eth.dst, dst);
1822 EXPECT_EQ(eth.src, src);
1831 ASSERT_EQ(dcp.payload.size(), 10U);
1837 EXPECT_EQ(dcp.payload[2], 0x00);
1838 EXPECT_EQ(dcp.payload[3], 0x05);
1840 EXPECT_EQ(dcp.payload[4], 0x00);
1841 EXPECT_EQ(dcp.payload[5], 0x00);
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);
1850TEST(DcpTest, BuildSetParamRequestBuildsPermanentEvenLengthName)
1861 EXPECT_EQ(dcp.payload[4], 0x00);
1862 EXPECT_EQ(dcp.payload[5], 0x01);
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');
1869 EXPECT_EQ(dcp.length, 10);
1873TEST(DcpTest, BuildSetParamRequestRejectsIpParameter)
1884TEST(DcpTest, BuildSetParamRequestRejectsUnknownParameter)
1895TEST(DcpTest, BuildSetParamRequestRejectsOverlongStationName)
1908TEST(DcpTest, BuildSetParamRequestAcceptsMaximumStationName)
1920TEST(DcpTest, BuildDcpSignalRequestBuildsCorrectWireFormat)
1930 EXPECT_EQ(eth.dst, dst);
1931 EXPECT_EQ(eth.src, src);
1940 ASSERT_EQ(dcp.payload.size(), 8U);
1946 EXPECT_EQ(dcp.payload[2], 0x00);
1947 EXPECT_EQ(dcp.payload[3], 0x04);
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);
1956TEST(DcpTest, ParamsContainsSupportedParameters)
1958 const auto& params =
Params();
1960 ASSERT_EQ(params.size(), 2U);
1971TEST(DcpTest, GetParamThrowsForUnknownParameter)
1982TEST(DcpTest, GetParamReturnsMatchingParameter)
1990 const Bytes expected{
'a',
'u',
'm',
'a'};
1993 EXPECT_CALL(sock, SetTimeout(testing::_));
1995 EXPECT_CALL(sock, Send(testing::_))
1996 .WillOnce(testing::SaveArg<0>(&sentFrame));
1998 const Bytes response = BuildDcpResponseFrame(target, src, key.
option, key.
suboption, expected);
2000 EXPECT_CALL(sock, Recv())
2001 .WillOnce([&sentFrame, &target, &src, &key, &expected]()
2003 EXPECT_FALSE(sentFrame.empty());
2004 if (sentFrame.empty())
2012 return BuildDcpResponseFrame(
2023 ASSERT_TRUE(result.has_value());
2024 EXPECT_EQ(*result, expected);
2028TEST(DcpTest, GetParamReturnsNulloptWhenParameterIsMissing)
2035 const Bytes response = BuildDcpResponseFrame(target, src,
2041 EXPECT_CALL(sock, Send(testing::_))
2042 .WillOnce(testing::SaveArg<0>(&sentFrame));
2044 EXPECT_CALL(sock, Recv())
2045 .WillOnce([&sentFrame, &target, &src]()
2047 EXPECT_FALSE(sentFrame.empty());
2048 if (sentFrame.empty())
2056 return BuildDcpResponseFrame(
2067 EXPECT_FALSE(result.has_value());
2071TEST(DcpTest, GetParamReturnsNulloptOnTimeout)
2078 EXPECT_CALL(sock, Send(testing::_));
2082 EXPECT_FALSE(result.has_value());
2086TEST(DcpTest, SetParamReturnsTrueForSuccessfulResponse)
2095 EXPECT_CALL(sock, Send(testing::_));
2097 EXPECT_CALL(sock, Recv())
2098 .WillOnce(testing::Return(response));
2100 const auto start = std::chrono::steady_clock::now();
2102 const bool result =
SetParam(sock, src,
Mac2String(target),
"name",
"AUMA", 1,
true, 100);
2104 EXPECT_TRUE(result);
2107 const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start);
2109 EXPECT_GE(elapsed.count(), 90);
2113TEST(DcpTest, SetParamThrowsForDeviceBlockError)
2122 EXPECT_CALL(sock, SetTimeout(testing::_));
2123 EXPECT_CALL(sock, Send(testing::_));
2125 EXPECT_CALL(sock, Recv())
2126 .WillOnce(testing::Return(response));
2132TEST(DcpTest, SetParamReturnsFalseOnTimeout)
2139 EXPECT_CALL(sock, Send(testing::_));
2141 const bool result =
SetParam(sock, src,
Mac2String(target),
"name",
"AUMA", 0,
true, 100);
2143 EXPECT_FALSE(result);
2147TEST(DcpTest, SetParamBuildsTemporaryRequest)
2153 EXPECT_CALL(sock, Send(testing::_))
2154 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2155 testing::Return()));
2165 EXPECT_FALSE(result);
2166 ASSERT_FALSE(sentFrame.empty());
2167 EXPECT_GE(sentFrame.size(), 14U);
2171TEST(DcpTest, SetIpReturnsFalseOnTimeout)
2177 EXPECT_CALL(sock, Send(testing::_))
2178 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2179 testing::Return()));
2180 const bool result =
SetIp(sock,
2189 EXPECT_FALSE(result);
2190 ASSERT_FALSE(sentFrame.empty());
2191 EXPECT_GE(sentFrame.size(), 14U);
2195TEST(DcpTest, SetIpRejectsInvalidIpAddress)
2202 EXPECT_CALL(sock, Send(testing::_))
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);
2209TEST(DcpTest, SetIpEncodesPermanentQualifier)
2217 EXPECT_CALL(sock, Send(testing::_))
2218 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2219 testing::Return()));
2221 EXPECT_FALSE(
SetIp(sock,
2231 ASSERT_FALSE(sentFrame.empty());
2235 EXPECT_EQ(eth.
dst, target);
2236 EXPECT_EQ(eth.
src, src);
2245 ASSERT_GE(dcp.
payload.size(), 18U);
2252 EXPECT_EQ(dcp.
payload[2], 0x00);
2253 EXPECT_EQ(dcp.
payload[3], 0x0E);
2256 EXPECT_EQ(dcp.
payload[4], 0x00);
2257 EXPECT_EQ(dcp.
payload[5], 0x01);
2260 EXPECT_EQ(dcp.
payload[6], 192);
2261 EXPECT_EQ(dcp.
payload[7], 168);
2263 EXPECT_EQ(dcp.
payload[9], 10);
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);
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);
2279TEST(DcpTest, SetIpEncodesTemporaryQualifier)
2287 EXPECT_CALL(sock, Send(testing::_))
2288 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2289 testing::Return()));
2291 EXPECT_FALSE(
SetIp(sock,
2301 ASSERT_FALSE(sentFrame.empty());
2306 ASSERT_GE(dcp.
payload.size(), 6U);
2309 EXPECT_EQ(dcp.
payload[4], 0x00);
2310 EXPECT_EQ(dcp.
payload[5], 0x00);
2314TEST(DcpTest, SendRequestBuildsIdentifyRequest)
2325 const Bytes value{
'A',
'U',
'M',
'A'};
2329 EXPECT_CALL(sock, Send(testing::_))
2330 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2331 testing::Return()));
2335 ASSERT_FALSE(sentFrame.empty());
2340 EXPECT_EQ(eth.
src, src);
2350 ASSERT_GE(dcp.
payload.size(), 8U);
2356 EXPECT_EQ(dcp.
payload[2], 0x00);
2357 EXPECT_EQ(dcp.
payload[3], 0x04);
2363TEST(DcpTest, SetIpBuildsPermanentIpBlock)
2371 EXPECT_CALL(sock, Send(testing::_))
2372 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
2373 testing::Return()));
2375 EXPECT_FALSE(
SetIp(sock, src,
Mac2String(target),
"192.168.1.10",
"255.255.255.0",
"192.168.1.1",
true, 0, 100));
2378 EXPECT_EQ(eth.
dst, target);
2379 EXPECT_EQ(eth.
src, src);
2388 ASSERT_GE(dcp.
payload.size(), 18U);
2394 EXPECT_EQ(dcp.
payload[2], 0x00);
2395 EXPECT_EQ(dcp.
payload[3], 0x0E);
2398 EXPECT_EQ(dcp.
payload[4], 0x00);
2399 EXPECT_EQ(dcp.
payload[5], 0x01);
2409TEST(DcpTest, ReadResponseSetsExpectedSocketTimeout)
2415 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2417 const auto result =
ReadResponse(sock, myMac, 0,
true);
2418 EXPECT_TRUE(result.empty());
2422TEST(DcpTest, ReadResponseReturnsValidResponse)
2432 const Bytes expectedPayload{
'A',
'U',
'M',
'A'};
2433 const Bytes block = BuildReadResponseBlock(key.option,
2437 const Bytes frame = BuildReadResponseFrame(deviceMac,
2442 static_cast<std::uint16_t
>(block.size()));
2446 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2448 EXPECT_CALL(sock, Recv())
2449 .WillOnce(testing::Return(
frame));
2453 ASSERT_EQ(result.size(), 1U);
2455 const auto deviceIt = result.find(deviceMac);
2456 ASSERT_NE(deviceIt, result.end());
2458 const auto blockIt = deviceIt->second.find(key);
2459 ASSERT_NE(blockIt, deviceIt->second.end());
2461 EXPECT_EQ(blockIt->second, expectedPayload);
2465TEST(DcpTest, ReadResponseAcceptsAnyXidWhenExpectedXidIsNotSpecified)
2475 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, {
'A',
'U',
'M',
'A'});
2477 const Bytes frame = BuildReadResponseFrame(deviceMac,
2482 static_cast<std::uint16_t
>(block.size()));
2486 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2488 EXPECT_CALL(sock, Recv())
2489 .WillOnce(testing::Return(
frame));
2493 ASSERT_EQ(result.size(), 1U);
2494 EXPECT_EQ(result.at(deviceMac).at(key),
2495 (
Bytes{
'A',
'U',
'M',
'A'}));
2499TEST(DcpTest, ReadResponseIgnoresUnexpectedXid)
2509 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, {
'A',
'U',
'M',
'A'});
2511 const Bytes frame = BuildReadResponseFrame(deviceMac,
2516 static_cast<std::uint16_t
>(block.size()));
2520 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2522 EXPECT_CALL(sock, Recv())
2523 .WillOnce(testing::Return(
frame))
2524 .WillRepeatedly(testing::Return(
Bytes{}));
2528 EXPECT_TRUE(result.empty());
2532TEST(DcpTest, ReadResponseIgnoresEmptyData)
2538 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2540 EXPECT_CALL(sock, Recv())
2541 .WillRepeatedly(testing::Return(
Bytes{}));
2545 EXPECT_TRUE(result.empty());
2549TEST(DcpTest, ReadResponseIgnoresShortEthernetFrame)
2555 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2557 EXPECT_CALL(sock, Recv())
2558 .WillOnce(testing::Return(
Bytes(13, 0x00)))
2559 .WillRepeatedly(testing::Return(
Bytes{}));
2563 EXPECT_TRUE(result.empty());
2567TEST(DcpTest, ReadResponseIgnoresMalformedEthernetFrame)
2573 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2575 EXPECT_CALL(sock, Recv())
2576 .WillOnce(testing::Return(
Bytes(14, 0x00)))
2577 .WillRepeatedly(testing::Return(
Bytes{}));
2581 EXPECT_TRUE(result.empty());
2585TEST(DcpTest, ReadResponseIgnoresWrongDestination)
2591 const Bytes frame = BuildReadResponseFrame(deviceMac,
2600 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2602 EXPECT_CALL(sock, Recv())
2603 .WillOnce(testing::Return(
frame))
2604 .WillRepeatedly(testing::Return(
Bytes{}));
2608 EXPECT_TRUE(result.empty());
2612TEST(DcpTest, ReadResponseIgnoresShortVlanPayload)
2619 eth.
src = deviceMac;
2621 eth.
payload = {0x00, 0x00, 0x88};
2627 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2629 EXPECT_CALL(sock, Recv())
2630 .WillOnce(testing::Return(
frame))
2631 .WillRepeatedly(testing::Return(
Bytes{}));
2635 EXPECT_TRUE(result.empty());
2639TEST(DcpTest, ReadResponseIgnoresWrongVlanInnerEtherType)
2644 const Bytes frame = BuildVlanReadResponseFrame(deviceMac,
2654 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2656 EXPECT_CALL(sock, Recv())
2657 .WillOnce(testing::Return(
frame))
2658 .WillRepeatedly(testing::Return(
Bytes{}));
2662 EXPECT_TRUE(result.empty());
2666TEST(DcpTest, ReadResponseIgnoresWrongEtherType)
2673 eth.
src = deviceMac;
2675 eth.
payload = {0x01, 0x02, 0x03, 0x04};
2681 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2683 EXPECT_CALL(sock, Recv())
2684 .WillOnce(testing::Return(
frame))
2685 .WillRepeatedly(testing::Return(
Bytes{}));
2689 EXPECT_TRUE(result.empty());
2693TEST(DcpTest, ReadResponseIgnoresMalformedDcpHeader)
2700 eth.
src = deviceMac;
2702 eth.
payload = {0x01, 0x02, 0x03};
2708 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2710 EXPECT_CALL(sock, Recv())
2711 .WillOnce(testing::Return(
frame))
2712 .WillRepeatedly(testing::Return(
Bytes{}));
2716 EXPECT_TRUE(result.empty());
2720TEST(DcpTest, ReadResponseIgnoresUnsuccessfulServiceType)
2725 const Bytes frame = BuildReadResponseFrame(deviceMac,
2734 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2736 EXPECT_CALL(sock, Recv())
2737 .WillOnce(testing::Return(
frame))
2738 .WillRepeatedly(testing::Return(
Bytes{}));
2742 EXPECT_TRUE(result.empty());
2746TEST(DcpTest, ReadResponseParsesValidVlanResponse)
2756 const Bytes expected{
'A',
'U',
'M',
'A'};
2757 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, expected);
2759 const Bytes frame = BuildVlanReadResponseFrame(deviceMac,
2764 static_cast<std::uint16_t
>(block.size()));
2768 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2770 EXPECT_CALL(sock, Recv())
2771 .WillOnce(testing::Return(
frame))
2772 .WillRepeatedly(testing::Return(
Bytes{}));
2776 ASSERT_EQ(result.size(), 1U);
2777 EXPECT_EQ(result.at(deviceMac).at(key), expected);
2781TEST(DcpTest, ReadResponseUsesEmptyBlockRegionForShortDcpLength)
2791 const Bytes block = BuildReadResponseBlock(key.option,
2793 {
'A',
'U',
'M',
'A'});
2795 ASSERT_FALSE(block.empty());
2797 const Bytes frame = BuildReadResponseFrame(deviceMac,
2806 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2809 EXPECT_CALL(sock, Recv())
2810 .WillOnce(testing::Return(
frame));
2814 ASSERT_EQ(result.size(), 1U);
2816 EXPECT_TRUE(result.at(deviceMac).empty());
2820TEST(DcpTest, ReadResponseLimitsBlockRegionToDcpLength)
2835 const Bytes firstBlock = BuildReadResponseBlock(firstKey.option, firstKey.suboption, {
'A',
'U',
'M',
'A'});
2837 const Bytes secondBlock = BuildReadResponseBlock(secondKey.option, secondKey.suboption, {
'S',
'G',
'x'});
2840 payload.insert(
payload.end(), secondBlock.begin(), secondBlock.end());
2842 const Bytes frame = BuildReadResponseFrame(deviceMac,
2847 static_cast<std::uint16_t
>(firstBlock.size()));
2851 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2854 EXPECT_CALL(sock, Recv())
2855 .WillOnce(testing::Return(
frame));
2859 ASSERT_EQ(result.size(), 1U);
2861 const auto& blocks = result.at(deviceMac);
2863 ASSERT_EQ(blocks.size(), 1U);
2864 EXPECT_EQ(blocks.at(firstKey), (
Bytes{
'A',
'U',
'M',
'A'}));
2866 EXPECT_EQ(blocks.find(secondKey), blocks.end());
2870TEST(DcpTest, ReadResponseProcessesMultipleResponsesWhenOnceIsFalse)
2881 const Bytes block1 = BuildReadResponseBlock(key.option, key.suboption, {
'D',
'e',
'v',
'1'});
2883 const Bytes block2 = BuildReadResponseBlock(key.option, key.suboption, {
'D',
'e',
'v',
'2'});
2885 const Bytes frame1 = BuildReadResponseFrame(device1,
2890 static_cast<std::uint16_t
>(block1.size()));
2892 const Bytes frame2 = BuildReadResponseFrame(device2,
2897 static_cast<std::uint16_t
>(block2.size()));
2901 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2903 EXPECT_CALL(sock, Recv())
2904 .WillOnce(testing::Return(frame1))
2905 .WillOnce(testing::Return(frame2))
2906 .WillRepeatedly(testing::Return(
Bytes{}));
2910 ASSERT_EQ(result.size(), 2U);
2912 EXPECT_EQ(result.at(device1).at(key), (
Bytes{
'D',
'e',
'v',
'1'}));
2914 EXPECT_EQ(result.at(device2).at(key), (
Bytes{
'D',
'e',
'v',
'2'}));
2918TEST(DcpTest, ReadResponseStopsAfterFirstValidResponseWhenOnceIsTrue)
2928 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, {
'D',
'e',
'v',
'1'});
2930 const Bytes frame = BuildReadResponseFrame(deviceMac,
2935 static_cast<std::uint16_t
>(block.size()));
2939 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
2942 EXPECT_CALL(sock, Recv())
2943 .WillOnce(testing::Return(
frame));
2947 ASSERT_EQ(result.size(), 1U);
2948 EXPECT_EQ(result.at(deviceMac).at(key), (
Bytes{
'D',
'e',
'v',
'1'}));
2952TEST(DcpTest, ReadResponseContinuesAfterIgnoredFrames)
2963 const Bytes block = BuildReadResponseBlock(key.option, key.suboption, {
'A',
'U',
'M',
'A'});
2965 const Bytes wrongDestination = BuildReadResponseFrame(deviceMac,
2970 static_cast<std::uint16_t
>(block.size()));
2972 const Bytes unsuccessful = BuildReadResponseFrame(deviceMac,
2977 static_cast<std::uint16_t
>(block.size()));
2983 const Bytes request = BuildDcpFrame(myMac,
2991 const Bytes valid = BuildReadResponseFrame(deviceMac,
2996 static_cast<std::uint16_t
>(block.size()));
3000 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
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{}));
3013 ASSERT_EQ(result.size(), 1U);
3014 EXPECT_EQ(result.at(deviceMac).at(key), (
Bytes{
'A',
'U',
'M',
'A'}));
3018TEST(DcpTest, ReadResponseReturnsEmptyMapOnTimeout)
3024 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3028 EXPECT_TRUE(result.empty());
3032TEST(DcpTest, SendHelloBuildsCompleteHelloFrame)
3039 EXPECT_CALL(sock, Send(testing::_))
3040 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3041 testing::Return()));
3052 ASSERT_FALSE(sentFrame.empty());
3058 EXPECT_EQ(eth.
src, src);
3068 ASSERT_FALSE(dcp.
payload.empty());
3071 std::size_t offset = 0;
3074 const TestDcpBlock nameBlock = ParseTestDcpBlock(dcp.
payload, offset);
3078 EXPECT_EQ(nameBlock.length, 6);
3079 EXPECT_EQ(nameBlock.payload, (
Bytes{
'v',
'a',
'l',
'v',
'e',
'2'}));
3081 offset += 4U + nameBlock.length;
3082 EXPECT_EQ(offset % 2U, 0U);
3085 const TestDcpBlock ipBlock = ParseTestDcpBlock(dcp.
payload, offset);
3089 EXPECT_EQ(ipBlock.length, 12);
3092 EXPECT_EQ(ipBlock.payload,
3099 offset += 4U + ipBlock.length;
3102 const TestDcpBlock deviceIdBlock = ParseTestDcpBlock(dcp.
payload, offset);
3106 EXPECT_EQ(deviceIdBlock.length, 4);
3108 EXPECT_EQ(deviceIdBlock.payload, (
Bytes{0x01, 0x3F, 0x00, 0x02}));
3110 offset += 4U + deviceIdBlock.length;
3113 const TestDcpBlock roleBlock = ParseTestDcpBlock(dcp.
payload, offset);
3117 EXPECT_EQ(roleBlock.length, 2);
3119 EXPECT_EQ(roleBlock.payload, (
Bytes{
3120 static_cast<std::uint8_t>(DcpDeviceRole::IoDevice),
3124 offset += 4U + roleBlock.length;
3127 const TestDcpBlock initiativeBlock = ParseTestDcpBlock(dcp.
payload, offset);
3133 EXPECT_EQ(initiativeBlock.length, 2);
3135 EXPECT_EQ(initiativeBlock.payload, (
Bytes{
3137 static_cast<std::uint8_t>(DcpDeviceInitiative::IssueHello),
3140 offset += 4U + initiativeBlock.length;
3142 EXPECT_EQ(offset, dcp.
payload.size());
3146TEST(DcpTest, SendHelloAddsPaddingForOddLengthStationName)
3153 EXPECT_CALL(sock, Send(testing::_))
3154 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3155 testing::Return()));
3166 ASSERT_FALSE(sentFrame.empty());
3172 ASSERT_GE(dcp.
payload.size(), 10U);
3178 EXPECT_EQ(dcp.
payload[2], 0x00);
3179 EXPECT_EQ(dcp.
payload[3], 0x05);
3184 EXPECT_EQ(dcp.
payload[9], 0x00);
3191TEST(DcpTest, SendHelloEncodesCombinedDeviceRole)
3200 EXPECT_CALL(sock, Send(testing::_))
3201 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3202 testing::Return()));
3213 ASSERT_FALSE(sentFrame.empty());
3219 constexpr std::size_t roleOffset = 4U + 6U +
3223 ASSERT_GE(dcp.
payload.size(), roleOffset + 6U);
3229 EXPECT_EQ(dcp.
payload[roleOffset + 2U], 0x00);
3230 EXPECT_EQ(dcp.
payload[roleOffset + 3U], 0x02);
3232 EXPECT_EQ(dcp.
payload[roleOffset + 4U],
static_cast<std::uint8_t
>(role));
3234 EXPECT_EQ(dcp.
payload[roleOffset + 5U], 0x00);
3238TEST(DcpTest, SendHelloEncodesDeviceId)
3245 EXPECT_CALL(sock, Send(testing::_))
3246 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3247 testing::Return()));
3258 ASSERT_FALSE(sentFrame.empty());
3263 std::size_t offset = 0;
3265 const TestDcpBlock nameBlock = ParseTestDcpBlock(dcp.
payload, offset);
3267 offset += 4U + nameBlock.length;
3269 if (nameBlock.length % 2U != 0U)
3274 const TestDcpBlock ipBlock = ParseTestDcpBlock(dcp.
payload, offset);
3276 offset += 4U + ipBlock.length;
3278 const TestDcpBlock deviceIdBlock = ParseTestDcpBlock(dcp.
payload, offset);
3280 EXPECT_EQ(deviceIdBlock.payload, (
Bytes{0x12, 0x34, 0xAB, 0xCD}));
3284TEST(DcpTest, SendHelloRejectsInvalidIpAddress)
3290 EXPECT_CALL(sock, Send(testing::_))
3305TEST(DcpTest, SendHelloRejectsInvalidNetmask)
3311 EXPECT_CALL(sock, Send(testing::_))
3326TEST(DcpTest, SendHelloRejectsInvalidGateway)
3332 EXPECT_CALL(sock, Send(testing::_))
3347TEST(DcpTest, ReceiveHelloSetsExpectedSocketTimeout)
3353 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3357 EXPECT_TRUE(devices.empty());
3361TEST(DcpTest, ReceiveHelloReturnsValidDevice)
3370 {
'v',
'a',
'l',
'v',
'e',
'2'});
3380 {0x01, 0x3F, 0x00, 0x02});
3389 payload.insert(
payload.end(), nameBlock.begin(), nameBlock.end());
3391 payload.insert(
payload.end(), deviceIdBlock.begin(), deviceIdBlock.end());
3392 payload.insert(
payload.end(), roleBlock.begin(), roleBlock.end());
3394 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3402 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3404 EXPECT_CALL(sock, Recv())
3405 .WillOnce(testing::Return(
frame))
3406 .WillRepeatedly(testing::Return(
Bytes{}));
3408 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3410 ASSERT_EQ(devices.size(), 1U);
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);
3420 ASSERT_EQ(devices.front().deviceRoles.size(), 1U);
3421 EXPECT_EQ(devices.front().deviceRoles.front(),
"IoDevice");
3425TEST(DcpTest, ReceiveHelloInvokesCallbackForValidDevice)
3432 {
'd',
'e',
'v',
'1'});
3434 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3442 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3444 EXPECT_CALL(sock, Recv())
3445 .WillOnce(testing::Return(
frame))
3446 .WillRepeatedly(testing::Return(
Bytes{}));
3448 bool callbackCalled =
false;
3453 callbackCalled =
true;
3457 EXPECT_EQ(device.
name,
"dev1");
3460 ASSERT_EQ(devices.size(), 1U);
3461 EXPECT_TRUE(callbackCalled);
3462 ASSERT_NE(callbackDevice,
nullptr);
3466TEST(DcpTest, ReceiveHelloDoesNotInvokeEmptyCallback)
3473 {
'd',
'e',
'v',
'1'});
3475 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3483 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3485 EXPECT_CALL(sock, Recv())
3486 .WillOnce(testing::Return(
frame))
3487 .WillRepeatedly(testing::Return(
Bytes{}));
3489 const auto devices =
ReceiveHello(sock, myMac, 0.01,
nullptr);
3491 ASSERT_EQ(devices.size(), 1U);
3495TEST(DcpTest, ReceiveHelloParsesValidVlanHello)
3502 {
'v',
'a',
'l',
'v',
'e'});
3504 const Bytes frame = BuildVlanReceiveHelloFrame(deviceMac,
3512 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3514 EXPECT_CALL(sock, Recv())
3515 .WillOnce(testing::Return(
frame))
3516 .WillRepeatedly(testing::Return(
Bytes{}));
3518 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3520 ASSERT_EQ(devices.size(), 1U);
3521 EXPECT_EQ(devices.front().mac,
Mac2String(deviceMac));
3522 EXPECT_EQ(devices.front().name,
"valve");
3526TEST(DcpTest, ReceiveHelloIgnoresEmptyData)
3532 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3534 EXPECT_CALL(sock, Recv())
3535 .WillOnce(testing::Return(
Bytes{}))
3536 .WillRepeatedly(testing::Return(
Bytes{}));
3538 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3540 EXPECT_TRUE(devices.empty());
3544TEST(DcpTest, ReceiveHelloIgnoresShortEthernetFrame)
3550 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3552 EXPECT_CALL(sock, Recv())
3553 .WillOnce(testing::Return(
Bytes(13, 0x00)))
3554 .WillRepeatedly(testing::Return(
Bytes{}));
3556 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3558 EXPECT_TRUE(devices.empty());
3562TEST(DcpTest, ReceiveHelloIgnoresMalformedEthernetFrame)
3568 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3570 EXPECT_CALL(sock, Recv())
3571 .WillOnce(testing::Return(
Bytes(14, 0x00)))
3572 .WillRepeatedly(testing::Return(
Bytes{}));
3574 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3576 EXPECT_TRUE(devices.empty());
3580TEST(DcpTest, ReceiveHelloIgnoresShortVlanPayload)
3587 eth.
src = deviceMac;
3589 eth.
payload = {0x00, 0x00, 0x88};
3595 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3597 EXPECT_CALL(sock, Recv())
3598 .WillOnce(testing::Return(
frame))
3599 .WillRepeatedly(testing::Return(
Bytes{}));
3601 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3603 EXPECT_TRUE(devices.empty());
3607TEST(DcpTest, ReceiveHelloIgnoresWrongVlanInnerEtherType)
3612 const Bytes frame = BuildVlanReceiveHelloFrame(deviceMac,
3621 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3623 EXPECT_CALL(sock, Recv())
3624 .WillOnce(testing::Return(
frame))
3625 .WillRepeatedly(testing::Return(
Bytes{}));
3627 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3629 EXPECT_TRUE(devices.empty());
3633TEST(DcpTest, ReceiveHelloIgnoresWrongEtherType)
3640 eth.
src = deviceMac;
3648 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3650 EXPECT_CALL(sock, Recv())
3651 .WillOnce(testing::Return(
frame))
3652 .WillRepeatedly(testing::Return(
Bytes{}));
3654 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3656 EXPECT_TRUE(devices.empty());
3660TEST(DcpTest, ReceiveHelloIgnoresShortPnioPayload)
3667 eth.
src = deviceMac;
3675 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3677 EXPECT_CALL(sock, Recv())
3678 .WillOnce(testing::Return(
frame))
3679 .WillRepeatedly(testing::Return(
Bytes{}));
3681 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3683 EXPECT_TRUE(devices.empty());
3687TEST(DcpTest, ReceiveHelloIgnoresMalformedDcpHeader)
3694 eth.
src = deviceMac;
3702 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3704 EXPECT_CALL(sock, Recv())
3705 .WillOnce(testing::Return(
frame))
3706 .WillRepeatedly(testing::Return(
Bytes{}));
3708 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3710 EXPECT_TRUE(devices.empty());
3714TEST(DcpTest, ReceiveHelloIgnoresNonHelloServiceId)
3723 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3731 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3733 EXPECT_CALL(sock, Recv())
3734 .WillOnce(testing::Return(
frame))
3735 .WillRepeatedly(testing::Return(
Bytes{}));
3737 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3739 EXPECT_TRUE(devices.empty());
3743TEST(DcpTest, ReceiveHelloIgnoresNonRequestServiceType)
3752 const Bytes frame = BuildReceiveHelloFrame(deviceMac,
3760 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3762 EXPECT_CALL(sock, Recv())
3763 .WillOnce(testing::Return(
frame))
3764 .WillRepeatedly(testing::Return(
Bytes{}));
3766 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3768 EXPECT_TRUE(devices.empty());
3772TEST(DcpTest, ReceiveHelloContinuesAfterIgnoredFrame)
3777 const Bytes invalidFrame = BuildReceiveHelloFrame(deviceMac,
3785 {
'v',
'a',
'l',
'i',
'd'});
3787 const Bytes validFrame = BuildReceiveHelloFrame(deviceMac,
3795 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3797 EXPECT_CALL(sock, Recv())
3798 .WillOnce(testing::Return(invalidFrame))
3799 .WillOnce(testing::Return(validFrame))
3800 .WillRepeatedly(testing::Return(
Bytes{}));
3802 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3804 ASSERT_EQ(devices.size(), 1U);
3805 EXPECT_EQ(devices.front().name,
"valid");
3809TEST(DcpTest, ReceiveHelloReturnsMultipleDevices)
3817 {
'd',
'e',
'v',
'1'});
3821 {
'd',
'e',
'v',
'2'});
3823 const Bytes frame1 = BuildReceiveHelloFrame(device1,
3829 const Bytes frame2 = BuildReceiveHelloFrame(device2,
3837 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3839 EXPECT_CALL(sock, Recv())
3840 .WillOnce(testing::Return(frame1))
3841 .WillOnce(testing::Return(frame2))
3842 .WillRepeatedly(testing::Return(
Bytes{}));
3844 const auto devices =
ReceiveHello(sock, myMac, 0.01, {});
3846 ASSERT_EQ(devices.size(), 2U);
3848 EXPECT_EQ(devices[0].mac,
Mac2String(device1));
3849 EXPECT_EQ(devices[0].name,
"dev1");
3851 EXPECT_EQ(devices[1].mac,
Mac2String(device2));
3852 EXPECT_EQ(devices[1].name,
"dev2");
3856TEST(DcpTest, ReceiveHelloReturnsEmptyOnTimeout)
3862 EXPECT_CALL(sock, SetTimeout(std::chrono::milliseconds(2000)));
3866 EXPECT_TRUE(devices.empty());
3870TEST(DcpTest, SignalDeviceReturnsTrueForSuccessfulResponse)
3880 EXPECT_CALL(sock, Send(testing::_))
3881 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3882 testing::Return()));
3884 EXPECT_CALL(sock, Recv())
3885 .WillOnce(testing::Return(response));
3889 EXPECT_TRUE(result);
3891 ASSERT_FALSE(sentFrame.empty());
3895 EXPECT_EQ(eth.
dst, target);
3896 EXPECT_EQ(eth.
src, src);
3907TEST(DcpTest, SignalDeviceThrowsForBlockError)
3916 EXPECT_CALL(sock, Send(testing::_));
3918 EXPECT_CALL(sock, Recv())
3919 .WillOnce(testing::Return(response));
3926TEST(DcpTest, SignalDeviceReturnsFalseOnTimeout)
3934 EXPECT_CALL(sock, Send(testing::_))
3935 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3936 testing::Return()));
3940 EXPECT_FALSE(result);
3942 ASSERT_FALSE(sentFrame.empty());
3946 EXPECT_EQ(eth.
dst, target);
3947 EXPECT_EQ(eth.
src, src);
3952TEST(DcpTest, SignalDeviceRejectsInvalidTargetMac)
3958 EXPECT_CALL(sock, Send(testing::_))
3961 EXPECT_THROW(
SignalDevice(sock, src,
"not-a-mac-address", 3000, 0.0),
3966TEST(DcpTest, ResetToFactoryReturnsTrueForSuccessfulResponse)
3976 EXPECT_CALL(sock, Send(testing::_))
3977 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
3978 testing::Return()));
3980 EXPECT_CALL(sock, Recv())
3981 .WillOnce(testing::Return(response));
3983 const auto start = std::chrono::steady_clock::now();
3987 EXPECT_TRUE(result);
3990 const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start);
3992 EXPECT_GE(elapsed.count(), 90);
3994 ASSERT_FALSE(sentFrame.empty());
3998 EXPECT_EQ(eth.
dst, target);
3999 EXPECT_EQ(eth.
src, src);
4009 ASSERT_GE(dcp.
payload.size(), 6U);
4016 EXPECT_EQ(dcp.
payload[2], 0x00);
4017 EXPECT_EQ(dcp.
payload[3], 0x02);
4020 EXPECT_EQ(dcp.
payload[4], 0x00);
4021 EXPECT_EQ(dcp.
payload[5], 0x02);
4023 EXPECT_EQ(dcp.
length, 6U);
4027TEST(DcpTest, ResetToFactoryEncodesCombinedResetMode)
4039 EXPECT_CALL(sock, Send(testing::_))
4040 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
4041 testing::Return()));
4045 EXPECT_FALSE(result);
4047 ASSERT_FALSE(sentFrame.empty());
4053 ASSERT_GE(dcp.
payload.size(), 6U);
4057 const auto encodedMode = r.
U16();
4059 EXPECT_EQ(encodedMode,
static_cast<std::uint16_t
>(mode));
4064 EXPECT_EQ(encodedMode, 0x000E);
4068TEST(DcpTest, ResetToFactoryReturnsFalseOnTimeout)
4076 EXPECT_CALL(sock, Send(testing::_))
4077 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
4078 testing::Return()));
4082 EXPECT_FALSE(result);
4084 ASSERT_FALSE(sentFrame.empty());
4088 EXPECT_EQ(eth.
dst, target);
4089 EXPECT_EQ(eth.
src, src);
4094TEST(DcpTest, ResetToFactoryThrowsForBlockError)
4103 EXPECT_CALL(sock, Send(testing::_));
4105 EXPECT_CALL(sock, Recv())
4106 .WillOnce(testing::Return(response));
4113TEST(DcpTest, ResetToFactoryRejectsInvalidTargetMac)
4119 EXPECT_CALL(sock, Send(testing::_))
4127TEST(DcpTest, GetParamSendsCorrectGetRequest)
4135 EXPECT_CALL(sock, Send(testing::_))
4136 .WillOnce(testing::DoAll(testing::SaveArg<0>(&sentFrame),
4137 testing::Return()));
4141 EXPECT_FALSE(result.has_value());
4143 ASSERT_FALSE(sentFrame.empty());
4147 EXPECT_EQ(ethernet.
dst, target);
4148 EXPECT_EQ(ethernet.
src, src);
4156 EXPECT_NE(dcp.
xId, 0U);
4158 ASSERT_EQ(dcp.
payload.size(), 4U);
4164 EXPECT_EQ(dcp.
payload[2], 0x00);
4165 EXPECT_EQ(dcp.
payload[3], 0x00);
GoogleMock implementation of IRawEthernetSocket.
Parsed PROFINET device information from a DCP response.
std::pair< std::uint8_t, std::uint8_t > deviceInstance
Device instance (high, low) pair.
bool ipConflict
Whether an IP address conflict was detected.
std::string netmask
IPv4 subnet mask, or "0.0.0.0" if unset.
std::string name
Station name.
std::uint16_t VendorId() const
Combine VendorHigh/VendorLow into a 16-bit vendor ID.
DcpIpBlockInfo ipBlockInfo
IP block info flags (see IPBlockInfo).
std::uint8_t deviceRole
Raw device role bitmask.
std::string gateway
IPv4 default gateway, or "0.0.0.0" if unset.
std::uint16_t DeviceId() const
Combine DeviceHigh/DeviceLow into a 16-bit device ID.
std::string aliasName
Device alias name, if set.
std::map< BlockKey, std::vector< std::uint8_t > > rawBlocks
Any parsed blocks not covered by the named fields above.
DcpDeviceInitiative deviceInitiative
Raw device-initiative value (see DeviceInitiative).
std::vector< BlockKey > supportedOptions
List of (option, suboption) blocks the device supports.
bool issuesHello
Whether the device issues a DCP-Hello after power-on.
std::string mac
Device's MAC address, formatted as a string.
std::string ip
IPv4 address, or "0.0.0.0" if unset.
bool ipSetByDhcp
Whether the IP address was assigned via DHCP.
std::vector< DCPDHCPBlock > dhcpBlocks
Parsed DHCP blocks from the response, if any.
std::vector< std::string > deviceRoles
Decoded device role names (see DecodeDeviceRole()).
std::string deviceType
Device type string (e.g. model name).
Lightweight cursor for parsing a big-endian byte buffer without copies.
void SkipTo(std::size_t offset)
Move the read position to an absolute byte offset.
std::uint16_t U16()
Read and consume a big-endian 16-bit value.
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.
std::optional< std::vector< std::uint8_t > > GetParam(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string ¶m, int timeoutSec=DEFAULT_TIMEOUT)
Read a named parameter from a device.
Bytes BuildSetParamRequest(const MacAddress &dst, const MacAddress &src, const std::string ¶m, const std::string &value, bool permanent)
Build a serialized DCP SET request for a named parameter.
std::string GetResetQualifierName(std::uint16_t qualifier)
Get the human-readable name for a reset qualifier value.
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).
Bytes BuildDcpSignalRequest(const MacAddress &dst, const MacAddress &src)
Build a serialized DCP Signal request.
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.
const std::map< std::string, BlockKey > & Params()
Table of supported parameter names, mapping to their (option, suboption).
std::string DeviceRoleName(DcpDeviceRole role)
Get the human-readable name of a DCP device role.
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.
constexpr DcpBlockKey MakeBlockKey(std::uint16_t option, std::uint8_t suboption)
TEST(DcpTest, DecodeDeviceRoleUnknown)
DcpBlockError RecvSetResponse(IRawEthernetSocket &sock, const MacAddress &srcMac, int timeoutSec)
Receive and parse a DCP SET response, filtering out unrelated frames.
std::string GetDeviceInitiativeName(DcpDeviceInitiative value)
std::string GetDcpBlockQualifierDescription(DcpBlockQualifier qualifier)
Get the human-readable description of a DCP block qualifier.
bool IsDhcp(DcpIpBlockInfo info)
Whether the address was assigned via DHCP.
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).
const std::string DCP_MULTICAST_MAC
DCP Identify/Get/Set multicast destination MAC.
const std::string DCP_HELLO_MULTICAST_MAC
DCP Hello multicast destination MAC.
std::string GetIPBlockInfoName(DcpIpBlockInfo info)
Get the human-readable name for an IP block info value.
std::vector< std::string > DecodeDeviceRole(std::uint8_t roleByte)
Decode a device role bitmask into a list of role names.
std::map< MacAddress, std::map< BlockKey, std::vector< std::uint8_t > > > ResponseMap
Result of ReadResponse(): MAC address -> parsed (option,suboption) -> payload blocks.
std::string GetBlockName(std::uint16_t option, std::uint8_t suboption)
Get the human-readable name for a (option, suboption) DCP block.
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.
std::string GetDcpBlockErrorDescription(DcpBlockError error)
Get the human-readable name for a DCP SET block-level error code.
std::string GetDcpResponseCodeName(DcpResponseCode value)
Get the human-readable name for a device-response value.
bool SetParam(IRawEthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string ¶m, const std::string &value, int timeoutSec=DEFAULT_TIMEOUT, bool permanent=false, int threadSleepMs=DEFAULT_THREAD_SLEEP_MS)
Write a named parameter to a device.
DcpBlockError ParseSetResponse(const Bytes &data)
Parses the raw byte response payload received from a SET operation.
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.
constexpr std::size_t DCP_MAX_NAME_LENGTH
Maximum allowed length in bytes for a station name.
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.
void PutBigEndian(std::vector< std::uint8_t > &out, T value)
Append an unsigned integral value to a buffer in big-endian order.
DcpBlockQualifier
DCP block qualifier values.
@ 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.
@ 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).
@ Signal
Signal / flash LEDs.
@ Response
Control response.
@ ResetToFactory
Reset to factory.
DcpBlockError
DCP block-level SET response errors.
@ 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.
DcpResetMode
Reset mode bitmask.
@ 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.
DcpFrameId
PROFINET DCP frame identifiers.
@ IdentifyResponse
DCP Identify response.
@ IdentifyRequest
DCP Identify request.
@ GetSet
DCP GET/SET request or response.
@ Hello
DCP Hello announcement.
@ Unknown
Not set / initialize value.
@ All
Wildcard: all options.
@ DeviceInitiative
Device initiative.
@ Device
Device identification.
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
@ DeviceId
Vendor and device ID.
@ Instance
Device instance.
@ Options
Supported options.
DcpDeviceInitiative
DCP device-initiative values.
@ 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.
DcpResetQualifier
Reset qualifier values for ResetToFactory().
DcpServiceId
DCP service identifiers.
@ Identify
IDENTIFY service.
std::string Mac2String(const MacAddress &mac)
Format a MAC address as a colon-separated string.
DcpServiceType
DCP service types.
@ ResponseUnsupported
Service unsupported response.
@ ResponseSuccess
Successful response.
DcpIpBlockInfo
PROFINET DCP IP block status values. IP block status flags (IEC 61158-6-10 IPBlockInfo).
@ 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.
@ PnSupervisor
PN-Supervisor.
@ IoController
IO-Controller.
@ IoMultidevice
IO-Multidevice.
DcpResponseCode
DCP GET/SET response codes.
@ 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.
Identifies a PROFINET DCP block by option and suboption.
std::uint8_t suboption
DCP suboption code.
DcpOption option
DCP option.
static DCPDHCPBlock Parse(DcpDhcpSuboption suboption, const std::vector< std::uint8_t > &data)
Parse a DHCP suboption's payload into a DCPDHCPBlock.
std::uint16_t frameId
Ethernet FrameID.