10#include <gtest/gtest.h>
18#include <system_error>
28using namespace std::chrono_literals;
36std::uint16_t FindFreeUdpPort()
41 socket.open(asio::ip::udp::v4());
42 socket.bind(asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
44 return socket.local_endpoint().port();
52std::unique_ptr<asio::ip::udp::socket> CreateBoundUdpSocket(asio::io_context&
ioContext, std::uint16_t port = 0)
54 auto socket = std::make_unique<asio::ip::udp::socket>(
ioContext);
56 socket->open(asio::ip::udp::v4());
57 socket->bind(asio::ip::udp::endpoint(asio::ip::address_v4::loopback(), port));
66void SendUdpDatagram(std::uint16_t port,
const Bytes& data)
71 socket.open(asio::ip::udp::v4());
73 const asio::ip::udp::endpoint endpoint(asio::ip::address_v4::loopback(), port);
76 socket.send_to(asio::buffer(data), endpoint, 0, ec);
78 ASSERT_FALSE(ec) <<
"UDP send failed: " << ec.message();
86Bytes ReceiveUdpDatagram(asio::ip::udp::socket& socket, asio::ip::udp::endpoint&
remoteEndpoint)
88 std::array<std::uint8_t, profinet::RECEIVE_BUFFER_LENGTH>
buffer{};
95 throw std::system_error(ec,
"Failed to receive test UDP datagram");
109 socket.open(asio::ip::udp::v4());
110 socket.bind(asio::ip::udp::endpoint(asio::ip::address_v4::loopback(), 0));
111 port = socket.local_endpoint().port();
120 UdpEchoServer(
const UdpEchoServer&) =
delete;
121 UdpEchoServer& operator=(
const UdpEchoServer&) =
delete;
126 [[nodiscard]] std::uint16_t Port()
const
136 thread = std::thread(
153 if (thread.joinable())
163 socket.async_receive_from(asio::buffer(receiveBuffer),
165 [&](
const asio::error_code& ec, std::size_t bytesReceived)
172 socket.async_send_to(asio::buffer(receiveBuffer.data(), bytesReceived),
174 [&](
const asio::error_code& sendEc, std::size_t)
187 asio::ip::udp::socket socket;
189 std::array<std::uint8_t, profinet::RECEIVE_BUFFER_LENGTH> receiveBuffer{};
190 std::uint16_t port{0};
202 ioContext = std::make_unique<asio::io_context>();
233 std::unique_ptr<profinet::AsioIoContextRunner>
runner;
241 EXPECT_FALSE(transport.
IsOpen());
249 ASSERT_FALSE(transport.
IsOpen());
251 transport.
Open(
"127.0.0.1", 34964);
253 EXPECT_TRUE(transport.
IsOpen());
259 const std::uint16_t port = FindFreeUdpPort();
263 transport.
Open(
"127.0.0.1", port,
true);
265 EXPECT_TRUE(transport.
IsOpen());
273 EXPECT_THROW(transport.
Open(
"not-an-ip-address", 34964), std::system_error);
275 EXPECT_FALSE(transport.
IsOpen());
281 asio::io_context occupiedIoContext;
282 asio::ip::udp::socket occupiedSocket(occupiedIoContext);
284 occupiedSocket.open(asio::ip::udp::v4());
285 occupiedSocket.bind(asio::ip::udp::endpoint(asio::ip::address_v4::any(), 0));
287 const std::uint16_t port = occupiedSocket.local_endpoint().port();
291 EXPECT_THROW(transport.
Open(
"127.0.0.1", port,
true), std::system_error);
293 EXPECT_FALSE(transport.
IsOpen());
299 auto receiver = CreateBoundUdpSocket(*
ioContext);
300 const std::uint16_t receiverPort = receiver->local_endpoint().port();
302 const std::uint16_t unusedPort = FindFreeUdpPort();
306 transport.
Open(
"127.0.0.1", unusedPort);
308 ASSERT_TRUE(transport.
IsOpen());
310 transport.
Open(
"127.0.0.1", receiverPort);
312 const Bytes request{0x10, 0x20, 0x30, 0x40};
314 ASSERT_NO_THROW(transport.
Send(request));
316 asio::ip::udp::endpoint senderEndpoint;
317 const Bytes received = ReceiveUdpDatagram(*receiver, senderEndpoint);
319 EXPECT_EQ(received, request);
325 const std::uint16_t port = FindFreeUdpPort();
328 transport.
Open(
"127.0.0.1", port,
true);
330 const Bytes expected{0x01, 0x02, 0x03, 0x04, 0xAA, 0xBB};
332 SendUdpDatagram(port, expected);
337 EXPECT_EQ(received, expected);
338 EXPECT_EQ(
remoteEndpoint.address(), asio::ip::address_v4::loopback());
355 const std::uint16_t port = FindFreeUdpPort();
358 transport.
Open(
"127.0.0.1", port,
true);
368 const std::uint16_t port = FindFreeUdpPort();
371 transport.
Open(
"127.0.0.1", port,
true);
381 const std::uint16_t port = FindFreeUdpPort();
384 transport.
Open(
"127.0.0.1", port,
true);
394 const std::uint16_t port = FindFreeUdpPort();
397 transport.
Open(
"127.0.0.1", port,
true);
401 asio::steady_timer closeTimer(*
ioContext);
402 closeTimer.expires_after(1ms);
403 closeTimer.async_wait([&transport](
const asio::error_code& ec)
417 auto receiver = CreateBoundUdpSocket(*
ioContext);
418 const std::uint16_t receiverPort = receiver->local_endpoint().port();
421 transport.
Open(
"127.0.0.1", FindFreeUdpPort());
423 const Bytes request{0x10, 0x20, 0x30, 0x40};
424 const asio::ip::udp::endpoint target(asio::ip::address_v4::loopback(), receiverPort);
426 ASSERT_NO_THROW(transport.
SendTo(request, target));
428 asio::ip::udp::endpoint senderEndpoint;
429 const Bytes received = ReceiveUdpDatagram(*receiver, senderEndpoint);
431 EXPECT_EQ(received, request);
432 EXPECT_EQ(senderEndpoint.address(), asio::ip::address_v4::loopback());
433 EXPECT_NE(senderEndpoint.port(), 0);
441 const Bytes request{0x01, 0x02, 0x03};
442 const asio::ip::udp::endpoint target(asio::ip::address_v4::loopback(), 34964);
444 EXPECT_THROW(transport.
SendTo(request, target), std::system_error);
451 transport.
Open(
"127.0.0.1", FindFreeUdpPort());
453 const Bytes request{0x01, 0x02, 0x03};
454 const asio::ip::udp::endpoint target(asio::ip::address_v6::loopback(), 34964);
456 EXPECT_THROW(transport.
SendTo(request, target), std::system_error);
462 UdpEchoServer server;
466 transport.
Open(
"127.0.0.1", server.Port());
468 const Bytes request{0x01, 0x02, 0x03, 0x04, 0xAA, 0xBB};
470 const Bytes response = transport.
SendReceive(request, 1000ms);
472 EXPECT_EQ(response, request);
486 UdpEchoServer server;
493 transport.
Open(
"127.0.0.1", server.Port());
503 const Bytes response = transport.
SendReceive(request, 1000ms);
505 EXPECT_EQ(response, request);
513 auto silentServer = CreateBoundUdpSocket(*
ioContext);
514 const std::uint16_t port = silentServer->local_endpoint().port();
517 transport.
Open(
"127.0.0.1", port);
519 const Bytes request{0x01, 0x02, 0x03};
529 const Bytes request{0x01, 0x02, 0x03};
531 EXPECT_THROW(transport.
SendReceive(request, 100ms), std::system_error);
537 UdpEchoServer server;
541 transport.
Open(
"127.0.0.1", server.Port());
543 const Bytes request{0x01, 0x02, 0x03};
545 EXPECT_THROW(transport.
SendReceive(request, 0ms), std::system_error);
553 UdpEchoServer server;
557 transport.
Open(
"127.0.0.1", server.Port());
559 const Bytes request{0x01, 0x02, 0x03};
561 EXPECT_THROW(transport.
SendReceive(request, -1ms), std::system_error);
569 auto silentServer = CreateBoundUdpSocket(*
ioContext);
570 const std::uint16_t port = silentServer->local_endpoint().port();
573 transport.
Open(
"127.0.0.1", port);
575 const Bytes request{0x01, 0x02, 0x03};
577 asio::steady_timer closeTimer(*
ioContext);
578 closeTimer.expires_after(1ms);
579 closeTimer.async_wait(
580 [&transport](
const asio::error_code& ec)
588 EXPECT_THROW(transport.
SendReceive(request, 500ms), std::system_error);
594 auto receiver = CreateBoundUdpSocket(*
ioContext);
595 const std::uint16_t receiverPort = receiver->local_endpoint().port();
598 transport.
Open(
"127.0.0.1", receiverPort);
600 const Bytes request{0x10, 0x20, 0x30, 0x40};
602 ASSERT_NO_THROW(transport.
Send(request));
604 asio::ip::udp::endpoint senderEndpoint;
605 const Bytes received = ReceiveUdpDatagram(*receiver, senderEndpoint);
607 EXPECT_EQ(received, request);
615 const Bytes request{0x01, 0x02, 0x03};
617 EXPECT_THROW(transport.
Send(request), std::system_error);
625 transport.
Open(
"127.0.0.1", FindFreeUdpPort());
627 ASSERT_TRUE(transport.
IsOpen());
631 EXPECT_FALSE(transport.
IsOpen());
639 transport.
Open(
"127.0.0.1", FindFreeUdpPort());
641 EXPECT_NO_THROW(transport.
Close());
642 EXPECT_NO_THROW(transport.
Close());
644 EXPECT_FALSE(transport.
IsOpen());
652 EXPECT_FALSE(transport.
IsOpen());
654 EXPECT_NO_THROW(transport.
Close());
656 EXPECT_FALSE(transport.
IsOpen());
664 EXPECT_FALSE(transport.
IsOpen());
666 transport.
Open(
"127.0.0.1", FindFreeUdpPort());
668 EXPECT_TRUE(transport.
IsOpen());
676 transport.
Open(
"127.0.0.1", FindFreeUdpPort());
678 ASSERT_TRUE(transport.
IsOpen());
682 EXPECT_FALSE(transport.
IsOpen());
698 const std::uint16_t port = FindFreeUdpPort();
701 transport.
Open(
"127.0.0.1", port,
true);
703 const Bytes expected{
714 SendUdpDatagram(port, expected);
720 EXPECT_EQ(received, expected);
721 EXPECT_EQ(
remoteEndpoint.address(), asio::ip::address_v4::loopback());
Runs an Asio I/O context on a dedicated worker thread.
void SetUp() override
Create a fresh Asio execution context for the test.
std::unique_ptr< profinet::AsioIoContextRunner > runner
void TearDown() override
Destroy the Asio execution context after the test.
std::unique_ptr< asio::io_context > ioContext
Asio execution context used by the transport under test.
Keeps an Asio I/O context running on a dedicated worker thread.
Synchronous UDP transport used by PROFINET DCE/RPC.
void SendTo(const Bytes &data, const asio::ip::udp::endpoint &targetEndpoint) override
Send a UDP datagram to a specific remote endpoint.
bool IsOpen() const noexcept
Check whether the socket is open.
void Open(const std::string &address, std::uint16_t port, bool isListener=false) override
Open the UDP socket and configure the remote endpoint.
Bytes SendReceive(const Bytes &data, std::chrono::milliseconds timeout) override
Send a UDP datagram and wait for one response.
void Close() noexcept override
Close the UDP socket.
void Send(const Bytes &data) override
Send a UDP datagram without waiting for a response.
Bytes Receive(std::chrono::milliseconds timeout, asio::ip::udp::endpoint &remoteEndpoint) override
Wait for an incoming UDP datagram and capture the sender's endpoint.
Declares exceptions and error types used by the PROFINET IO controller stack.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
asio::ip::udp::endpoint remoteEndpoint
std::size_t receivedBytes
UDP transport abstraction for PROFINET DCE/RPC.
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
TEST_F(RpcTransportTests, ConstructorCreatesClosedTransport)