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