PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
dcp.cpp
Go to the documentation of this file.
1
30
31#include "profinet/dcp.h"
32
33#include <algorithm>
34#include <cstdio>
35#include <cstring>
36#include <map>
37#include <random>
38#include <thread>
39
40#include "profinet/exceptions.h"
41#include "profinet/protocol.h"
42#include "profinet/vendors.h"
43
44namespace profinet::dcp
45{
46
47namespace
48{
49
50std::uint32_t GenerateXid()
51{
52 static thread_local std::mt19937 rng{std::random_device{}()};
53 static constexpr std::size_t maxXid = 0xFFFFFFFFULL;
54 static thread_local std::uniform_int_distribution<std::uint64_t> dist(0, maxXid);
55 return static_cast<std::uint32_t>(dist(rng));
56}
57
58// name -> (option, suboption)
59const std::map<std::uint8_t, std::string>& OptionNames()
60{
61 static const std::map<std::uint8_t, std::string> table = {
62 {0x01, "IP"},
63 {0x02, "Device"},
64 {0x03, "DHCP"},
65 {0x04, "Reserved"},
66 {0x05, "Control"},
67 {0x06, "DeviceInitiative"},
68 {0x07, "NME"},
69 {0xFF, "All"},
70 };
71 return table;
72}
73
74const std::map<std::uint8_t, std::map<std::uint8_t, std::string>>& SuboptionNames()
75{
76 static const std::map<std::uint8_t, std::map<std::uint8_t, std::string>> table = {
77 {0x01, {{0x01, "MAC"}, {0x02, "IP"}, {0x03, "FullIPSuite"}}},
78 {0x02,
79 {{0x01, "Type"}, {0x02, "Name"}, {0x03, "DeviceID"}, {0x04, "Role"}, {0x05, "Options"}, {0x06, "Alias"}, {0x07, "Instance"}, {0x08, "OEM-ID"}, {0x0A, "RSI"}}},
80 {0x03,
81 {{0x0C, "Hostname"}, {0x2B, "VendorSpec"}, {0x36, "ServerID"}, {0x37, "ParamReq"}, {0x3C, "ClassID"}, {0x3D, "ClientID"}, {0x51, "FQDN"}, {0x61, "UUID"}, {0xFF, "Control"}}},
82 {0x05,
83 {{0x01, "Start"}, {0x02, "Stop"}, {0x03, "Signal"}, {0x04, "Response"}, {0x05, "FactoryReset"}, {0x06, "ResetToFactory"}}},
84 {0x06, {{0x01, "Initiative"}}},
85 };
86 return table;
87}
88
89const std::map<std::uint16_t, std::string>& DeviceRoleNames()
90{
91 static const std::map<std::uint16_t, std::string> table = {
92 {DEVICE_ROLE_IO_DEVICE, "IO-Device"},
93 {DEVICE_ROLE_IO_CONTROLLER, "IO-Controller"},
94 {DEVICE_ROLE_IO_MULTIDEVICE, "IO-Multidevice"},
95 {DEVICE_ROLE_PN_SUPERVISOR, "PN-Supervisor"},
96 };
97 return table;
98}
99
100const std::map<std::uint8_t, std::string>& DcpBlockErrorNames()
101{
102 static const std::map<std::uint8_t, std::string> table = {
103 {DCP_BLOCK_ERROR_OK, "OK"},
104 {DCP_BLOCK_ERROR_OPTION_UNSUPPORTED, "Option not supported"},
105 {DCP_BLOCK_ERROR_SUBOPTION_UNSUPPORTED, "Suboption not supported or no dataset available"},
106 {DCP_BLOCK_ERROR_SUBOPTION_NOT_SET, "Suboption not set"},
107 {DCP_BLOCK_ERROR_RESOURCE, "Resource error"},
108 {DCP_BLOCK_ERROR_SET_NOT_POSSIBLE, "SET not possible by local reasons"},
109 {DCP_BLOCK_ERROR_IN_OPERATION, "In operation, SET not possible"},
110 };
111 return table;
112}
113
116template <typename Fn>
117void ForEachBlock(const std::vector<std::uint8_t>& data, Fn&& fn)
118{
119 std::size_t offset = 0;
120 while (offset + 4 <= data.size())
121 {
122 const std::uint8_t option = data[offset];
123 const std::uint8_t suboption = data[offset + 1];
124 const auto length = static_cast<std::uint16_t>((data[offset + 2] << OneOctetShift) | data[offset + 3]);
125 const std::size_t payloadStart = offset + 4;
126 const std::size_t payloadEnd = std::min(payloadStart + length, data.size());
127 const std::vector<std::uint8_t> payload(data.begin() + payloadStart, data.begin() + payloadEnd);
128
129 fn(option, suboption, payload);
130
131 std::size_t blockLen = 4 + length;
132 if (length % 2 == 1)
133 {
134 blockLen += 1;
135 }
136 if (blockLen == 0)
137 {
138 break;
139 }
140 offset += blockLen;
141 }
142}
143
144} // namespace
145
146std::vector<std::string> DecodeDeviceRole(std::uint8_t roleByte)
147{
148 std::vector<std::string> roles;
149 for (const auto& [mask, Name] : DeviceRoleNames())
150 {
151 if ((roleByte & mask) != 0)
152 {
153 roles.push_back(Name);
154 }
155 }
156 if (roles.empty())
157 {
158 roles.emplace_back("Unknown");
159 }
160 return roles;
161}
162
163std::string GetBlockName(std::uint8_t option, std::uint8_t suboption)
164{
165 std::string optName;
166 auto oit = OptionNames().find(option);
167 if (oit != OptionNames().end())
168 {
169 optName = oit->second;
170 }
171 else if (option >= 0x80 && option <= 0xFE)
172 {
173 optName = "Vendor-" + Hex2(option);
174 }
175 else
176 {
177 optName = "Opt-" + Hex2(option);
178 }
179
180 std::string suboptName = Hex2(suboption);
181 auto sit = SuboptionNames().find(option);
182 if (sit != SuboptionNames().end())
183 {
184 auto ssit = sit->second.find(suboption);
185 if (ssit != sit->second.end())
186 {
187 suboptName = ssit->second;
188 }
189 }
190
191 return optName + "/" + suboptName;
192}
193
195std::uint8_t RecvSetResponse(const IRawEthernetSocket& sock, const MacAddress& srcMac, int timeoutSec)
196{
197 sock.SetTimeout(std::chrono::milliseconds(2000));
198 const MaxTimeout timer{std::chrono::duration<double>(timeoutSec)};
199 while (!timer.TimedOut())
200 {
201 const Bytes data = sock.Recv();
202 if (data.empty() || data.size() < 14)
203 {
204 continue;
205 }
206
207 EthernetHeader eth;
208 try
209 {
210 eth = EthernetHeader::Parse(data);
211 }
212 catch (const std::exception&)
213 {
214 continue;
215 }
216
217 if (eth.dst != srcMac)
218 {
219 continue;
220 }
221
222 Bytes payload = eth.payload;
223 if (eth.type == VLAN_ETHERTYPE)
224 {
225 if (payload.size() < 4)
226 {
227 continue;
228 }
229 const auto innerType = static_cast<std::uint16_t>((payload[2] << OneOctetShift) | payload[3]);
230 if (innerType != PROFINET_ETHERTYPE)
231 {
232 continue;
233 }
234 }
235 else if (eth.type != PROFINET_ETHERTYPE)
236 {
237 continue;
238 }
239
240 try
241 {
242 return ParseSetResponse(data);
243 }
244 catch (const DCPError& e)
245 {
246 if (std::string(e.what()).find("unexpected service_type") != std::string::npos)
247 {
248 continue;
249 }
250 throw;
251 }
252 }
253 throw DCPTimeoutError("No DCP SET response received");
254}
255
258std::uint8_t ParseSetResponse(const Bytes& data)
259{
260 if (data.size() < 14)
261 {
262 throw DCPError("DCP SET response too short");
263 }
264
265 EthernetHeader eth;
266 try
267 {
268 eth = EthernetHeader::Parse(data);
269 }
270 catch (const std::exception& e)
271 {
272 throw DCPError(std::string("Failed to parse Ethernet header: ") + e.what());
273 }
274
275 Bytes payload = eth.payload;
276 if (eth.type == VLAN_ETHERTYPE)
277 {
278 if (payload.size() < 4)
279 {
280 throw DCPError("VLAN frame too short");
281 }
282 const auto innerType = static_cast<std::uint16_t>((payload[2] << OneOctetShift) | payload[3]);
283 if (innerType != PROFINET_ETHERTYPE)
284 {
285 throw DCPError("Unexpected inner EtherType: " + Hex4(innerType));
286 }
287 payload = Bytes(payload.begin() + 4, payload.end());
288 }
289 else if (eth.type != PROFINET_ETHERTYPE)
290 {
291 throw DCPError("Unexpected EtherType: " + Hex4(eth.type));
292 }
293
294 PNDCPHeader hdr;
295 try
296 {
298 }
299 catch (const std::exception& e)
300 {
301 throw DCPError(std::string("Failed to parse DCP header: ") + e.what());
302 }
303
305 {
306 throw DCPError("DCP SET: service not supported by device");
307 }
309 {
310 throw DCPError("DCP SET: unexpected service_type " + Hex2(hdr.serviceType));
311 }
312
313 std::uint8_t result = DCP_BLOCK_ERROR_OK;
314 bool found = false;
315 ForEachBlock(hdr.payload, [&](std::uint8_t option, std::uint8_t suboption, const Bytes& blockPayload)
316 {
317 if (found)
318 {
319 return;
320 }
321 if (option == DCP_OPTION_CONTROL && suboption == DCP_SUBOPTION_CONTROL_RESPONSE)
322 {
323 found = true;
324 if (blockPayload.size() >= 3)
325 {
326 result = blockPayload[2];
327 }
328 else if (!blockPayload.empty())
329 {
330 result = blockPayload[0];
331 }
332 }
333 });
334 // No Control/Response block found - some devices omit it; treat as success.
335 return result;
336}
337
338std::string IPBlockInfo::GetName(std::uint16_t info)
339{
340 switch (info)
341 {
342 case IP_NOT_SET:
343 return "IP not set";
344 case IP_SET:
345 return "IP set";
346 case IP_SET_BY_DHCP:
347 return "IP set by DHCP";
348 case IP_NOT_SET_CONFLICT:
349 return "IP not set (address conflict detected)";
350 case IP_SET_CONFLICT:
351 return "IP set (address conflict detected)";
352 case IP_SET_BY_DHCP_CONFLICT:
353 return "IP set by DHCP (address conflict detected)";
354 default:
355 return "Unknown (" + Hex4(info) + ")";
356 }
357}
358
359std::string BlockQualifier::GetName(std::uint16_t qualifier)
360{
361 return qualifier == PERMANENT ? "Permanent" : (qualifier == TEMPORARY ? "Temporary" : "Unknown (" + Hex4(qualifier) + ")");
362}
363
364std::string ResetQualifier::GetName(std::uint16_t qualifier)
365{
366 switch (qualifier)
367 {
368 case 0x0002:
369 case 0x0003:
370 return "Reset application data";
371 case 0x0004:
372 case 0x0005:
373 return "Reset communication parameter";
374 case 0x0006:
375 case 0x0007:
376 return "Reset engineering parameter";
377 case 0x0008:
378 case 0x0009:
379 return "Reset all stored data";
380 case 0x000A:
381 case 0x000B:
382 return "Reset engineering parameter";
383 case 0x0010:
384 case 0x0011:
385 return "Reset to factory values";
386 case 0x0012:
387 case 0x0013:
388 return "Reset and restore data";
389 default:
390 return "Unknown (" + Hex4(qualifier) + ")";
391 }
392}
393
394std::string DeviceInitiative::GetName(std::uint16_t value)
395{
396 if (value == NO_HELLO)
397 {
398 return "Device does not issue DCP-Hello after power on";
399 }
400 if (value == ISSUE_HELLO)
401 {
402 return "Device issues DCP-Hello after power on";
403 }
404 return "Unknown (" + Hex4(value) + ")";
405}
406
407std::string DCPResponseCode::GetName(std::uint8_t code)
408{
409 switch (code)
410 {
411 case NO_ERROR:
412 return "No error";
413 case OPTION_NOT_SUPPORTED:
414 return "Option not supported";
415 case SUBOPTION_NOT_SUPPORTED:
416 return "Suboption not supported or no DataSet available";
417 case SUBOPTION_NOT_SET:
418 return "Suboption not set";
419 case RESOURCE_ERROR:
420 return "Resource error";
421 case SET_NOT_POSSIBLE:
422 return "Set not possible";
423 case IN_OPERATION_SET_NOT_POSSIBLE:
424 return "In operation, SET not possible";
425 default:
426 return "Unknown (" + Hex2(code) + ")";
427 }
428}
429
430std::string DcpBlockErrorName(std::uint8_t code)
431{
432 auto it = DcpBlockErrorNames().find(code);
433 if (it != DcpBlockErrorNames().end())
434 {
435 return it->second;
436 }
437 return "Unknown error (" + Hex2(code) + ")";
438}
439
440DCPDHCPBlock DCPDHCPBlock::Parse(std::uint8_t suboption, const std::vector<std::uint8_t>& data)
441{
442 DCPDHCPBlock block;
443 block.suboption = suboption;
444 auto sit = SuboptionNames().find(DCP_OPTION_DHCP);
445 if (sit != SuboptionNames().end())
446 {
447 auto ssit = sit->second.find(suboption);
448 block.suboptionName = ssit != sit->second.end() ? ssit->second : Hex2(suboption);
449 }
450 else
451 {
452 block.suboptionName = Hex2(suboption);
453 }
454 block.rawData = data;
455
456 if (suboption == DCP_SUBOPTION_DHCP_HOSTNAME)
457 {
458 block.hostname = DecodeBytes(data);
459 }
460 else if (suboption == DCP_SUBOPTION_DHCP_CLIENT_ID)
461 {
462 block.clientId = data;
463 }
464 else if (suboption == DCP_SUBOPTION_DHCP_VENDOR_SPEC)
465 {
466 block.vendorSpecific = data;
467 }
468 else if (suboption == DCP_SUBOPTION_DHCP_FQDN)
469 {
470 block.fqdn = DecodeBytes(data);
471 }
472 else if (suboption == DCP_SUBOPTION_DHCP_UUID && data.size() >= uuidLenght)
473 {
474 block.uuid = ToHex(&data[0], 4) + "-" + ToHex(&data[4], 2) + "-" + ToHex(&data[6], 2) + "-" +
475 ToHex(&data[8], 2) + "-" + ToHex(&data[10], 6);
476 }
477 return block;
478}
479
480const std::map<std::string, BlockKey>& Params()
481{
482 static const std::map<std::string, BlockKey> table = {
483 {"name", PNDCPBlock::NAME_OF_STATION},
484 {"ip", PNDCPBlock::IP_ADDRESS},
485 };
486 return table;
487}
488
489// =============================================================================
490// DCPDeviceDescription
491// =============================================================================
492
493DCPDeviceDescription::DCPDeviceDescription(const MacAddress& macBytes,
494 const std::map<BlockKey, std::vector<std::uint8_t>>& blocks)
495{
496 mac = Mac2String(macBytes);
497
498 auto get = [&](BlockKey key) -> const std::vector<std::uint8_t>*
499 {
500 auto it = blocks.find(key);
501 return it != blocks.end() ? &it->second : nullptr;
502 };
503
504 if (const auto* typeBlock = get(PNDCPBlock::DEVICE_TYPE))
505 {
506 // DeviceType = DecodeBytes(*type_block);
507 // Zoek de eerste byte die NIET '\0' is
508 auto firstNonNull = std::find_if(typeBlock->begin(), typeBlock->end(), [](char c)
509 {
510 return c != '\0';
511 });
512
513 // Maak de string vanaf dat punt tot het einde
514 const std::string deviceTypeString(firstNonNull, typeBlock->end());
515 this->deviceType = deviceTypeString;
516 }
517 if (const auto* nameBlock = get(PNDCPBlock::NAME_OF_STATION))
518 {
519 // Name = std::string(name_block->begin(), name_block->end());
520
521 // Zoek de eerste byte die NIET '\0' is
522 auto firstNonNull = std::find_if(nameBlock->begin(), nameBlock->end(), [](char c)
523 {
524 return c != '\0';
525 });
526
527 // Maak de string vanaf dat punt tot het einde
528 const std::string name(firstNonNull, nameBlock->end());
529 this->name = name;
530 }
531
532 if (const auto* ipBlock = get(PNDCPBlock::IP_ADDRESS); (ipBlock != nullptr) && ipBlock->size() >= 12)
533 {
534 ip = String2Ip(&(*ipBlock)[0], 4);
535 netmask = String2Ip(&(*ipBlock)[4], 4);
536 gateway = String2Ip(&(*ipBlock)[8], 4);
537 if (ipBlock->size() >= 14)
538 {
539 ipBlockInfo = static_cast<std::uint16_t>(((*ipBlock)[0] << OneOctetShift) | (*ipBlock)[1]);
540 ipConflict = IPBlockInfo::HasConflict(ipBlockInfo);
541 ipSetByDhcp = IPBlockInfo::IsDhcp(ipBlockInfo);
542 ip = String2Ip(&(*ipBlock)[2], 4);
543 netmask = String2Ip(&(*ipBlock)[6], 4);
544 gateway = String2Ip(&(*ipBlock)[10], 4);
545 }
546 }
547
548 if (const auto* devId = get(PNDCPBlock::DEVICE_ID); devId && devId->size() >= 4)
549 {
550 vendorHigh = (*devId)[devId->size() - 4];
551 vendorLow = (*devId)[devId->size() - 3];
552 deviceHigh = (*devId)[devId->size() - 2];
553 deviceLow = (*devId)[devId->size() - 1];
554 }
555
556 if (const auto* roleBlock = get(PNDCPBlock::DEVICE_ROLE); roleBlock && !roleBlock->empty())
557 {
558 this->deviceRole = (*roleBlock)[0];
559 this->deviceRoles = DecodeDeviceRole(this->deviceRole);
560 }
561
562 if (const auto* inst = get({DCP_OPTION_DEVICE, DCP_SUBOPTION_DEVICE_INSTANCE}); inst && inst->size() >= 2)
563 {
564 deviceInstance = {(*inst)[0], (*inst)[1]};
565 }
566
567 if (const auto* alias = get({DCP_OPTION_DEVICE, DCP_SUBOPTION_DEVICE_ALIAS}))
568 {
569 aliasName = DecodeBytes(*alias);
570 }
571
572 if (const auto* opts = get(PNDCPBlock::DEVICE_OPTIONS); opts && opts->size() >= 2)
573 {
574 for (std::size_t i = 0; i + 1 < opts->size(); i += 2)
575 {
576 supportedOptions.emplace_back((*opts)[i], (*opts)[i + 1]);
577 }
578 }
579
580 for (const auto& [key, Data] : blocks)
581 {
582 if (key.first == DCP_OPTION_DHCP)
583 {
584 dhcpBlocks.push_back(DCPDHCPBlock::Parse(key.second, Data));
585 }
586 }
587
589 init && init->size() >= 2)
590 {
591 deviceInitiative = static_cast<std::uint16_t>(((*init)[0] << OneOctetShift) | (*init)[1]);
592 issuesHello = deviceInitiative == DeviceInitiative::ISSUE_HELLO;
593 }
594
595 static const std::vector<BlockKey> known = {
596 PNDCPBlock::IP_ADDRESS,
597 PNDCPBlock::DEVICE_TYPE,
598 PNDCPBlock::NAME_OF_STATION,
599 PNDCPBlock::DEVICE_ID,
600 PNDCPBlock::DEVICE_ROLE,
601 PNDCPBlock::DEVICE_OPTIONS,
602 PNDCPBlock::DEVICE_ALIAS,
603 PNDCPBlock::DEVICE_INSTANCE,
607 };
608 for (const auto& [key, value] : blocks)
609 {
610 if (key.first == DCP_OPTION_DHCP)
611 {
612 continue;
613 }
614 if (std::find(known.begin(), known.end(), key) == known.end())
615 {
616 rawBlocks[key] = value;
617 }
618 }
619}
620
621std::string DCPDeviceDescription::VendorName() const
622{
623 return GetVendorName(VendorId());
624}
625
626std::string DCPDeviceDescription::ToString() const
627{
628 std::string out = "PROFINET Device: " + name + "\n";
629 out += " MAC: " + mac + "\n";
630 if (!deviceType.empty())
631 {
632 out += " Type: " + deviceType + "\n";
633 }
634 out += " IP: " + ip + "\n";
635 out += " Netmask: " + netmask + "\n";
636 out += " Gateway: " + gateway + "\n";
637 if (ipBlockInfo != 0u)
638 {
639 out += " IP Info: " + IPBlockInfo::GetName(ipBlockInfo) + "\n";
640 }
641 if (ipConflict)
642 {
643 out += " Warning: IP address conflict detected\n";
644 }
645 if (ipSetByDhcp)
646 {
647 out += " IP Source: DHCP\n";
648 }
649 out += " Vendor: " + VendorName() + " (" + Hex4(VendorId()) + ")\n";
650 out += " Device: " + Hex4(DeviceId()) + "\n";
651 if (!deviceRoles.empty())
652 {
653 out += " Role: ";
654 for (std::size_t i = 0; i < deviceRoles.size(); ++i)
655 {
656 out += deviceRoles[i];
657 if (i + 1 < deviceRoles.size())
658 {
659 out += ", ";
660 }
661 }
662 out += "\n";
663 }
664 if (deviceInstance != std::pair<std::uint8_t, std::uint8_t>{0, 0})
665 {
666 out += " Instance: " + std::to_string(deviceInstance.first) + "." +
667 std::to_string(deviceInstance.second) + "\n";
668 }
669 if (!aliasName.empty())
670 {
671 out += " Alias: " + aliasName + "\n";
672 }
673 if (deviceInitiative != 0u)
674 {
675 out += " Initiative: " + DeviceInitiative::GetName(deviceInitiative) + "\n";
676 }
677 if (!supportedOptions.empty())
678 {
679 out += " Supports: ";
680 for (std::size_t i = 0; i < supportedOptions.size(); ++i)
681 {
682 out += GetBlockName(supportedOptions[i].first, supportedOptions[i].second);
683 if (i + 1 < supportedOptions.size())
684 {
685 out += ", ";
686 }
687 }
688 out += "\n";
689 }
690 if (!dhcpBlocks.empty())
691 {
692 out += " DHCP:\n";
693 for (const auto& b : dhcpBlocks)
694 {
695 if (b.hostname)
696 {
697 out += " Hostname: " + *b.hostname + "\n";
698 }
699 else if (b.fqdn)
700 {
701 out += " FQDN: " + *b.fqdn + "\n";
702 }
703 else if (b.uuid)
704 {
705 out += " UUID: " + *b.uuid + "\n";
706 }
707 else
708 {
709 out += " " + b.suboptionName + ": " + ToHex(b.rawData) + "\n";
710 }
711 }
712 }
713 for (const auto& [key, Data] : rawBlocks)
714 {
715 out += " Unknown (" + std::to_string(key.first) + "," + std::to_string(key.second) +
716 "): " + ToHex(Data) + "\n";
717 }
718 if (!out.empty() && out.back() == '\n')
719 {
720 out.pop_back();
721 }
722 return out;
723}
724
725// =============================================================================
726// Frame builders / parsers (internal helpers)
727// =============================================================================
728
729namespace
730{
731
732Bytes BuildEthernetDcp(const MacAddress& dst, const MacAddress& src, const PNDCPHeader& dcp)
733{
734 EthernetHeader eth;
735 eth.dst = dst;
736 eth.src = src;
738 eth.payload = dcp.ToBytes();
739 return eth.ToBytes();
740}
741
742bool EqualsIcase(std::string_view lhs, std::string_view rhs)
743{
744 return std::ranges::equal(lhs, rhs, [](char a, char b)
745 {
746 return std::tolower(static_cast<unsigned char>(a)) ==
747 std::tolower(static_cast<unsigned char>(b));
748 });
749}
750
751} // namespace
752
753// =============================================================================
754// Public operations
755// =============================================================================
757 const MacAddress& dst,
758 const MacAddress& src,
759 const std::string& param,
760 const std::string& value,
761 bool permanent)
762{
763 if (EqualsIcase(param, "ip"))
764 {
765 // The IP suite must be sent as 12 binary bytes(IP + mask + gateway),
766 // not an ASCII string; an ASCII value fails the device's length
767 // check and is never applied.
768 throw DCPError("Use SetIp() to configure the IP address");
769 }
770 auto it = Params().find(param);
771 if (it == Params().end())
772 {
773 throw DCPError("Unknown parameter: '" + param + "'");
774 }
775 if (param == "name" && value.size() > DCP_MAX_NAME_LENGTH)
776 {
777 throw ValidationError("Station name exceeds maximum length: " + std::to_string(value.size()) +
778 " > " + std::to_string(DCP_MAX_NAME_LENGTH));
779 }
780 const BlockKey key = it->second;
781 Bytes valueBytes = ToVec(value);
782 const std::uint32_t xid = GenerateXid();
783 const std::uint16_t qualifier = permanent ? 0x0001 : 0x0000;
784
785 PNDCPBlockRequest block;
786 block.option = key.first;
787 block.suboption = key.second;
788 block.length = static_cast<std::uint16_t>(valueBytes.size() + 2);
789 block.payload = {static_cast<std::uint8_t>((qualifier >> OneOctetShift) & LowByteMask),
790 static_cast<std::uint8_t>(qualifier & LowByteMask)};
791 block.payload.insert(block.payload.end(), valueBytes.begin(), valueBytes.end());
792
793 // const std::uint16_t padding = valueBytes.size() % 2 == 1 ? 1 : 0;
794
795 auto blockData = block.ToBytes();
796 if (valueBytes.size() % 2 == 1)
797 {
798 blockData.push_back(0x00);
799 }
800
801 PNDCPHeader dcp;
803 dcp.serviceId = PNDCPHeader::SET;
804 dcp.serviceType = PNDCPHeader::REQUEST;
805 dcp.xId = xid;
806 dcp.respDelay = 0;
807 dcp.length = static_cast<std::uint16_t>(blockData.size());
808 dcp.payload = blockData;
809 return BuildEthernetDcp(dst, src, dcp);
810}
811
813{
814 const std::uint32_t xid = GenerateXid();
815
816 /*
817 const int durationUnits = std::max(1, durationMs / 100);
818 const Bytes blockData = {0x00, 0x01, static_cast<std::uint8_t>((durationUnits >> OneOctetShift) & LowByteMask),
819 static_cast<std::uint8_t>(durationUnits & LowByteMask)};
820*/
821 // Signal block data: BlockQualifier 0x0000 + SignalValue 0x0100
822 // ("flash once") - the only value defined by IEC 61158-6-10
823 const Bytes blockData = {0x00, 0x00, 0x01, 0x00};
824 PNDCPBlockRequest block;
827 block.length = static_cast<std::uint16_t>(blockData.size());
828 block.payload = blockData;
829
830 PNDCPHeader dcp;
832 dcp.serviceId = PNDCPHeader::SET;
833 dcp.serviceType = PNDCPHeader::REQUEST;
834 dcp.xId = xid;
835 dcp.respDelay = 0;
836 dcp.length = static_cast<std::uint16_t>(blockData.size() + 4);
837 dcp.payload = block.ToBytes();
838 return BuildEthernetDcp(
839 dst,
840 src,
841 dcp);
842}
843/*
844Bytes BuildSignalRequest(
845 const MacAddress& dst,
846 const MacAddress& src)
847{
848 constexpr Bytes signalBlockData{
849 0x00,
850 0x00,
851 0x01,
852 0x00};
853
854 const std::uint32_t xid = GenerateXid();
855
856 PNDCPBlockRequest block;
857 block.option = DCP_OPTION_CONTROL;
858 block.suboption = DCP_SUBOPTION_CONTROL_SIGNAL;
859 block.length =
860 static_cast<std::uint16_t>(
861 signalBlockData.size());
862 block.payload = signalBlockData;
863
864 PNDCPHeader dcp;
865 dcp.frameId = DCP_GET_SET_FRAME_ID;
866 dcp.serviceId = PNDCPHeader::SET;
867 dcp.serviceType = PNDCPHeader::REQUEST;
868 dcp.xId = xid;
869 dcp.respDelay = 0;
870 dcp.length =
871 static_cast<std::uint16_t>(
872 signalBlockData.size() + 4U);
873 dcp.payload = block.ToBytes();
874
875 return BuildEthernetDcp(
876 dst,
877 src,
878 dcp);
879}*/
880std::optional<Bytes> GetParam(const EthernetSocket& sock, const MacAddress& src, const std::string& target,
881 const std::string& param, int timeoutSec)
882{
883 auto it = Params().find(param);
884 if (it == Params().end())
885 {
886 throw DCPError("Unknown parameter: '" + param + "'");
887 }
888
889 const MacAddress dst = String2Mac(target);
890 const BlockKey key = it->second;
891 const std::uint32_t xid = GenerateXid();
892
893 PNDCPBlockRequest block;
894 block.option = key.first;
895 block.suboption = key.second;
896 block.length = 0;
897
898 PNDCPHeader dcp;
900 dcp.serviceId = PNDCPHeader::GET;
901 dcp.serviceType = PNDCPHeader::REQUEST;
902 dcp.xId = xid;
903 dcp.respDelay = 0;
904 dcp.length = 2;
905 dcp.payload = block.ToBytes();
906
907 sock.Send(BuildEthernetDcp(dst, src, dcp));
908
909 auto responses = ReadResponse(sock, src, timeoutSec, /*once=*/true);
910 if (!responses.empty())
911 {
912 const auto& blocks = responses.begin()->second;
913 auto bit = blocks.find(key);
914 if (bit != blocks.end())
915 {
916 return bit->second;
917 }
918 }
919 return std::nullopt;
920}
921
922bool SetParam(const EthernetSocket& sock, const MacAddress& src, const std::string& target,
923 const std::string& param, const std::string& value, int timeoutSec, bool permanent)
924{
925 const MacAddress dst = String2Mac(target);
926
927 const Bytes frame = BuildSetParamRequest(dst,
928 src,
929 param,
930 value,
931 permanent);
932 sock.Send(frame);
933
934 try
935 {
936 const std::uint8_t blockError = RecvSetResponse(sock, src, timeoutSec);
937 if (blockError != DCP_BLOCK_ERROR_OK)
938 {
939 throw DCPError("DCP SET failed for '" + param + "': " + DcpBlockErrorName(blockError));
940 }
941 std::this_thread::sleep_for(std::chrono::seconds(2));
942 return true;
943 }
944 catch (const DCPTimeoutError&)
945 {
946 return false;
947 }
948}
949
950bool SetIp(const EthernetSocket& sock, const MacAddress& src, const std::string& target, const std::string& ip,
951 const std::string& netmask, const std::string& gateway, bool permanent, int timeoutSec)
952{
953 const MacAddress dst = String2Mac(target);
954 const std::uint32_t xid = GenerateXid();
955
956 auto ipBytes = Ip2String(ip);
957 auto netmaskBytes = Ip2String(netmask);
958 auto gatewayBytes = Ip2String(gateway);
959
960 Bytes valueBytes;
961 valueBytes.insert(valueBytes.end(), ipBytes.begin(), ipBytes.end());
962 valueBytes.insert(valueBytes.end(), netmaskBytes.begin(), netmaskBytes.end());
963 valueBytes.insert(valueBytes.end(), gatewayBytes.begin(), gatewayBytes.end());
964
965 const std::uint16_t qualifier = permanent ? BlockQualifier::PERMANENT : BlockQualifier::TEMPORARY;
966
967 PNDCPBlockRequest block;
968 block.option = PNDCPBlock::IP_ADDRESS.first;
969 block.suboption = PNDCPBlock::IP_ADDRESS.second;
970 block.length = static_cast<std::uint16_t>(valueBytes.size() + 2);
971 block.payload = {static_cast<std::uint8_t>(qualifier >> OneOctetShift), static_cast<std::uint8_t>(qualifier & LowByteMask)};
972 block.payload.insert(block.payload.end(), valueBytes.begin(), valueBytes.end());
973
974 const std::uint16_t padding = valueBytes.size() % 2 == 0 ? 0 : 1;
975
976 PNDCPHeader dcp;
978 dcp.serviceId = PNDCPHeader::SET;
979 dcp.serviceType = PNDCPHeader::REQUEST;
980 dcp.xId = xid;
981 dcp.respDelay = 0;
982 dcp.length = static_cast<std::uint16_t>(valueBytes.size() + 6 + padding);
983 dcp.payload = block.ToBytes();
984
985 sock.Send(BuildEthernetDcp(dst, src, dcp));
986
987 try
988 {
989 const std::uint8_t blockError = RecvSetResponse(sock, src, timeoutSec);
990 if (blockError != DCP_BLOCK_ERROR_OK)
991 {
992 throw DCPError("DCP SET IP failed: " + DcpBlockErrorName(blockError));
993 }
994 std::this_thread::sleep_for(std::chrono::seconds(2));
995 return true;
996 }
997 catch (const DCPTimeoutError&)
998 {
999 return false;
1000 }
1001}
1002
1003void SendDiscover(const EthernetSocket& sock, const MacAddress& src, std::uint16_t responseDelay)
1004{
1005 const std::uint32_t xid = GenerateXid();
1006
1007 PNDCPBlockRequest block;
1008 block.option = 0xFF;
1009 block.suboption = 0xFF;
1010 block.length = 0;
1011
1012 PNDCPHeader dcp;
1014 dcp.serviceId = PNDCPHeader::IDENTIFY;
1015 dcp.serviceType = PNDCPHeader::REQUEST;
1016 dcp.xId = xid;
1017 dcp.respDelay = responseDelay;
1018 const Bytes blockBytes = block.ToBytes();
1019 dcp.length = static_cast<std::uint16_t>(blockBytes.size());
1020 dcp.payload = blockBytes;
1021
1022 sock.Send(BuildEthernetDcp(String2Mac(DCP_MULTICAST_MAC), src, dcp));
1023}
1024
1025void SendRequest(const EthernetSocket& sock, const MacAddress& src, BlockKey blockType, const Bytes& value)
1026{
1027 const std::uint32_t xid = GenerateXid();
1028
1029 PNDCPBlockRequest block;
1030 block.option = blockType.first;
1031 block.suboption = blockType.second;
1032 block.length = static_cast<std::uint16_t>(value.size());
1033 block.payload = value;
1034
1035 PNDCPHeader dcp;
1037 dcp.serviceId = PNDCPHeader::IDENTIFY;
1038 dcp.serviceType = PNDCPHeader::REQUEST;
1039 dcp.xId = xid;
1040 dcp.respDelay = 0x0080;
1041 const Bytes blockBytes = block.ToBytes();
1042 dcp.length = static_cast<std::uint16_t>(blockBytes.size());
1043 dcp.payload = blockBytes;
1044
1045 sock.Send(BuildEthernetDcp(String2Mac(DCP_MULTICAST_MAC), src, dcp));
1046}
1047
1048ResponseMap ReadResponse(const EthernetSocket& sock, const MacAddress& myMac, int timeoutSec, bool once,
1049 std::optional<std::uint32_t> expectedXid)
1050{
1051 ResponseMap result;
1052 sock.SetTimeout(std::chrono::milliseconds(2000));
1053 const MaxTimeout timer{std::chrono::duration<double>(timeoutSec)};
1054
1055 while (!timer.TimedOut())
1056 {
1057 const Bytes data = sock.Recv();
1058 if (data.empty() || data.size() < 14)
1059 {
1060 continue;
1061 }
1062
1063 EthernetHeader eth;
1064 try
1065 {
1066 eth = EthernetHeader::Parse(data);
1067 }
1068 catch (const std::exception&)
1069 {
1070 continue;
1071 }
1072
1073 if (eth.dst != myMac)
1074 {
1075 continue;
1076 }
1077
1078 Bytes payload = eth.payload;
1079 if (eth.type == VLAN_ETHERTYPE)
1080 {
1081 if (payload.size() < 4)
1082 {
1083 continue;
1084 }
1085 const auto innerType = static_cast<std::uint16_t>((payload[2] << OneOctetShift) | payload[3]);
1086 if (innerType != PROFINET_ETHERTYPE)
1087 {
1088 continue;
1089 }
1090 payload = Bytes(payload.begin() + 4, payload.end());
1091 }
1092 else if (eth.type != PROFINET_ETHERTYPE)
1093 {
1094 continue;
1095 }
1096
1097 PNDCPHeader dcp;
1098 try
1099 {
1100 dcp = PNDCPHeader::Parse(payload);
1101 }
1102 catch (const std::exception&)
1103 {
1104 continue;
1105 }
1106
1107 if (dcp.serviceType != PNDCPHeader::RESPONSE)
1108 {
1109 continue;
1110 }
1111 if (expectedXid && dcp.xId != *expectedXid)
1112 {
1113 continue;
1114 }
1115
1116 std::map<BlockKey, Bytes> parsed;
1117 // dcp.length includes the whole block-list; each block ends 2-byte aligned.
1118 const std::size_t consumedLimit = dcp.length > 6 ? static_cast<std::size_t>(dcp.length) : 0;
1119 const Bytes blocksRegion = dcp.payload.size() > consumedLimit ? Bytes(dcp.payload.begin(), dcp.payload.begin() + consumedLimit) : dcp.payload;
1120 ForEachBlock(blocksRegion, [&](std::uint8_t option, std::uint8_t suboption, const Bytes& blockPayload)
1121 {
1122 parsed[{option, suboption}] = blockPayload;
1123 });
1124
1125 result[eth.src] = std::move(parsed);
1126 if (once)
1127 {
1128 break;
1129 }
1130 }
1131
1132 return result;
1133}
1134
1135void SendHello(const EthernetSocket& sock, const MacAddress& src, const std::string& stationName,
1136 const std::string& ip, const std::string& netmask, const std::string& gateway,
1137 std::pair<std::uint16_t, std::uint16_t> deviceId, std::uint8_t deviceRole)
1138{
1139 const std::uint32_t xid = GenerateXid();
1140 Bytes blocksData;
1141
1142 // Name of Station block
1143 const Bytes nameBytes = ToVec(stationName);
1144 PNDCPBlockRequest nameBlock;
1145 nameBlock.option = DCP_OPTION_DEVICE;
1147 nameBlock.length = static_cast<std::uint16_t>(nameBytes.size());
1148 nameBlock.payload = nameBytes;
1149 Bytes nameBlockBytes = nameBlock.ToBytes();
1150 blocksData.insert(blocksData.end(), nameBlockBytes.begin(), nameBlockBytes.end());
1151 if (nameBytes.size() % 2 == 1)
1152 {
1153 blocksData.push_back(0x00);
1154 }
1155
1156 // IP block
1157 auto ipB = Ip2String(ip);
1158 auto nmB = Ip2String(netmask);
1159 auto gwB = Ip2String(gateway);
1160 Bytes ipPayload;
1161 ipPayload.insert(ipPayload.end(), ipB.begin(), ipB.end());
1162 ipPayload.insert(ipPayload.end(), nmB.begin(), nmB.end());
1163 ipPayload.insert(ipPayload.end(), gwB.begin(), gwB.end());
1164 PNDCPBlockRequest ipBlock;
1165 ipBlock.option = DCP_OPTION_IP;
1167 ipBlock.length = static_cast<std::uint16_t>(ipPayload.size());
1168 ipBlock.payload = ipPayload;
1169 Bytes ipBlockBytes = ipBlock.ToBytes();
1170 blocksData.insert(blocksData.end(), ipBlockBytes.begin(), ipBlockBytes.end());
1171
1172 // Device ID block
1173 const Bytes deviceIdBytes = {
1174 static_cast<std::uint8_t>(deviceId.first >> OneOctetShift), static_cast<std::uint8_t>(deviceId.first & LowByteMask),
1175 static_cast<std::uint8_t>(deviceId.second >> OneOctetShift), static_cast<std::uint8_t>(deviceId.second & LowByteMask)};
1176 PNDCPBlockRequest deviceIdBlock;
1177 deviceIdBlock.option = DCP_OPTION_DEVICE;
1178 deviceIdBlock.suboption = DCP_SUBOPTION_DEVICE_ID;
1179 deviceIdBlock.length = static_cast<std::uint16_t>(deviceIdBytes.size());
1180 deviceIdBlock.payload = deviceIdBytes;
1181 Bytes deviceIdBlockBytes = deviceIdBlock.ToBytes();
1182 blocksData.insert(blocksData.end(), deviceIdBlockBytes.begin(), deviceIdBlockBytes.end());
1183
1184 // Device Role block
1185 PNDCPBlockRequest roleBlock;
1186 roleBlock.option = DCP_OPTION_DEVICE;
1188 roleBlock.length = 2;
1189 roleBlock.payload = {deviceRole, 0x00};
1190 Bytes roleBlockBytes = roleBlock.ToBytes();
1191 blocksData.insert(blocksData.end(), roleBlockBytes.begin(), roleBlockBytes.end());
1192
1193 // Device Initiative block
1194 PNDCPBlockRequest initBlock;
1197 initBlock.length = 2;
1198 initBlock.payload = {0x00, static_cast<std::uint8_t>(DeviceInitiative::ISSUE_HELLO)};
1199 Bytes initBlockBytes = initBlock.ToBytes();
1200 blocksData.insert(blocksData.end(), initBlockBytes.begin(), initBlockBytes.end());
1201
1202 PNDCPHeader dcp;
1204 dcp.serviceId = PNDCPHeader::HELLO;
1205 dcp.serviceType = PNDCPHeader::REQUEST;
1206 dcp.xId = xid;
1207 dcp.respDelay = 0;
1208 dcp.length = static_cast<std::uint16_t>(blocksData.size());
1209 dcp.payload = blocksData;
1210
1211 sock.Send(BuildEthernetDcp(String2Mac(DCP_HELLO_MULTICAST_MAC), src, dcp));
1212}
1213
1214std::vector<DCPDeviceDescription> ReceiveHello(const EthernetSocket& sock, const MacAddress& /*myMac*/,
1215 int timeoutSec,
1216 const std::function<void(const DCPDeviceDescription&)>& callback)
1217{
1218 std::vector<DCPDeviceDescription> devices;
1219 sock.SetTimeout(std::chrono::milliseconds(2000));
1220 const MaxTimeout timer{std::chrono::duration<double>(timeoutSec)};
1221
1222 while (!timer.TimedOut())
1223 {
1224 const Bytes data = sock.Recv();
1225 if (data.empty() || data.size() < 14)
1226 {
1227 continue;
1228 }
1229
1230 EthernetHeader eth;
1231 try
1232 {
1233 eth = EthernetHeader::Parse(data);
1234 }
1235 catch (const std::exception&)
1236 {
1237 continue;
1238 }
1239
1240 Bytes payload = eth.payload;
1241 if (eth.type == VLAN_ETHERTYPE)
1242 {
1243 if (payload.size() < 4)
1244 {
1245 continue;
1246 }
1247 const auto innerType = static_cast<std::uint16_t>((payload[2] << OneOctetShift) | payload[3]);
1248 if (innerType != PROFINET_ETHERTYPE)
1249 {
1250 continue;
1251 }
1252 payload = Bytes(payload.begin() + 4, payload.end());
1253 }
1254 else if (eth.type != PROFINET_ETHERTYPE)
1255 {
1256 continue;
1257 }
1258 if (payload.size() < 10)
1259 {
1260 continue;
1261 }
1262
1263 PNDCPHeader dcp;
1264 try
1265 {
1266 dcp = PNDCPHeader::Parse(payload);
1267 }
1268 catch (const std::exception&)
1269 {
1270 continue;
1271 }
1272
1273 if (dcp.serviceId != PNDCPHeader::HELLO)
1274 {
1275 continue;
1276 }
1277 if (dcp.serviceType != PNDCPHeader::REQUEST)
1278 {
1279 continue;
1280 }
1281
1282 std::map<BlockKey, Bytes> blocks;
1283 ForEachBlock(dcp.payload, [&](std::uint8_t option, std::uint8_t suboption, const Bytes& blockPayload)
1284 {
1285 blocks[{option, suboption}] = blockPayload;
1286 });
1287
1288 try
1289 {
1290 const DCPDeviceDescription device(eth.src, blocks);
1291 devices.push_back(device);
1292 if (callback)
1293 {
1294 callback(device);
1295 }
1296 }
1297 // NOLINTNEXTLINE(bugprone-empty-catch)
1298 catch (const std::exception&)
1299 {
1300 // Ignore malformed Hello PDUs, matching dcp.py's warn-and-continue behavior.
1301 }
1302 }
1303
1304 return devices;
1305}
1306
1307bool SignalDevice(const EthernetSocket& sock, const MacAddress& src, const std::string& target, int durationMs, int timeoutSec)
1308{
1309 const MacAddress dst = String2Mac(target);
1310 const Bytes frame = BuildDcpSignalRequest(dst, src);
1311 sock.Send(frame);
1312
1313 try
1314 {
1315 const std::uint8_t blockError = RecvSetResponse(sock, src, timeoutSec);
1316 if (blockError != DCP_BLOCK_ERROR_OK)
1317 {
1318 throw DCPError("DCP Signal failed: " + DcpBlockErrorName(blockError));
1319 }
1320 return true;
1321 }
1322 catch (const DCPTimeoutError&)
1323 {
1324 return false;
1325 }
1326}
1327
1328bool ResetToFactory(const EthernetSocket& sock, const MacAddress& src, const std::string& target,
1329 std::uint16_t mode, int timeoutSec)
1330{
1331 const MacAddress dst = String2Mac(target);
1332 const std::uint32_t xid = GenerateXid();
1333
1334 const Bytes blockQualifier = {static_cast<std::uint8_t>(mode >> OneOctetShift), static_cast<std::uint8_t>(mode & LowByteMask)};
1335
1336 PNDCPBlockRequest block;
1337 block.option = DCP_OPTION_CONTROL;
1339 block.length = static_cast<std::uint16_t>(blockQualifier.size());
1340 block.payload = blockQualifier;
1341
1342 PNDCPHeader dcp;
1344 dcp.serviceId = PNDCPHeader::SET;
1345 dcp.serviceType = PNDCPHeader::REQUEST;
1346 dcp.xId = xid;
1347 dcp.respDelay = 0;
1348 dcp.length = static_cast<std::uint16_t>(blockQualifier.size() + 4);
1349 dcp.payload = block.ToBytes();
1350
1351 sock.Send(BuildEthernetDcp(dst, src, dcp));
1352
1353 try
1354 {
1355 const std::uint8_t blockError = RecvSetResponse(sock, src, timeoutSec);
1356 if (blockError != DCP_BLOCK_ERROR_OK)
1357 {
1358 throw DCPError("DCP Reset to Factory failed: " + DcpBlockErrorName(blockError));
1359 }
1360 std::this_thread::sleep_for(std::chrono::seconds(2));
1361 return true;
1362 }
1363 catch (const DCPTimeoutError&)
1364 {
1365 return false;
1366 }
1367}
1368
1369} // namespace profinet::dcp
DCP protocol errors.
Definition exceptions.h:539
DCP operation timed out.
Definition exceptions.h:551
A minimal RAII wrapper around a Linux AF_PACKET raw socket bound to an interface.
Definition util.h:196
void SetTimeout(std::chrono::milliseconds timeout) const override
Set the receive timeout.
Definition util.cpp:375
void Send(const std::vector< std::uint8_t > &frame) const override
Send a raw Ethernet frame.
Definition util.cpp:366
std::vector< std::uint8_t > Recv() const override
Receive up to MAX_ETHERNET_FRAME bytes.
Definition util.cpp:386
Abstract interface for raw Ethernet frame transport.
virtual void SetTimeout(std::chrono::milliseconds timeout) const =0
Set the receive timeout.
virtual std::vector< std::uint8_t > Recv() const =0
Receive one raw Ethernet frame.
Time-limited-operation helper, mirroring util.py's MaxTimeout context manager.
Definition util.h:319
Input validation error.
Definition exceptions.h:627
Parsed PROFINET device information from a DCP response.
Definition dcp.h:426
PROFINET DCP discovery and device configuration interface.
Declares exceptions and error types used by the PROFINET IO controller stack.
bool ResetToFactory(const EthernetSocket &sock, const MacAddress &src, const std::string &target, std::uint16_t mode=RESET_MODE_COMMUNICATION, int timeoutSec=DEFAULT_TIMEOUT)
Reset a device to factory defaults.
Definition dcp.cpp:1328
void SendRequest(const EthernetSocket &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:1025
constexpr std::uint8_t DCP_SUBOPTION_CONTROL_SIGNAL
DCP suboption (Control): signal (flash LEDs).
Definition dcp.h:135
constexpr std::uint8_t DCP_SUBOPTION_IP_PARAMETER
DCP suboption (IP): IP/netmask/gateway parameter.
Definition dcp.h:107
constexpr std::uint8_t DEVICE_ROLE_IO_MULTIDEVICE
Device role bit: IO-Multidevice.
Definition dcp.h:170
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:756
constexpr std::uint8_t DCP_SUBOPTION_DHCP_CLIENT_ID
DCP suboption (DHCP): client identifier option.
Definition dcp.h:154
bool SetParam(const EthernetSocket &sock, const MacAddress &src, const std::string &target, const std::string &param, const std::string &value, int timeoutSec=DEFAULT_TIMEOUT, bool permanent=false)
Write a named parameter to a device.
Definition dcp.cpp:922
constexpr std::uint16_t DCP_HELLO_FRAME_ID
Frame ID for DCP Hello announcements.
Definition dcp.h:66
Bytes BuildDcpSignalRequest(const MacAddress &dst, const MacAddress &src)
Build a serialized DCP Signal request.
Definition dcp.cpp:812
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_INSTANCE
DCP suboption (Device): device instance.
Definition dcp.h:124
constexpr std::uint8_t DCP_OPTION_DEVICE
DCP option: device identification.
Definition dcp.h:90
const std::map< std::string, BlockKey > & Params()
Table of supported parameter names, mapping to their (option, suboption).
Definition dcp.cpp:480
std::string GetBlockName(std::uint8_t option, std::uint8_t suboption)
Get the human-readable name for a (option, suboption) DCP block.
Definition dcp.cpp:163
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_ROLE
DCP suboption (Device): device role bitmask.
Definition dcp.h:118
constexpr std::uint8_t DCP_BLOCK_ERROR_SET_NOT_POSSIBLE
DCP SET block-level result: set not possible locally.
Definition dcp.h:376
constexpr std::uint16_t DCP_GET_SET_FRAME_ID
Frame ID for DCP Get/Set requests and responses.
Definition dcp.h:64
constexpr std::uint8_t DCP_BLOCK_ERROR_OPTION_UNSUPPORTED
DCP SET block-level result: option not supported.
Definition dcp.h:368
constexpr std::uint8_t DCP_BLOCK_ERROR_SUBOPTION_UNSUPPORTED
DCP SET block-level result: suboption not supported.
Definition dcp.h:370
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_INITIATIVE
DCP suboption (DeviceInitiative): the only suboption.
Definition dcp.h:163
constexpr std::uint8_t DCP_SUBOPTION_DHCP_UUID
DCP suboption (DHCP): client UUID option.
Definition dcp.h:158
std::pair< std::uint8_t, std::uint8_t > BlockKey
(option, suboption) key identifying a DCP block.
Definition dcp.h:421
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_ALIAS
DCP suboption (Device): alias name.
Definition dcp.h:122
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_ID
DCP suboption (Device): vendor/device ID.
Definition dcp.h:116
constexpr std::uint16_t DCP_IDENTIFY_REQUEST_FRAME_ID
Frame ID for DCP Identify requests.
Definition dcp.h:60
constexpr std::uint8_t DCP_SUBOPTION_DHCP_VENDOR_SPEC
DCP suboption (DHCP): vendor-specific option.
Definition dcp.h:146
std::optional< std::vector< std::uint8_t > > GetParam(const EthernetSocket &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:880
void SendDiscover(const EthernetSocket &sock, const MacAddress &src, std::uint16_t responseDelay=DEFAULT_RESPONSE_DELAY_FACTOR)
Send a DCP Identify multicast request to discover all devices on the segment.
Definition dcp.cpp:1003
const std::string DCP_MULTICAST_MAC
DCP Identify/Get/Set multicast destination MAC.
Definition dcp.h:55
constexpr std::uint8_t DCP_SUBOPTION_DHCP_FQDN
DCP suboption (DHCP): fully qualified domain name option.
Definition dcp.h:156
constexpr std::uint8_t DEVICE_ROLE_IO_DEVICE
Device role bit: IO-Device.
Definition dcp.h:166
const std::string DCP_HELLO_MULTICAST_MAC
DCP Hello multicast destination MAC.
Definition dcp.h:57
ResponseMap ReadResponse(const EthernetSocket &sock, const MacAddress &myMac, int timeoutSec=DEFAULT_RESPONSE_TIMEOUT, bool once=false, std::optional< std::uint32_t > expectedXid=std::nullopt)
Read and parse DCP Identify responses.
Definition dcp.cpp:1048
std::string DcpBlockErrorName(std::uint8_t code)
Get the human-readable name for a DCP SET block-level error code.
Definition dcp.cpp:430
std::vector< std::string > DecodeDeviceRole(std::uint8_t roleByte)
Decode a device role bitmask into a list of role names.
Definition dcp.cpp:146
constexpr std::uint8_t DCP_OPTION_IP
DCP option: IP configuration.
Definition dcp.h:88
std::uint8_t RecvSetResponse(const IRawEthernetSocket &sock, const MacAddress &srcMac, int timeoutSec)
Receive and parse a DCP SET response, filtering out unrelated frames.
Definition dcp.cpp:195
constexpr std::uint8_t DEVICE_ROLE_IO_CONTROLLER
Device role bit: IO-Controller.
Definition dcp.h:168
bool SetIp(const EthernetSocket &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)
Set a device's IP configuration via DCP.
Definition dcp.cpp:950
constexpr std::uint8_t DCP_SUBOPTION_DHCP_HOSTNAME
DCP suboption (DHCP): hostname option.
Definition dcp.h:144
constexpr std::uint8_t DCP_SUBOPTION_CONTROL_RESPONSE
DCP suboption (Control): response.
Definition dcp.h:137
constexpr std::uint8_t DCP_BLOCK_ERROR_IN_OPERATION
DCP SET block-level result: set not possible, device in operation.
Definition dcp.h:378
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:632
constexpr std::uint8_t DCP_SUBOPTION_DEVICE_NAME
DCP suboption (Device): station name.
Definition dcp.h:114
constexpr std::uint8_t DCP_BLOCK_ERROR_OK
DCP SET block-level result: OK.
Definition dcp.h:366
bool SignalDevice(const EthernetSocket &sock, const MacAddress &src, const std::string &target, int durationMs=FLASH_DURATION_MS, int timeoutSec=DEFAULT_TIMEOUT)
Flash a device's identification LEDs.
Definition dcp.cpp:1307
constexpr std::uint8_t DCP_SUBOPTION_CONTROL_RESET_TO_FACTORY
DCP suboption (Control): reset to factory.
Definition dcp.h:141
constexpr std::uint8_t DCP_BLOCK_ERROR_SUBOPTION_NOT_SET
DCP SET block-level result: suboption not set.
Definition dcp.h:372
constexpr std::uint8_t DCP_OPTION_CONTROL
DCP option: control (start/stop/signal/reset).
Definition dcp.h:96
constexpr std::uint8_t DCP_OPTION_DHCP
DCP option: DHCP.
Definition dcp.h:92
constexpr std::uint8_t DEVICE_ROLE_PN_SUPERVISOR
Device role bit: PN-Supervisor.
Definition dcp.h:172
constexpr std::uint8_t DCP_BLOCK_ERROR_RESOURCE
DCP SET block-level result: resource error.
Definition dcp.h:374
void SendHello(const EthernetSocket &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}, std::uint8_t deviceRole=DEVICE_ROLE_IO_DEVICE)
Send a DCP Hello multicast announcement (device self-announce after power-on).
Definition dcp.cpp:1135
constexpr std::uint8_t DCP_SERVICE_TYPE_RESPONSE_SUCCESS
DCP service type: RESPONSE (success).
Definition dcp.h:80
constexpr std::uint8_t DCP_OPTION_DEVICE_INITIATIVE
DCP option: device-initiative (Hello) configuration.
Definition dcp.h:98
std::vector< DCPDeviceDescription > ReceiveHello(const EthernetSocket &sock, const MacAddress &myMac, int timeoutSec=DEFAULT_HELLO_TIMEOUT, const std::function< void(const DCPDeviceDescription &)> &callback=nullptr)
Listen for DCP Hello announcements from other devices.
Definition dcp.cpp:1214
std::uint8_t ParseSetResponse(const Bytes &data)
Parses the raw byte response payload received from a SET operation.
Definition dcp.cpp:258
constexpr std::uint8_t DCP_SERVICE_TYPE_RESPONSE_UNSUPPORTED
DCP service type: RESPONSE (service unsupported).
Definition dcp.h:82
constexpr std::size_t DCP_MAX_NAME_LENGTH
Maximum allowed length in bytes for a station name.
Definition dcp.h:85
std::string Hex2(std::uint8_t value)
Format an 8-bit unsigned integer as a 2-digit hexadecimal string.
Definition util.cpp:68
constexpr std::uint16_t VLAN_ETHERTYPE
EtherType value identifying an 802.1Q VLAN tag (0x8100).
Definition util.h:36
constexpr int uuidLenght
Constant lenght of a UUID.
Definition util.h:44
std::vector< std::uint8_t > ToVec(const std::string &input)
Convert a string into a vector of raw bytes.
Definition util.cpp:73
std::string String2Ip(std::span< const std::uint8_t > ipBytes)
Format raw IPv4 address bytes as a dotted string.
Definition util.cpp:214
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:67
std::array< std::uint8_t, 4 > Ip2String(const std::string &ipStr)
Parse a dotted-decimal IPv4 address string.
Definition util.cpp:230
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
Definition util.cpp:130
static constexpr int OneOctetShift
The bit-shift distance required to move data across a single octet.
Definition util.h:50
std::string ToHex(const std::uint8_t *data, std::size_t len)
Hex-encode a byte buffer.
Definition util.cpp:112
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
Definition util.h:32
std::string Hex4(std::uint16_t value)
Format a 16-bit unsigned integer as a 4-digit hexadecimal string.
Definition util.cpp:63
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:52
std::string Mac2String(const MacAddress &mac)
Format a MAC address as a colon-separated string.
Definition util.cpp:196
std::string DecodeBytes(const std::uint8_t *data, std::size_t len)
Decode bytes to a UTF-8 string, stripping trailing NUL bytes.
Definition util.cpp:247
std::string GetVendorName(std::uint16_t vendorId)
Get the vendor name for a 16-bit PROFINET vendor ID.
Definition vendors.cpp:29
PROFINET protocol wire-format structures and serialization helpers.
Bytes payload
Definition rpc.cpp:3254
Plain (untagged) Ethernet II frame header.
Definition protocol.h:63
MacAddress src
Source MAC address.
Definition protocol.h:68
std::uint16_t type
EtherType field.
Definition protocol.h:71
static EthernetHeader Parse(const Bytes &data)
Parse an Ethernet header and payload from raw frame bytes.
Definition protocol.h:79
Bytes ToBytes() const
Serialize this header and its payload back to raw frame bytes.
Definition protocol.h:97
MacAddress dst
Destination MAC address.
Definition protocol.h:65
Bytes payload
Frame payload following the header.
Definition protocol.h:74
DCP request block: Option(1) + SubOption(1) + Length(2) + payload(Length).
Definition protocol.h:271
std::uint16_t length
Length in bytes of Payload.
Definition protocol.h:279
std::uint8_t suboption
DCP suboption code.
Definition protocol.h:276
Bytes payload
Block payload.
Definition protocol.h:282
std::uint8_t option
DCP option code.
Definition protocol.h:273
Bytes ToBytes() const
Serialize this block back to raw bytes.
Definition protocol.h:308
DCP (Discovery and Configuration Protocol) frame header.
Definition protocol.h:168
std::uint32_t xId
Transaction ID correlating requests and responses.
Definition protocol.h:205
Bytes ToBytes() const
Serialize this header and its block-list payload back to raw bytes.
Definition protocol.h:242
std::uint16_t respDelay
Response delay factor (requests) or reserved (responses).
Definition protocol.h:208
std::uint16_t frameId
DCP frame ID identifying the service/multicast group.
Definition protocol.h:196
static PNDCPHeader Parse(const Bytes &data)
Parse a DCP header and block-list payload from raw bytes.
Definition protocol.h:222
std::uint16_t length
Length in bytes of the block list that follows.
Definition protocol.h:211
std::uint8_t serviceType
DCP service type (REQUEST/RESPONSE/...).
Definition protocol.h:202
std::uint8_t serviceId
DCP service ID (GET/SET/IDENTIFY/HELLO).
Definition protocol.h:199
Bytes payload
DCP block list payload.
Definition protocol.h:214
Parsed DHCP block from a DCP response.
Definition dcp.h:388
std::uint8_t suboption
DHCP suboption code.
Definition dcp.h:390
std::optional< std::string > fqdn
Decoded fully qualified domain name, if this is an FQDN suboption.
Definition dcp.h:408
std::vector< std::uint8_t > rawData
Raw, undecoded block payload.
Definition dcp.h:396
std::optional< std::vector< std::uint8_t > > vendorSpecific
Decoded vendor-specific data, if this is a VendorSpec suboption.
Definition dcp.h:405
std::optional< std::string > hostname
Decoded hostname, if this is a Hostname suboption.
Definition dcp.h:399
std::string suboptionName
Human-readable name of the suboption.
Definition dcp.h:393
std::optional< std::vector< std::uint8_t > > clientId
Decoded client ID, if this is a ClientID suboption.
Definition dcp.h:402
std::optional< std::string > uuid
Decoded UUID string, if this is a UUID suboption.
Definition dcp.h:411
Declares PROFINET Vendor ID lookup utilities. The lookup table itself lives in vendors_data....