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