OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
var_size_prefix_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/read.hpp>
8#include <boost/asio/write.hpp>
9#include <boost/endian/conversion.hpp>
10
11#include <array>
12#include <type_traits>
13
14namespace ossia::net
15{
16
17enum class byte_order
18{
19 big_endian,
20 little_endian
21};
22
23template <int HeaderBytes, byte_order Order = byte_order::big_endian>
24struct var_size_prefix_framing
25{
26 static_assert(HeaderBytes == 1 || HeaderBytes == 2 || HeaderBytes == 4);
27
28 using header_type = std::conditional_t<
29 HeaderBytes == 1, uint8_t, std::conditional_t<HeaderBytes == 2, uint16_t, uint32_t>>;
30
31 template <typename Socket>
32 struct decoder
33 {
34 Socket& socket;
35 header_type m_header{};
36 ossia::pod_vector<char> m_data;
37 std::size_t max_frame_size
38 = HeaderBytes == 1 ? std::size_t(255)
39 : HeaderBytes == 2 ? std::size_t(65535)
40 : std::size_t(0x7FFFFFFF);
41 lifetime_token m_lifetime;
42
43 explicit decoder(Socket& socket)
44 : socket{socket}
45 {
46 if constexpr(HeaderBytes == 1)
47 m_data.reserve(256);
48 else
49 m_data.reserve(65535);
50 }
51
52 template <typename F>
53 void receive(F f)
54 {
55 boost::asio::async_read(
56 socket, boost::asio::mutable_buffer(&m_header, sizeof(header_type)),
57 boost::asio::transfer_exactly(sizeof(header_type)),
58 [this, alive = m_lifetime.watch(),
59 f = std::move(f)](boost::system::error_code ec, std::size_t sz) mutable {
60 // The socket may be gone since this read was armed; see lifetime_token.
61 if(alive.expired())
62 return;
63
64 read_size(std::move(f), ec, sz);
65 });
66 }
67
68 template <typename F>
69 void read_size(F&& f, boost::system::error_code ec, std::size_t sz)
70 {
71 if(!f.validate_stream(ec))
72 return;
73
74 if constexpr(HeaderBytes > 1)
75 {
76 if constexpr(Order == byte_order::big_endian)
77 boost::endian::big_to_native_inplace(m_header);
78 else
79 boost::endian::little_to_native_inplace(m_header);
80 }
81
82 auto packet_size = static_cast<std::size_t>(m_header);
83 if(packet_size == 0 || packet_size > max_frame_size)
84 return;
85
86 m_data.resize(packet_size, boost::container::default_init);
87 boost::asio::async_read(
88 socket, boost::asio::mutable_buffer(m_data.data(), packet_size),
89 boost::asio::transfer_exactly(packet_size),
90 [this, alive = m_lifetime.watch(),
91 f = std::move(f)](boost::system::error_code ec, std::size_t sz) mutable {
92 // The socket may be gone since this read was armed; see lifetime_token.
93 if(alive.expired())
94 return;
95
96 read_data(std::move(f), ec, sz);
97 });
98 }
99
100 template <typename F>
101 void read_data(F&& f, boost::system::error_code ec, std::size_t sz)
102 {
103 if(!f.validate_stream(ec))
104 return;
105
106 if(!ec && sz > 0)
107 {
108 try
109 {
110 f((const unsigned char*)m_data.data(), sz);
111 }
112 catch(...)
113 {
114 }
115 }
116
117 this->receive(std::move(f));
118 }
119 };
120
121 template <typename Socket>
122 struct encoder
123 {
124 Socket& socket;
125
126 void write(const char* data, std::size_t sz)
127 {
128 header_type header = static_cast<header_type>(sz);
129 if constexpr(HeaderBytes > 1)
130 {
131 if constexpr(Order == byte_order::big_endian)
132 boost::endian::native_to_big_inplace(header);
133 else
134 boost::endian::native_to_little_inplace(header);
135 }
136
137 std::array<boost::asio::const_buffer, 2> bufs = {
138 boost::asio::buffer(
139 reinterpret_cast<const char*>(&header), sizeof(header_type)),
140 boost::asio::buffer(data, sz)};
141 this->do_write(socket, bufs);
142 }
143
144 template <typename T, std::size_t N>
145 void do_write(T& sock, const std::array<boost::asio::const_buffer, N>& bufs)
146 {
147 boost::asio::write(sock, bufs);
148 }
149
150 template <typename T, std::size_t N>
151 void do_write(
152 multi_socket_writer<T>& sock,
153 const std::array<boost::asio::const_buffer, N>& bufs)
154 {
155 for(const auto& buf : bufs)
156 sock.write(buf);
157 }
158 };
159};
160
161// Common configurations
162using size_prefix_1byte_framing = var_size_prefix_framing<1>;
163using size_prefix_2byte_be_framing = var_size_prefix_framing<2, byte_order::big_endian>;
164using size_prefix_2byte_le_framing = var_size_prefix_framing<2, byte_order::little_endian>;
165using size_prefix_4byte_be_framing = var_size_prefix_framing<4, byte_order::big_endian>;
166using size_prefix_4byte_le_framing = var_size_prefix_framing<4, byte_order::little_endian>;
167
168}