OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
cobs_framing.hpp
1#pragma once
2#include <ossia/detail/pod_vector.hpp>
3#include <ossia/network/sockets/writers.hpp>
4
5#include <boost/asio/buffer.hpp>
6#include <boost/asio/error.hpp>
7#include <boost/asio/write.hpp>
8
9namespace ossia::net
10{
11
12template <typename Socket>
13struct cobs_decoder
14{
15 Socket& socket;
16 alignas(64) uint8_t m_readbuf[4096];
17 ossia::pod_vector<uint8_t> m_encoded;
18 ossia::pod_vector<char> m_decoded;
19 lifetime_token m_lifetime;
20
21 explicit cobs_decoder(Socket& socket)
22 : socket{socket}
23 {
24 m_encoded.reserve(1024);
25 m_decoded.reserve(1024);
26 }
27
28 template <typename F>
29 void receive(F f)
30 {
31 socket.async_read_some(
32 boost::asio::buffer(m_readbuf),
33 [this, alive = m_lifetime.watch(),
34 f = std::move(f)](boost::system::error_code ec, std::size_t sz) mutable {
35 // The socket may be gone since this read was armed; see lifetime_token.
36 if(alive.expired())
37 return;
38
39 if(!f.validate_stream(ec))
40 return;
41
42 if(sz > 0)
43 process_bytes(f, m_readbuf, sz);
44
45 receive(std::move(f));
46 });
47 }
48
49 template <typename F>
50 void process_bytes(const F& f, const uint8_t* data, std::size_t sz)
51 {
52 const uint8_t* ptr = data;
53 const uint8_t* end = data + sz;
54
55 while(ptr < end)
56 {
57 // Scan for next 0x00 delimiter
58 const uint8_t* delim = ptr;
59 while(delim < end && *delim != 0x00)
60 ++delim;
61
62 // Bulk append non-delimiter bytes
63 if(delim > ptr)
64 m_encoded.insert(m_encoded.end(), ptr, delim);
65
66 ptr = delim;
67
68 // Process delimiter
69 if(ptr < end)
70 {
71 ++ptr; // consume the 0x00
72 if(!m_encoded.empty())
73 {
74 if(decode_frame())
75 f((const unsigned char*)m_decoded.data(), m_decoded.size());
76 m_encoded.clear();
77 }
78 }
79 }
80 }
81
82 bool decode_frame()
83 {
84 m_decoded.clear();
85 const uint8_t* ptr = m_encoded.data();
86 const uint8_t* end = ptr + m_encoded.size();
87
88 while(ptr < end)
89 {
90 uint8_t code = *ptr++;
91 if(code == 0)
92 return false;
93
94 uint8_t count = code - 1;
95 if(ptr + count > end)
96 return false;
97
98 for(uint8_t j = 0; j < count; ++j)
99 m_decoded.push_back(static_cast<char>(*ptr++));
100
101 // If code < 0xFF, a zero byte was removed here (unless at end of packet)
102 if(code < 0xFF && ptr < end)
103 m_decoded.push_back(0);
104 }
105
106 return m_decoded.size() > 0;
107 }
108};
109
110template <typename Socket>
111struct cobs_encoder
112{
113 Socket& socket;
114 ossia::pod_vector<uint8_t> m_buf;
115
116 void write(const char* data, std::size_t sz)
117 {
118 m_buf.clear();
119 m_buf.reserve(sz + sz / 254 + 3);
120
121 const auto* src = reinterpret_cast<const uint8_t*>(data);
122
123 std::size_t code_idx = m_buf.size();
124 m_buf.push_back(0); // placeholder for first code byte
125 uint8_t code = 1;
126
127 for(std::size_t i = 0; i < sz; ++i)
128 {
129 if(src[i] == 0)
130 {
131 m_buf[code_idx] = code;
132 code_idx = m_buf.size();
133 m_buf.push_back(0);
134 code = 1;
135 }
136 else
137 {
138 m_buf.push_back(src[i]);
139 ++code;
140 if(code == 0xFF)
141 {
142 m_buf[code_idx] = code;
143 code_idx = m_buf.size();
144 m_buf.push_back(0);
145 code = 1;
146 }
147 }
148 }
149
150 m_buf[code_idx] = code;
151 m_buf.push_back(0x00); // frame delimiter
152
153 this->do_write(socket, boost::asio::buffer(m_buf.data(), m_buf.size()));
154 }
155
156 template <typename T>
157 void do_write(T& sock, const boost::asio::const_buffer& buf)
158 {
159 boost::asio::write(sock, buf);
160 }
161
162 template <typename T>
163 void do_write(multi_socket_writer<T>& sock, const boost::asio::const_buffer& buf)
164 {
165 sock.write(buf);
166 }
167};
168
169struct cobs_framing
170{
171 template <typename Socket>
172 using encoder = cobs_encoder<Socket>;
173 template <typename Socket>
174 using decoder = cobs_decoder<Socket>;
175};
176
177}