OSSIA
Open Scenario System for Interactive Application
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 
10 namespace ossia::net
11 {
12 struct 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 
20 template <typename Socket>
21 struct 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 
142 template <typename Socket>
143 struct 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 
201 struct 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 }