PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
rpcTransport.cpp
Go to the documentation of this file.
1
9
11
12#include <algorithm>
13#include <atomic>
14#include <future>
15#include <memory>
16#include <system_error>
17#include <utility>
18#include <vector>
19
20#include "profinet/exceptions.h"
21
22namespace profinet::rpc
23{
24namespace
25{
26
27struct ReceiveOperation
28{
29 explicit ReceiveOperation(asio::io_context& ioContext)
32 {
33 }
34
35 asio::steady_timer timer;
37 asio::ip::udp::endpoint remoteEndpoint;
38
39 asio::error_code error;
40 std::size_t receivedBytes = 0;
41
42 std::atomic_bool timedOut{false};
43 std::atomic_bool completed{false};
44
45 std::promise<void> completion;
46
47 void Complete()
48 {
49 if (!completed.exchange(true))
50 {
51 completion.set_value();
52 }
53 }
54};
55} // namespace
56
59 socket(ioContext)
60{
61}
62void RpcTransport::Open(const std::string& address, std::uint16_t port, bool isListener)
63{
64 asio::error_code ec;
65 const auto ipAddress = asio::ip::make_address_v4(address, ec);
66
67 if (ec)
68 {
69 throw std::system_error(ec, "Invalid IPv4 address: " + address);
70 }
71
72 if (!socket.is_open())
73 {
74 socket.open(asio::ip::udp::v4(), ec);
75 if (ec)
76 {
77 throw std::system_error(ec, "Failed to open UDP socket");
78 }
79
80 // Allow socket reuse if needed
81 socket.set_option(asio::ip::udp::socket::reuse_address(true), ec);
82 if (isListener)
83 {
84 // BIND to the specified local interface and port!
85 const asio::ip::udp::endpoint listenEndpoint(ipAddress, port);
86 socket.bind(listenEndpoint, ec);
87 if (ec)
88 {
89 socket.close();
90 throw std::system_error(ec, "Failed to bind UDP socket to port " + std::to_string(port));
91 }
92 }
93 }
94
95 if (!isListener)
96 {
97 // Target endpoint for outgoing client requests
98 endpoint = asio::ip::udp::endpoint(ipAddress, port);
99 }
100}
101/*
102void RpcTransport::Open(
103 const std::string& address,
104 std::uint16_t port,
105 bool isListener)
106{
107 asio::error_code ec;
108
109 const auto ipAddress =
110 asio::ip::make_address_v4(address, ec);
111
112 if (ec)
113 {
114 throw std::system_error(
115 ec,
116 "Invalid IPv4 address: " + address);
117 }
118
119 endpoint =
120 asio::ip::udp::endpoint(ipAddress, port);
121
122 if (!socket.is_open())
123 {
124 socket.open(
125 asio::ip::udp::v4(),
126 ec);
127
128 if (ec)
129 {
130 throw std::system_error(
131 ec,
132 "Failed to open UDP socket");
133 }
134 }
135}
136*/
137Bytes RpcTransport::Receive(std::chrono::milliseconds timeout, asio::ip::udp::endpoint& remoteEndpoint)
138{
139 if (!socket.is_open())
140 {
141 throw std::system_error(asio::error::not_connected,
142 "UDP socket is not open");
143 }
144
145 if (timeout <= std::chrono::milliseconds::zero())
146 {
147 throw std::system_error(asio::error::invalid_argument,
148 "UDP receive timeout must be greater than zero");
149 }
150
151 auto operation = std::make_shared<ReceiveOperation>(ioContext);
152 auto completion = operation->completion.get_future();
153
154 asio::post(ioContext, [&, operation, timeout]
155 {
156 operation->timer.expires_after(timeout);
157
158 socket.async_receive_from(asio::buffer(operation->buffer),
159 operation->remoteEndpoint,
160 [operation](const asio::error_code& ec, std::size_t bytesReceived)
161 {
162 operation->error = ec;
163 operation->receivedBytes = bytesReceived;
164
165 if (!operation->timedOut.load())
166 {
167 asio::error_code timerError;
168 operation->timer.cancel(timerError);
169 operation->Complete();
170 }
171 });
172
173 operation->timer.async_wait([&, operation](const asio::error_code& ec)
174 {
175 if (ec)
176 {
177 return;
178 }
179
180 operation->timedOut = true;
181
182 asio::error_code cancelError;
183 socket.cancel(cancelError);
184
185 operation->Complete();
186 });
187 });
188
189 completion.wait();
190
191 if (operation->timedOut)
192 {
193 throw RPCTimeoutError("UDP receive timed out");
194 }
195
196 if (operation->error)
197 {
198 throw std::system_error(operation->error,
199 "Failed to receive UDP datagram");
200 }
201
202 remoteEndpoint = operation->remoteEndpoint;
203
204 operation->buffer.resize(operation->receivedBytes);
205
206 return std::move(operation->buffer);
207}
208
210 const Bytes& data,
211 const asio::ip::udp::endpoint& targetEndpoint)
212{
213 if (!socket.is_open())
214 {
215 throw std::system_error(
216 asio::error::not_connected,
217 "UDP socket is not open");
218 }
219
220 asio::error_code ec;
221
222 socket.send_to(
223 asio::buffer(data),
224 targetEndpoint,
225 0,
226 ec);
227
228 if (ec)
229 {
230 throw std::system_error(
231 ec,
232 "Failed to send UDP datagram");
233 }
234}
235
236Bytes RpcTransport::SendReceive(const Bytes& data, std::chrono::milliseconds timeout)
237{
238 if (!socket.is_open())
239 {
240 throw std::system_error(asio::error::not_connected,
241 "UDP socket is not open");
242 }
243
244 if (timeout <= std::chrono::milliseconds::zero())
245 {
246 throw std::system_error(asio::error::invalid_argument,
247 "UDP receive timeout must be greater than zero");
248 }
249
250 auto operation = std::make_shared<ReceiveOperation>(ioContext);
251 auto completion = operation->completion.get_future();
252
253 const auto targetEndpoint = endpoint;
254
255 asio::post(ioContext, [&, operation, data, timeout, targetEndpoint]
256 {
257 asio::error_code sendError;
258
259 socket.send_to(asio::buffer(data), targetEndpoint, 0, sendError);
260
261 if (sendError)
262 {
263 operation->error = sendError;
264 operation->Complete();
265 return;
266 }
267
268 operation->timer.expires_after(timeout);
269
270 socket.async_receive_from(asio::buffer(operation->buffer),
271 operation->remoteEndpoint,
272 [operation](const asio::error_code& ec, std::size_t bytesReceived)
273 {
274 operation->error = ec;
275 operation->receivedBytes = bytesReceived;
276
277 if (!operation->timedOut.load())
278 {
279 asio::error_code timerError;
280 operation->timer.cancel(timerError);
281 operation->Complete();
282 }
283 });
284
285 operation->timer.async_wait([&, operation](const asio::error_code& ec)
286 {
287 if (ec)
288 {
289 return;
290 }
291
292 operation->timedOut = true;
293
294 asio::error_code cancelError;
295 socket.cancel(cancelError);
296
297 operation->Complete();
298 });
299 });
300
301 completion.wait();
302
303 if (operation->timedOut)
304 {
305 throw RPCTimeoutError("UDP receive timed out");
306 }
307
308 if (operation->error)
309 {
310 throw std::system_error(operation->error,
311 "Failed to send/receive UDP datagram");
312 }
313
314 operation->buffer.resize(operation->receivedBytes);
315
316 return std::move(operation->buffer);
317}
318
319void RpcTransport::Send(const Bytes& data)
320{
321 if (!socket.is_open())
322 {
323 throw std::system_error(
324 asio::error::not_connected,
325 "UDP socket is not open");
326 }
327
328 asio::error_code ec;
329
330 socket.send_to(
331 asio::buffer(data),
332 endpoint,
333 0,
334 ec);
335
336 if (ec)
337 {
338 throw std::system_error(
339 ec,
340 "Failed to send UDP datagram");
341 }
342}
343
344void RpcTransport::Close() noexcept
345{
346 asio::error_code ec;
347
348 if (socket.is_open())
349 {
350 socket.cancel(ec);
351 socket.close(ec);
352 }
353}
354
355bool RpcTransport::IsOpen() const noexcept
356{
357 return socket.is_open();
358}
359
360} // namespace profinet::rpc
RPC operation timed out.
Definition exceptions.h:587
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.
asio::io_context & ioContext
void Open(const std::string &address, std::uint16_t port, bool isListener=false) override
Open the UDP socket and configure the remote endpoint.
asio::ip::udp::socket socket
Bytes SendReceive(const Bytes &data, std::chrono::milliseconds timeout) override
Send a UDP datagram and wait for one response.
RpcTransport(asio::io_context &ioContext)
Create a transport using the supplied Asio execution context.
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.
asio::ip::udp::endpoint 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.
Definition protocol.h:55
constexpr std::size_t RECEIVE_BUFFER_LENGTH
Receive buffer length.
Definition util.h:43
asio::error_code error
Bytes buffer
std::atomic_bool timedOut
asio::steady_timer timer
std::promise< void > completion
std::atomic_bool completed
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.