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 lifetime_token m_lifetime;
32
33 explicit 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_data)
69 {
70 // Bulk scan for special bytes
71 const uint8_t* run_end = ptr;
72 while(run_end < end && *run_end != End && *run_end != Escape
73 && *run_end != Start)
74 ++run_end;
75
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 if(ptr < end)
85 process_byte(f, *ptr++);
86 }
87 else
88 {
89 process_byte(f, *ptr++);
90 }
91 }
92 }
93
94 template <typename F>
95 void process_byte(const F& f, uint8_t byte)
96 {
97 switch(m_status)
98 {
99 case waiting_start:
100 if(byte == Start)
101 {
102 m_status = reading_data;
103 m_decoded.clear();
104 }
105 break;
106
107 case reading_data:
108 switch(byte)
109 {
110 case End:
111 m_status = waiting_start;
112 if(m_decoded.size() > 0)
113 f((const unsigned char*)m_decoded.data(), m_decoded.size());
114 m_decoded.clear();
115 break;
116 case Escape:
117 m_status = reading_escape;
118 break;
119 case Start:
120 // Unexpected start mid-frame: restart
121 m_decoded.clear();
122 break;
123 default:
124 m_decoded.push_back(byte);
125 break;
126 }
127 break;
128
129 case reading_escape:
130 m_decoded.push_back(byte);
131 m_status = reading_data;
132 break;
133 }
134 }
135 };
136
137 template <typename Socket>
138 struct encoder
139 {
140 Socket& socket;
141 ossia::pod_vector<uint8_t> m_buf;
142
143 void write(const char* data, std::size_t sz)
144 {
145 m_buf.clear();
146 m_buf.reserve(sz * 2 + 2);
147 m_buf.push_back(Start);
148
149 auto* src = reinterpret_cast<const uint8_t*>(data);
150 for(std::size_t i = 0; i < sz; ++i)
151 {
152 switch(src[i])
153 {
154 case Start:
155 case End:
156 case Escape:
157 m_buf.push_back(Escape);
158 m_buf.push_back(src[i]);
159 break;
160 default:
161 m_buf.push_back(src[i]);
162 break;
163 }
164 }
165 m_buf.push_back(End);
166
167 this->do_write(socket, boost::asio::buffer(m_buf.data(), m_buf.size()));
168 }
169
170 template <typename T>
171 void do_write(T& sock, const boost::asio::const_buffer& buf)
172 {
173 boost::asio::write(sock, buf);
174 }
175
176 template <typename T>
177 void do_write(multi_socket_writer<T>& sock, const boost::asio::const_buffer& buf)
178 {
179 sock.write(buf);
180 }
181 };
182};
183
184// Standard STX/ETX with DLE escaping
185using stx_etx_framing = delimiter_framing<0x02, 0x03, 0x10>;
186
187}