OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
slip_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{
11struct slip
12{
13 static const constexpr uint8_t eot = 192;
14 static const constexpr uint8_t esc = 219;
15 static const constexpr uint8_t esc_end = 220;
16 static const constexpr uint8_t esc_esc = 221;
17};
18
19template <typename Socket>
20struct slip_decoder
21{
22 Socket& socket;
23 alignas(64) uint8_t m_readbuf[4096];
24 ossia::pod_vector<char> m_decoded;
25 enum
26 {
27 waiting,
28 reading_char,
29 reading_esc
30 } m_status{waiting};
31 lifetime_token m_lifetime;
32
33 explicit slip_decoder(Socket& socket)
34 : socket{socket}
35 {
36 m_decoded.reserve(1024);
37 }
38
39 template <typename F>
40 void receive(F f)
41 {
42 socket.async_read_some(
43 boost::asio::buffer(m_readbuf),
44 [this, alive = m_lifetime.watch(),
45 f = std::move(f)](boost::system::error_code ec, std::size_t sz) mutable {
46 // The socket may be gone since this read was armed; see lifetime_token.
47 if(alive.expired())
48 return;
49
50 if(!f.validate_stream(ec))
51 return;
52
53 if(sz > 0)
54 process_bytes(f, m_readbuf, sz);
55
56 receive(std::move(f));
57 });
58 }
59
60 template <typename F>
61 void process_bytes(const F& f, const uint8_t* data, std::size_t sz)
62 {
63 const uint8_t* ptr = data;
64 const uint8_t* end = data + sz;
65
66 while(ptr < end)
67 {
68 if(m_status == reading_char)
69 {
70 // Scan for next special byte
71 const uint8_t* run_end = ptr;
72 while(run_end < end && *run_end != slip::eot && *run_end != slip::esc)
73 ++run_end;
74
75 // Bulk append normal bytes
76 if(run_end > ptr)
77 {
78 m_decoded.insert(
79 m_decoded.end(), reinterpret_cast<const char*>(ptr),
80 reinterpret_cast<const char*>(run_end));
81 ptr = run_end;
82 }
83
84 // Process the special byte if any
85 if(ptr < end)
86 process_byte(f, *ptr++);
87 }
88 else
89 {
90 // waiting or reading_esc: one byte at a time
91 process_byte(f, *ptr++);
92 }
93 }
94 }
95
96 template <typename F>
97 void process_byte(const F& f, const uint8_t next_char)
98 {
99 switch(m_status)
100 {
101 case waiting: {
102 switch(next_char)
103 {
104 // Start of a message
105 case slip::eot:
106 m_status = reading_char;
107 break;
108
109 // Any other character than eot is an error, do nothing.
110 default:
111 m_decoded.clear();
112 break;
113 }
114 break;
115 }
116
117 case reading_char: {
118 switch(next_char)
119 {
120 // End of a message, we can process it
121 case slip::eot:
122 m_status = waiting;
123 if(m_decoded.size() > 0)
124 {
125 f((const unsigned char*)m_decoded.data(), m_decoded.size());
126 }
127 m_decoded.clear();
128 break;
129
130 // Start of an escape sequence
131 case slip::esc:
132 m_status = reading_esc;
133 break;
134
135 // Normal characters
136 default:
137 m_decoded.push_back(next_char);
138 break;
139 }
140 break;
141 }
142
143 case reading_esc: {
144 switch(next_char)
145 {
146 case slip::esc_end:
147 m_status = reading_char;
148 m_decoded.push_back(slip::eot);
149 break;
150
151 case slip::esc_esc:
152 m_status = reading_char;
153 m_decoded.push_back(slip::esc);
154 break;
155
156 // Invalid case, reset the decoder
157 default:
158 m_status = waiting;
159 m_decoded.clear();
160 break;
161 }
162
163 break;
164 }
165 }
166 }
167};
168
169template <typename Socket>
170struct slip_encoder
171{
172 Socket& socket;
173 ossia::pod_vector<uint8_t> m_buf;
174
175 // Encodes entire SLIP frame into a buffer, then writes once.
176 void write(const char* data, std::size_t sz)
177 {
178 m_buf.clear();
179 m_buf.reserve(sz * 2 + 2);
180 m_buf.push_back(slip::eot);
181
182 auto* src = reinterpret_cast<const uint8_t*>(data);
183 for(std::size_t i = 0; i < sz; ++i)
184 {
185 switch(src[i])
186 {
187 case slip::eot:
188 m_buf.push_back(slip::esc);
189 m_buf.push_back(slip::esc_end);
190 break;
191 case slip::esc:
192 m_buf.push_back(slip::esc);
193 m_buf.push_back(slip::esc_esc);
194 break;
195 default:
196 m_buf.push_back(src[i]);
197 break;
198 }
199 }
200 m_buf.push_back(slip::eot);
201
202 this->do_write(socket, boost::asio::buffer(m_buf.data(), m_buf.size()));
203 }
204
205 template <typename T>
206 void do_write(T& sock, const boost::asio::const_buffer& buf)
207 {
208 boost::asio::write(sock, buf);
209 }
210
211 template <typename T>
212 void do_write(multi_socket_writer<T>& sock, const boost::asio::const_buffer& buf)
213 {
214 sock.write(buf);
215 }
216};
217
218struct slip_framing
219{
220 template <typename Socket>
221 using encoder = slip_encoder<Socket>;
222 template <typename Socket>
223 using decoder = slip_decoder<Socket>;
224};
225}