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/error.hpp>
6#include <boost/asio/read.hpp>
7#include <boost/asio/streambuf.hpp>
8#include <boost/asio/write.hpp>
9
10namespace ossia::net
11{
12struct slip
13{
14 static const constexpr uint8_t eot = 192;
15 static const constexpr uint8_t esc = 219;
16 static const constexpr uint8_t esc_end = 220;
17 static const constexpr uint8_t esc_esc = 221;
18};
19
20template <typename Socket>
21struct slip_decoder
22{
23 Socket& socket;
24 boost::asio::streambuf m_data;
25 ossia::pod_vector<char> m_decoded;
26 enum
27 {
28 waiting,
29 reading_char,
30 reading_esc
31 } m_status{waiting};
32
33 explicit slip_decoder(Socket& socket)
34 : socket{socket}
35 {
36 }
37
38 template <typename F>
39 void receive(F f)
40 {
41 socket.async_read_some(
42 boost::asio::mutable_buffer(m_data.prepare(1024)),
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 {
49 process_bytes(f, sz);
50 }
51
52 receive(std::move(f));
53 });
54 }
55
56 template <typename F>
57 void process_bytes(const F& f, std::size_t sz)
58 {
59 auto begin = (const uint8_t*)m_data.data().data();
60 for(std::size_t i = 0; i < sz; i++)
61 {
62 const uint8_t next_char = *begin;
63 process_byte(f, next_char);
64 ++begin;
65 }
66 m_data.consume(sz);
67 }
68
69 template <typename F>
70 void process_byte(const F& f, const uint8_t next_char)
71 {
72 switch(m_status)
73 {
74 case waiting: {
75 switch(next_char)
76 {
77 // Start of a message
78 case slip::eot:
79 m_status = reading_char;
80 break;
81
82 // Any other character than eot is an error, do nothing.
83 default:
84 m_decoded.clear();
85 break;
86 }
87 break;
88 }
89
90 case reading_char: {
91 switch(next_char)
92 {
93 // End of a message, we can process it
94 case slip::eot:
95 m_status = waiting;
96 if(m_decoded.size() > 0)
97 {
98 f((const unsigned char*)m_decoded.data(), m_decoded.size());
99 }
100 m_decoded.clear();
101 break;
102
103 // Start of an escape sequence
104 case slip::esc:
105 m_status = reading_esc;
106 break;
107
108 // Normal characters
109 default:
110 m_decoded.push_back(next_char);
111 break;
112 }
113 break;
114 }
115
116 case reading_esc: {
117 switch(next_char)
118 {
119 case slip::esc_end:
120 m_status = reading_char;
121 m_decoded.push_back(slip::eot);
122 break;
123
124 case slip::esc_esc:
125 m_status = reading_char;
126 m_decoded.push_back(slip::esc);
127 break;
128
129 // Invalid case, reset the decoder
130 default:
131 m_status = waiting;
132 m_decoded.clear();
133 break;
134 }
135
136 break;
137 }
138 }
139 }
140};
141
142template <typename Socket>
143struct slip_encoder
144{
145 Socket& socket;
146
147 // This is tailored for OSC which uses double-ended encoding
148 void write(const char* data, std::size_t sz)
149 {
150 this->write(socket, boost::asio::buffer(&slip::eot, 1));
151
152 const uint8_t* begin = reinterpret_cast<const uint8_t*>(data);
153 const uint8_t* end = begin + sz;
154 while(begin < end)
155 {
156 std::size_t written = this->write(begin, end);
157 begin += written;
158 }
159 this->write(socket, boost::asio::buffer(&slip::eot, 1));
160 }
161
162 std::size_t write(const uint8_t* begin, const uint8_t* end)
163 {
164 const uint8_t byte = *begin;
165 switch(byte)
166 {
167 case slip::eot: {
168 const uint8_t data[2] = {slip::esc, slip::esc_end};
169 this->write(socket, boost::asio::buffer(data, 2));
170 return 1;
171 }
172 case slip::esc: {
173 const uint8_t data[2] = {slip::esc, slip::esc_esc};
174 this->write(socket, boost::asio::buffer(data, 2));
175 return 1;
176 }
177 default: {
178 auto sub_end = begin + 1;
179 while(sub_end != end && *sub_end != slip::eot && *sub_end != slip::esc)
180 ++sub_end;
181
182 this->write(socket, boost::asio::buffer(begin, sub_end - begin));
183 return sub_end - begin;
184 }
185 }
186 }
187
188 template <typename T>
189 void write(T& sock, const boost::asio::const_buffer& buf)
190 {
191 boost::asio::write(sock, buf);
192 }
193
194 template <typename T>
195 void write(multi_socket_writer<T>& sock, const boost::asio::const_buffer& buf)
196 {
197 sock.write(buf);
198 }
199};
200
201struct slip_framing
202{
203 template <typename Socket>
204 using encoder = slip_encoder<Socket>;
205 template <typename Socket>
206 using decoder = slip_decoder<Socket>;
207};
208}