OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
can_socket.hpp
1#pragma once
2#include <ossia/network/sockets/configuration.hpp>
3
4#if defined(__linux__)
5#include <ossia/network/sockets/writers.hpp>
6
7#include <boost/asio/buffer.hpp>
8#include <boost/asio/error.hpp>
9#include <boost/asio/io_context.hpp>
10#include <boost/asio/posix/stream_descriptor.hpp>
11#include <boost/asio/post.hpp>
12
13#include <linux/can.h>
14#include <linux/can/error.h>
15#include <linux/can/raw.h>
16#include <net/if.h>
17#include <sys/ioctl.h>
18#include <sys/socket.h>
19
20#include <nano_signal_slot.hpp>
21#include <unistd.h>
22
23#include <cstring>
24#include <system_error>
25
26namespace ossia::net
27{
28
31struct can_message
32{
36 uint32_t id{};
37
40 uint8_t size{};
41
43 bool extended{};
44
47 bool remote{};
48
52 bool error{};
53
55 bool fd{};
56
59 bool bitrate_switch{};
60
63 bool error_state{};
64
65 uint8_t data[CANFD_MAX_DLEN]{};
66};
67
79class can_socket
80{
81public:
82 using socket = boost::asio::posix::stream_descriptor;
83
84 can_socket(const can_configuration& conf, boost::asio::io_context& ctx)
85 : m_context{ctx}
86 , m_conf{conf}
87 , m_socket{ctx}
88 {
89 }
90
91 can_socket(const can_socket&) = delete;
92 can_socket(can_socket&&) = delete;
93 can_socket& operator=(const can_socket&) = delete;
94 can_socket& operator=(can_socket&&) = delete;
95
96 ~can_socket()
97 {
98 // Before touching the socket: close() posts close_impl() to the io_context
99 // thread, and the usual shutdown is close() then dropping the owner. Both
100 // inside basic_descriptor::close() on one impl segfaults in
101 // epoll_reactor::deregister_descriptor.
102 m_lifetime.reset();
103 close_impl();
104 }
105
107 void open()
108 {
109 int fd = ::socket(PF_CAN, SOCK_RAW, CAN_RAW);
110 if(fd < 0)
111 throw_errno("socket(PF_CAN, SOCK_RAW, CAN_RAW)");
112
113 try
114 {
115 // Interfaces are addressed by index, not by name, so the name has to be
116 // resolved first; this is also our "does this interface exist" check.
117 ifreq ifr{};
118 if(m_conf.interface_name.size() >= sizeof(ifr.ifr_name))
119 throw std::system_error{
120 std::make_error_code(std::errc::filename_too_long),
121 "CAN interface name too long: " + m_conf.interface_name};
122 std::strncpy(
123 ifr.ifr_name, m_conf.interface_name.c_str(), sizeof(ifr.ifr_name) - 1);
124 if(::ioctl(fd, SIOCGIFINDEX, &ifr) < 0)
125 throw_errno("no such CAN interface: " + m_conf.interface_name);
126
127 apply_options(fd);
128
129 sockaddr_can addr{};
130 addr.can_family = AF_CAN;
131 addr.can_ifindex = ifr.ifr_ifindex;
132 if(::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0)
133 throw_errno("bind(" + m_conf.interface_name + ")");
134 }
135 catch(...)
136 {
137 ::close(fd);
138 throw;
139 }
140
141 m_socket.assign(fd);
142 }
143
146 void connect()
147 {
148 open();
149 boost::asio::post(m_context, [this, alive = watch()] {
150 if(alive.expired())
151 return;
152 on_open();
153 });
154 }
155
156 void close()
157 {
158 if(m_socket.is_open())
159 {
160 m_socket.cancel();
161 boost::asio::post(m_context, [this, alive = watch()] {
162 if(alive.expired())
163 return;
164 close_impl();
165 on_close();
166 });
167 }
168 }
169
170 bool connected() const noexcept { return m_socket.is_open(); }
171
175 boost::system::error_code write(const can_message& msg)
176 {
177 boost::system::error_code ec;
178 if(!m_socket.is_open())
179 return boost::asio::error::not_connected;
180
181 // Classic frames must be written as `can_frame` even on a CAN_RAW_FD_FRAMES
182 // socket: the kernel decides which kind of frame it received from the size
183 // of the write, so sending a 72-byte canfd_frame with fd=false would turn
184 // it into an FD frame on the bus.
185 if(msg.fd)
186 {
187 canfd_frame f{};
188 f.can_id = raw_id(msg);
189 f.len = msg.size > CANFD_MAX_DLEN ? CANFD_MAX_DLEN : msg.size;
190 if(msg.bitrate_switch)
191 f.flags |= CANFD_BRS;
192 if(msg.error_state)
193 f.flags |= CANFD_ESI;
194 std::memcpy(f.data, msg.data, f.len);
195 m_socket.write_some(boost::asio::buffer(&f, sizeof(f)), ec);
196 }
197 else
198 {
199 can_frame f{};
200 f.can_id = raw_id(msg);
201 f.can_dlc = msg.size > CAN_MAX_DLEN ? CAN_MAX_DLEN : msg.size;
202 if(!msg.remote)
203 std::memcpy(f.data, msg.data, f.can_dlc);
204 m_socket.write_some(boost::asio::buffer(&f, sizeof(f)), ec);
205 }
206
207 if(ec)
208 on_write_error(ec);
209 return ec;
210 }
211
213 template <typename F>
214 void receive(F f)
215 {
216 struct proc : stream_processor<can_socket, F>
217 {
218 };
219 do_receive(proc{*this, std::move(f)});
220 }
221
222 Nano::Signal<void()> on_open;
223 Nano::Signal<void()> on_close;
224 Nano::Signal<void()> on_fail;
225
227 Nano::Signal<void(boost::system::error_code)> on_write_error;
228
229 boost::asio::io_context& m_context;
230 can_configuration m_conf;
231 socket m_socket;
232
233private:
234 // Every handler below holds a weak reference to this token and gives up if it
235 // has expired: closing a descriptor does not make asio forget the operations
236 // that were pending on it. See lifetime_token.
237 lifetime_token m_lifetime;
238 std::weak_ptr<void> watch() const noexcept { return m_lifetime.watch(); }
239
240 // CANFD_MTU (72) is the largest datagram that can be received here, as we
241 // never enable CAN_RAW_XL_FRAMES; the buffer is sized for a CAN XL frame
242 // anyway so that adding XL later needs no change here, and so that an
243 // unexpectedly large datagram is *detected* (unknown size -> dropped) rather
244 // than silently truncated into something that parses as an FD frame.
245 static constexpr std::size_t max_frame_size = 2060;
246 alignas(64) uint8_t m_readbuf[max_frame_size];
247
248 [[noreturn]] static void throw_errno(const std::string& what)
249 {
250 throw std::system_error{errno, std::generic_category(), what};
251 }
252
253 static uint32_t raw_id(const can_message& msg) noexcept
254 {
255 uint32_t id = msg.extended ? (msg.id & CAN_EFF_MASK) | CAN_EFF_FLAG
256 : (msg.id & CAN_SFF_MASK);
257 // RTR does not exist in CAN FD: the flag bit is reused there, so setting it
258 // on an FD frame would corrupt the identifier.
259 if(msg.remote && !msg.fd)
260 id |= CAN_RTR_FLAG;
261 return id;
262 }
263
264 void apply_options(int fd)
265 {
266 // Applied before bind(): older kernels additionally refuse
267 // CAN_RAW_FD_FRAMES on an already-bound socket whose device has an MTU
268 // below CANFD_MTU, and doing everything here keeps open() atomic -- either
269 // the socket comes back fully configured or it throws.
270 if(m_conf.fd)
271 {
272 const int on = 1;
273 if(::setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &on, sizeof(on)) < 0)
274 throw_errno("this kernel or interface does not support CAN FD");
275 }
276
277 if(!m_conf.filters.empty())
278 {
279 // An empty list is *not* the same as not calling this: setting zero
280 // filters tells the kernel to drop every non-error frame.
281 std::vector<can_filter> filters;
282 filters.reserve(m_conf.filters.size());
283 for(auto& f : m_conf.filters)
284 filters.push_back(can_filter{.can_id = f.id, .can_mask = f.mask});
285
286 if(::setsockopt(
287 fd, SOL_CAN_RAW, CAN_RAW_FILTER, filters.data(),
288 filters.size() * sizeof(can_filter))
289 < 0)
290 throw_errno("could not apply the CAN filters");
291 }
292
293 {
294 const can_err_mask_t mask = m_conf.error_frames ? CAN_ERR_MASK : 0;
295 if(::setsockopt(fd, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &mask, sizeof(mask)) < 0)
296 throw_errno("could not set the CAN error filter");
297 }
298
299 {
300 const int loopback = m_conf.loopback ? 1 : 0;
301 if(::setsockopt(fd, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback))
302 < 0)
303 throw_errno("could not set CAN_RAW_LOOPBACK");
304 }
305
306 {
307 const int own = m_conf.receive_own_messages ? 1 : 0;
308 if(::setsockopt(fd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &own, sizeof(own)) < 0)
309 throw_errno("could not set CAN_RAW_RECV_OWN_MSGS");
310 }
311 }
312
313 void close_impl() noexcept
314 {
315 if(m_socket.is_open())
316 {
317 boost::system::error_code ec;
318 m_socket.close(ec);
319 }
320 }
321
322 template <typename F>
323 void do_receive(F f)
324 {
325 m_socket.async_read_some(
326 boost::asio::buffer(m_readbuf),
327 [this, alive = watch(),
328 f = std::move(f)](boost::system::error_code ec, std::size_t sz) mutable {
329 if(alive.expired())
330 return;
331
332 if(!f.validate_stream(ec))
333 return;
334
335 // Unlike no_framing, which forwards whatever it read on any other error,
336 // we stop here: on a CAN socket the remaining errors are fatal for the
337 // socket (ENETDOWN when the interface goes down, ENODEV when it is
338 // removed) and re-arming would spin.
339 if(ec)
340 {
341 on_fail();
342 return;
343 }
344
345 if(can_message msg; decode(m_readbuf, sz, msg))
346 f(msg);
347
348 do_receive(std::move(f));
349 });
350 }
351
353
358 static bool decode(const uint8_t* data, std::size_t sz, can_message& msg) noexcept
359 {
360 uint32_t raw{};
361 if(sz == CAN_MTU)
362 {
363 can_frame f{};
364 std::memcpy(&f, data, sizeof(f));
365 raw = f.can_id;
366 msg.fd = false;
367 msg.remote = (raw & CAN_RTR_FLAG) != 0;
368 msg.size = f.can_dlc > CAN_MAX_DLEN ? CAN_MAX_DLEN : f.can_dlc;
369 if(!msg.remote)
370 std::memcpy(msg.data, f.data, msg.size);
371 }
372 else if(sz == CANFD_MTU)
373 {
374 canfd_frame f{};
375 std::memcpy(&f, data, sizeof(f));
376 raw = f.can_id;
377 msg.fd = true;
378 msg.remote = false; // no RTR in CAN FD
379 msg.bitrate_switch = (f.flags & CANFD_BRS) != 0;
380 msg.error_state = (f.flags & CANFD_ESI) != 0;
381 msg.size = f.len > CANFD_MAX_DLEN ? CANFD_MAX_DLEN : f.len;
382 std::memcpy(msg.data, f.data, msg.size);
383 }
384 else
385 {
386 // Neither CAN_MTU nor CANFD_MTU: a frame kind we do not know how to read
387 // (CAN XL, or a future layout). Dropping it is better than reinterpreting
388 // its bytes as one we do know.
389 return false;
390 }
391
392 msg.error = (raw & CAN_ERR_FLAG) != 0;
393 if(msg.error)
394 {
395 // For error frames the identifier is not an identifier at all: it is a
396 // bitmask of the error classes (CAN_ERR_TX_TIMEOUT, CAN_ERR_BUSOFF...).
397 msg.extended = false;
398 msg.id = raw & CAN_ERR_MASK;
399 }
400 else
401 {
402 msg.extended = (raw & CAN_EFF_FLAG) != 0;
403 msg.id = msg.extended ? (raw & CAN_EFF_MASK) : (raw & CAN_SFF_MASK);
404 }
405 return true;
406 }
407};
408
409}
410#endif