PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_dcpWireFormat.cpp
Go to the documentation of this file.
1
20
21#include <gtest/gtest.h>
22
23#include <array>
24#include <cstdint>
25#include <span>
26#include <string>
27
28#include "profinet/dcp.h"
29#include "profinet/exceptions.h"
30#include "profinet/protocol.h"
31#include "profinet/wire.h"
32
33namespace profinet::dcp
34{
35
36namespace
37{
38
39constexpr MacAddress kControllerMac{0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
40constexpr MacAddress kDeviceMac{0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
41
42constexpr std::size_t kEthernetHeaderLength = 14U;
43constexpr std::size_t kDcpHeaderLength = 12U;
44constexpr std::size_t kDcpPayloadOffset = kEthernetHeaderLength + kDcpHeaderLength;
45
46constexpr std::uint16_t kExpectedDcpFrameId = static_cast<std::uint16_t>(DcpFrameId::GetSet);
47constexpr std::uint8_t kExpectedSetServiceId = static_cast<std::uint8_t>(DcpServiceId::Set);
48constexpr std::uint8_t kExpectedRequestServiceType = static_cast<std::uint8_t>(DcpServiceType::Request);
49
56[[nodiscard]] std::uint16_t ReadU16(std::span<const std::uint8_t> frame, std::size_t offset)
57{
58 EXPECT_GE(frame.size(), offset + 2U);
59
60 return static_cast<std::uint16_t>((static_cast<std::uint16_t>(frame[offset]) << 8U) |
61 static_cast<std::uint16_t>(frame[offset + 1U]));
62}
63
70[[nodiscard]] std::uint32_t ReadU32(std::span<const std::uint8_t> frame, std::size_t offset)
71{
72 EXPECT_GE(frame.size(), offset + 4U);
73
74 return (static_cast<std::uint32_t>(frame[offset]) << 24U) |
75 (static_cast<std::uint32_t>(frame[offset + 1U]) << 16U) |
76 (static_cast<std::uint32_t>(frame[offset + 2U]) << 8U) |
77 static_cast<std::uint32_t>(frame[offset + 3U]);
78}
79
90[[nodiscard]] std::size_t VerifySetFrameHeader(std::span<const std::uint8_t> frame)
91{
92 EXPECT_GE(frame.size(), kDcpPayloadOffset);
93
94 if (frame.size() < kDcpPayloadOffset)
95 {
96 return {};
97 }
98 EXPECT_EQ((std::array<std::uint8_t, 6>{frame[0], frame[1], frame[2], frame[3], frame[4], frame[5]}), kDeviceMac);
99 EXPECT_EQ((std::array<std::uint8_t, 6>{frame[6], frame[7], frame[8], frame[9], frame[10], frame[11]}), kControllerMac);
100
101 EXPECT_EQ(ReadU16(frame, 12), PROFINET_ETHERTYPE);
102 EXPECT_EQ(ReadU16(frame, 14), kExpectedDcpFrameId);
103
104 EXPECT_EQ(frame[16], kExpectedSetServiceId);
105 EXPECT_EQ(frame[17], kExpectedRequestServiceType);
106
107 // XID occupies bytes 18..21 and is intentionally not checked because it is generated dynamically.
108
109 // Response delay occupies bytes 22..23.
110 EXPECT_EQ(ReadU16(frame, 22), 0U);
111
112 // DCP length occupies bytes 24..25.
113 const std::uint16_t dcpLength = ReadU16(frame, 24);
114
115 EXPECT_EQ(frame.size() - kDcpPayloadOffset, dcpLength);
116
117 return kDcpPayloadOffset;
118}
119
120} // namespace
121
128class DcpWireFormatTest : public ::testing::Test
129{
130 protected:
137 [[nodiscard]] Bytes BuildSetNameRequest(const std::string& name, bool permanent = false) const
138 {
139 return BuildSetParamRequest(kDeviceMac, kControllerMac, "name", name, permanent);
140 }
141
146 {
147 return BuildDcpSignalRequest(kDeviceMac, kControllerMac);
148 }
149};
150
158TEST_F(DcpWireFormatTest, OddLengthNameIsPaddedAndBlockLengthExcludesPadding)
159{
160 const Bytes frame = BuildSetNameRequest("abc");
161 const std::size_t payloadOffset = VerifySetFrameHeader(frame);
162 const auto payload = std::span<const std::uint8_t>(frame).subspan(payloadOffset);
163
164 ASSERT_EQ(payload.size(), 10U);
165
166 EXPECT_EQ(payload[0], PNDCPBlock::NAME_OF_STATION.first);
167 EXPECT_EQ(payload[1], PNDCPBlock::NAME_OF_STATION.second);
168 EXPECT_EQ(ReadU16(payload, 2), 5U);
169 EXPECT_EQ(ReadU16(payload, 4), static_cast<std::uint16_t>(DcpBlockQualifier::Temporary));
170
171 EXPECT_EQ(payload[6], static_cast<std::uint8_t>('a'));
172 EXPECT_EQ(payload[7], static_cast<std::uint8_t>('b'));
173 EXPECT_EQ(payload[8], static_cast<std::uint8_t>('c'));
174
175 // Padding is present physically but excluded from BlockLength.
176 EXPECT_EQ(payload[9], 0x00);
177}
178
184TEST_F(DcpWireFormatTest, EvenLengthNameIsNotPadded)
185{
186 const Bytes frame = BuildSetNameRequest("abcd");
187 const std::size_t payloadOffset = VerifySetFrameHeader(frame);
188 const auto payload = std::span<const std::uint8_t>(frame).subspan(payloadOffset);
189
190 ASSERT_EQ(payload.size(), 10U);
191
192 EXPECT_EQ(payload[0], PNDCPBlock::NAME_OF_STATION.first);
193 EXPECT_EQ(payload[1], PNDCPBlock::NAME_OF_STATION.second);
194
195 // BlockLength = BlockQualifier (2 bytes) + name (4 bytes).
196 EXPECT_EQ(ReadU16(payload, 2), 6U);
197 EXPECT_EQ(ReadU16(payload, 4), static_cast<std::uint16_t>(DcpBlockQualifier::Temporary));
198
199 EXPECT_EQ(payload[6], static_cast<std::uint8_t>('a'));
200 EXPECT_EQ(payload[7], static_cast<std::uint8_t>('b'));
201 EXPECT_EQ(payload[8], static_cast<std::uint8_t>('c'));
202 EXPECT_EQ(payload[9], static_cast<std::uint8_t>('d'));
203}
204
206TEST_F(DcpWireFormatTest, DefaultQualifierIsTemporary)
207{
208 const Bytes frame = BuildSetNameRequest("abcd");
209 const std::size_t payloadOffset = VerifySetFrameHeader(frame);
210 const auto payload = std::span<const std::uint8_t>(frame).subspan(payloadOffset);
211
212 EXPECT_EQ(ReadU16(payload, 4), static_cast<std::uint16_t>(DcpBlockQualifier::Temporary));
213}
214
216TEST_F(DcpWireFormatTest, PermanentQualifierIsSerialized)
217{
218 const Bytes frame = BuildSetNameRequest("abcd", true);
219 const std::size_t payloadOffset = VerifySetFrameHeader(frame);
220 const auto payload = std::span<const std::uint8_t>(frame).subspan(payloadOffset);
221
222 EXPECT_EQ(ReadU16(payload, 4), static_cast<std::uint16_t>(DcpBlockQualifier::Permanent));
223}
224
233TEST_F(DcpWireFormatTest, IpSetOperationIsRejected)
234{
235 EXPECT_THROW(BuildSetParamRequest(kDeviceMac,
236 kControllerMac,
237 "ip",
238 "192.168.0.1",
239 false),
240 DCPError);
241}
242
248TEST_F(DcpWireFormatTest, IpParameterMatchingIsCaseInsensitive)
249{
250 EXPECT_THROW(BuildSetParamRequest(kDeviceMac,
251 kControllerMac,
252 "IP",
253 "192.168.0.1",
254 false),
255 DCPError);
256
257 EXPECT_THROW(BuildSetParamRequest(kDeviceMac,
258 kControllerMac,
259 "Ip",
260 "192.168.0.1",
261 false),
262 DCPError);
263
264 EXPECT_THROW(BuildSetParamRequest(kDeviceMac,
265 kControllerMac,
266 "iP",
267 "192.168.0.1",
268 false),
269 DCPError);
270}
271
284TEST_F(DcpWireFormatTest, SignalUsesFlashOnceValue)
285{
286 const Bytes frame = BuildSignalRequest();
287
288 ASSERT_GE(frame.size(), kDcpPayloadOffset + 8U);
289
290 const auto payload = std::span<const std::uint8_t>(frame).subspan(kDcpPayloadOffset);
291
292 EXPECT_EQ(payload[0], static_cast<std::uint16_t>(DcpOption::Control));
293 EXPECT_EQ(payload[1], static_cast<std::uint8_t>(DcpControlSuboption::Signal));
294 EXPECT_EQ(ReadU16(payload, 2), 4U);
295
296 EXPECT_EQ(ReadU16(payload, 4), 0x0000U);
297
298 EXPECT_EQ(payload[6], 0x01U);
299 EXPECT_EQ(payload[7], 0x00U);
300
301 EXPECT_EQ(payload.size(), 8U);
302}
303
304} // namespace profinet::dcp
DCP protocol errors.
Definition exceptions.h:539
Test fixture for DCP request wire-format serialization.
Bytes BuildSetNameRequest(const std::string &name, bool permanent=false) const
Build a DCP SET request for a station name.
profinet::Bytes BuildSignalRequest() const
Build a DCP Signal request.
PROFINET DCP discovery and device configuration interface.
Declares exceptions and error types used by the PROFINET IO controller stack.
Bytes BuildSetParamRequest(const MacAddress &dst, const MacAddress &src, const std::string &param, const std::string &value, bool permanent)
Build a serialized DCP SET request for a named parameter.
Definition dcp.cpp:883
Bytes BuildDcpSignalRequest(const MacAddress &dst, const MacAddress &src)
Build a serialized DCP Signal request.
Definition dcp.cpp:937
TEST_F(DcpWireFormatTest, OddLengthNameIsPaddedAndBlockLengthExcludesPadding)
constexpr MacAddress kControllerMac
@ Temporary
Change applies until the next power cycle only.
@ Permanent
Change is persisted permanently.
@ Signal
Signal / flash LEDs.
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
@ GetSet
DCP GET/SET request or response.
constexpr std::uint16_t PROFINET_ETHERTYPE
EtherType value identifying PROFINET frames (0x8892).
Definition util.h:30
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
PROFINET protocol wire-format structures and serialization helpers.
static constexpr std::pair< std::uint8_t, std::uint8_t > NAME_OF_STATION
Block option/suboption: station name.
Definition protocol.h:767
Bytes frame
Bytes payload
Definition test_dcp.cpp:392
Generic helpers for reading and writing integer and byte data in big- and little-endian wire formats.