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