OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
stx_etx_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 <uint8_t Start = 0x02, uint8_t End = 0x03, uint8_t Escape = 0x10>
13struct delimiter_framing
14{
15 static_assert(
16 Start != End && Start != Escape && End != Escape,
17 "start, end, and escape bytes must be distinct");
18
19 template <typename Socket>
20 struct 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_start,
28 reading_data,
29 reading_escape
30 } m_status{waiting_start};
31
32 explicit 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,
44 f = std::move(f)](boost::system::error_code ec, std::size_t sz) mutable {
45 if(!f.validate_stream(ec))
46 return;
47
48 if(sz > 0)
49 process_bytes(f, m_readbuf, sz);
50
51 receive(std::move(f));
52 });
53 }
54
55 template <typename F>
56 void process_bytes(const F& f, const uint8_t* data, std::size_t sz)
57 {
58 const uint8_t* ptr = data;
59 const uint8_t* end = data + sz;
60
61 while(ptr < end)
62 {
63 if(m_status == reading_data)
64 {
65 // Bulk scan for special bytes
66 const uint8_t* run_end = ptr;
67 while(run_end < end && *run_end != End && *run_end != Escape
68 && *run_end != Start)
69 ++run_end;
70
71 if(run_end > ptr)
72 {
73 m_decoded.insert(
74 m_decoded.end(), reinterpret_cast<const char*>(ptr),
75 reinterpret_cast<const char*>(run_end));
76 ptr = run_end;
77 }
78
79 if(ptr < end)
80 process_byte(f, *ptr++);
81 }
82 else
83 {
84 process_byte(f, *ptr++);
85 }
86 }
87 }
88
89 template <typename F>
90 void process_byte(const F& f, uint8_t byte)
91 {
92 switch(m_status)
93 {
94 case waiting_start:
95 if(byte == Start)
96 {
97 m_status = reading_data;
98 m_decoded.clear();
99 }
100 break;
101
102 case reading_data:
103 switch(byte)
104 {
105 case End:
106 m_status = waiting_start;
107 if(m_decoded.size() > 0)
108 f((const unsigned char*)m_decoded.data(), m_decoded.size());
109 m_decoded.clear();
110 break;
111 case Escape:
112 m_status = reading_escape;
113 break;
114 case Start:
115 // Unexpected start mid-frame: restart
116 m_decoded.clear();
117 break;
118 default:
119 m_decoded.push_back(byte);
120 break;
121 }
122 break;
123
124 case reading_escape:
125 m_decoded.push_back(byte);
126 m_status = reading_data;
127 break;
128 }
129 }
130 };
131
132 template <typename Socket>
133 struct encoder
134 {
135 Socket& socket;
136 ossia::pod_vector<uint8_t> m_buf;
137
138 void write(const char* data, std::size_t sz)
139 {
140 m_buf.clear();
141 m_buf.reserve(sz * 2 + 2);
142 m_buf.push_back(Start);
143
144 auto* src = reinterpret_cast<const uint8_t*>(data);
145 for(std::size_t i = 0; i < sz; ++i)
146 {
147 switch(src[i])
148 {
149 case Start:
150 case End:
151 case Escape:
152 m_buf.push_back(Escape);
153 m_buf.push_back(src[i]);
154 break;
155 default:
156 m_buf.push_back(src[i]);
157 break;
158 }
159 }
160 m_buf.push_back(End);
161
162 this->do_write(socket, boost::asio::buffer(m_buf.data(), m_buf.size()));
163 }
164
165 template <typename T>
166 void do_write(T& sock, const boost::asio::const_buffer& buf)
167 {
168 boost::asio::write(sock, buf);
169 }
170
171 template <typename T>
172 void do_write(multi_socket_writer<T>& sock, const boost::asio::const_buffer& buf)
173 {
174 sock.write(buf);
175 }
176 };
177};
178
179// Standard STX/ETX with DLE escaping
180using stx_etx_framing = delimiter_framing<0x02, 0x03, 0x10>;
181
182}