PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_alarmListenerUdp.cpp
Go to the documentation of this file.
1
11
12#include <gmock/gmock.h>
13#include <gtest/gtest.h>
14
15#include <atomic>
16#include <chrono>
17#include <condition_variable>
18#include <cstdint>
19#include <memory>
20#include <mutex>
21#include <vector>
22
23#include "mocks/mockUdpSocket.h"
25#include "profinet/protocol.h"
26#include "profinet/wire.h"
27
28namespace profinet
29{
30
31namespace
32{
33
34using ::testing::_;
35using ::testing::StrictMock;
36
37constexpr std::uint16_t kControllerReference = 1;
38constexpr std::uint16_t kDeviceReference = 42;
39
40constexpr std::uint16_t kUdpSourcePort = 45000;
41
43UdpEndpoint MakeSourceAddress()
44{
45 return {
46 .address = "127.0.0.1",
47 .port = kUdpSourcePort};
48}
49
58Bytes BuildUdpAlarmNotification()
59{
60 Bytes body;
61
62 wire::PutU16(body, 0x0001); // AlarmType
63 wire::PutU32(body, 0x00000000); // API
64 wire::PutU16(body, 0x0001); // Slot
65 wire::PutU16(body, 0x0001); // Subslot
66 wire::PutU32(body, 0x00000001); // ModuleIdent
67 wire::PutU32(body, 0x00000001); // SubmoduleIdent
68 wire::PutU16(body, 0x0401); // AlarmSpecifier
69
70 EXPECT_EQ(body.size(), 20U);
71 if (body.size() < 20U)
72 {
73 return {};
74 }
75
77 0x00,
78 0x00,
79 }; // Block version
80
81 payload.insert(payload.end(), body.begin(), body.end());
82
83 // Unknown/generic USI. ParseAlarmItem() accepts this and stores the
84 // remaining payload as raw data.
85 payload.push_back(0xFF);
86 payload.push_back(0xFF);
87
88 Bytes block;
89
90 constexpr std::uint16_t blockType = 0x0002;
91
92 wire::PutU16(block, blockType);
93 wire::PutU16(block, static_cast<std::uint16_t>(payload.size()));
94
95 block.insert(block.end(), payload.begin(), payload.end());
96
97 return block;
98}
99} // namespace
106class AlarmListenerUdpTest : public ::testing::Test
107{
108 protected:
110 void SetUp() override
111 {
112 endpoint.interface = "lo";
113 endpoint.controllerRef = kControllerReference;
114 endpoint.deviceRef = kDeviceReference;
115 endpoint.deviceMac = MacAddress{0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
117 ioContext = std::make_unique<asio::io_context>();
118 listener = std::make_unique<AlarmListener>(*ioContext, endpoint);
119 }
120
123 [[nodiscard]] UdpEndpoint SourceAddress() const
124 {
125 return MakeSourceAddress();
126 }
127 std::unique_ptr<asio::io_context> ioContext;
128
130 std::unique_ptr<AlarmListener> listener;
131};
132
135TEST_F(AlarmListenerUdpTest, ReceivesAlarmAndSendsAckToSource)
136{
137 auto socket = std::make_unique<StrictMock<MockUdpSocket>>();
138
139 MockUdpSocket* mockSocket = socket.get();
140
141 const UdpEndpoint source = SourceAddress();
142
143 UdpDatagram datagram;
144 datagram.data = BuildUdpAlarmNotification();
145 datagram.source = source;
146
147 std::mutex callbackMutex;
148 std::condition_variable callbackCondition;
149 bool callbackReceived = false;
150
151 listener->AddAlarmNotificationCallback(
152 [&](const AlarmNotification& alarm)
153 {
154 EXPECT_EQ(alarm.slotNumber, 1U);
155 EXPECT_EQ(alarm.subslotNumber, 1U);
156
157 {
158 std::scoped_lock lock(callbackMutex);
159 callbackReceived = true;
160 }
161
162 callbackCondition.notify_one();
163 });
164
165 EXPECT_CALL(*mockSocket, RecvFrom(RECEIVE_BUFFER_LENGTH))
166 .WillOnce(::testing::Return(datagram))
167 .WillRepeatedly(
168 ::testing::Return(UdpDatagram{}));
169
170 EXPECT_CALL(*mockSocket, SendTo(_, _))
171 .WillOnce(
172 [&](const Bytes& ack,
173 const UdpEndpoint& destination)
174 {
175 const UdpEndpoint source = SourceAddress();
176 EXPECT_EQ(destination, source);
177
178 EXPECT_FALSE(ack.empty());
179 EXPECT_EQ(ack.size(), PNAlarmAckPDU::kSize);
180 });
181
182 listener->Start(nullptr, std::move(socket));
183
184 {
185 std::unique_lock lock(callbackMutex);
186
187 const bool received =
188 callbackCondition.wait_for(
189 lock,
190 std::chrono::seconds(1),
191 [&callbackReceived]
192 {
193 return callbackReceived;
194 });
195
196 EXPECT_TRUE(received);
197 }
198
199 EXPECT_TRUE(listener->IsRunning());
200
201 listener->Stop();
202
203 EXPECT_FALSE(listener->IsRunning());
204
205 (void)mockSocket;
206}
207
210TEST_F(AlarmListenerUdpTest, IgnoresTooShortUdpPayload)
211{
212 auto socket = std::make_unique<StrictMock<MockUdpSocket>>();
213
214 MockUdpSocket* mockSocket = socket.get();
215
216 std::mutex callbackMutex;
217 std::condition_variable callbackCondition;
218 bool callbackReceived = false;
219
220 listener->AddAlarmNotificationCallback(
221 [&](const AlarmNotification&)
222 {
223 {
224 std::scoped_lock lock(callbackMutex);
225 callbackReceived = true;
226 }
227
228 callbackCondition.notify_one();
229 });
230
231 UdpDatagram invalidDatagram;
232 invalidDatagram.data = {0x01, 0x02, 0x03};
233 invalidDatagram.source = SourceAddress();
234
235 EXPECT_CALL(*mockSocket, RecvFrom(RECEIVE_BUFFER_LENGTH))
236 .WillOnce(::testing::Return(invalidDatagram))
237 .WillRepeatedly(::testing::Return(UdpDatagram{}));
238
239 EXPECT_CALL(*mockSocket, SendTo(_, _))
240 .Times(0);
241
242 listener->Start(nullptr, std::move(socket));
243
244 std::unique_lock lock(callbackMutex);
245
246 EXPECT_FALSE(callbackCondition.wait_for(lock,
247 std::chrono::milliseconds(50),
248 [&callbackReceived]
249 {
250 return callbackReceived;
251 }));
252
253 lock.unlock();
254
255 listener->Stop();
256}
257
260TEST_F(AlarmListenerUdpTest, StartWhileRunningDoesNothing)
261{
262 auto socket = std::make_unique<StrictMock<MockUdpSocket>>();
263
264 MockUdpSocket* mockSocket = socket.get();
265
266 EXPECT_CALL(*mockSocket, RecvFrom(RECEIVE_BUFFER_LENGTH))
267 .WillRepeatedly(::testing::Return(UdpDatagram{}));
268
269 listener->Start(nullptr, std::move(socket));
270
271 ASSERT_TRUE(listener->IsRunning());
272
273 listener->Start();
274
275 EXPECT_TRUE(listener->IsRunning());
276
277 listener->Stop();
278
279 EXPECT_FALSE(listener->IsRunning());
280}
281
283TEST_F(AlarmListenerUdpTest, StopBeforeStartIsSafe)
284{
285 EXPECT_FALSE(listener->IsRunning());
286
287 EXPECT_NO_THROW(listener->Stop());
288
289 EXPECT_FALSE(listener->IsRunning());
290}
291
292} // namespace profinet
Declares the PROFINET RTA alarm listener and AlarmAck transmission support.
GoogleMock implementation of IUdpSocket.
Test fixture for UDP AlarmListener behavior.
std::unique_ptr< AlarmListener > listener
void SetUp() override
Construct an AlarmListener configured for UDP transport.
std::unique_ptr< asio::io_context > ioContext
UdpEndpoint SourceAddress() const
Build the source endpoint used for the injected UDP datagram.
GoogleMock implementation of IUdpSocket.
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t value)
Append a 16-bit value to a buffer in big-endian order.
Definition wire.h:97
void PutU32(std::vector< std::uint8_t > &out, std::uint32_t value)
Append a 32-bit value to a buffer in big-endian order.
Definition wire.h:105
TEST_F(AlarmListenerTests, InitializesWithEndpoint)
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
constexpr std::size_t RECEIVE_BUFFER_LENGTH
Receive buffer length.
Definition util.h:43
PROFINET protocol wire-format structures and serialization helpers.
Alarm endpoint configuration.
int transport
Transport: 0 = Layer2 (RTA), 1 = UDP.
std::string interface
Network interface name to bind to, e.g. "eth0".
std::uint16_t controllerRef
Controller's local alarm reference (from AlarmCRBlockReq).
std::uint16_t deviceRef
Device's local alarm reference (from AlarmCRBlockRes).
MacAddress deviceMac
The device's MAC address.
Complete parsed AlarmNotification PDU: header fields + parsed items.
Definition alarms.h:356
std::uint16_t slotNumber
Slot number where the alarm originated.
Definition alarms.h:370
std::uint16_t subslotNumber
Subslot number where the alarm originated.
Definition alarms.h:373
static constexpr std::size_t kSize
Size in bytes of this PDU.
Definition protocol.h:2370
A UDP datagram together with the IPv4 address of its sender.
Definition IUdpSocket.h:65
std::vector< std::uint8_t > data
Datagram payload.
Definition IUdpSocket.h:67
UdpEndpoint source
IPv4 address and UDP port of the datagram sender.
Definition IUdpSocket.h:70
Portable IPv4 UDP endpoint.
Definition IUdpSocket.h:42
Bytes payload
Definition test_dcp.cpp:392
Generic helpers for reading and writing integer and byte data in big- and little-endian wire formats.