Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #pragma once
18 : :
19 : : #include <concepts>
20 : : #include <functional>
21 : : #include <memory>
22 : : #include <string>
23 : : #include <string_view>
24 : : #include <utility>
25 : : #include <vector>
26 : :
27 : : #include "wirestead/base/constants.hpp"
28 : : #include "wirestead/base/visibility.hpp"
29 : : #include "wirestead/framer/iframer.hpp"
30 : : #include "wirestead/framer/length_prefix_framer.hpp"
31 : : #include "wirestead/framer/line_framer.hpp"
32 : : #include "wirestead/framer/packet_framer.hpp"
33 : : #include "wirestead/memory/safe_span.hpp"
34 : : #include "wirestead/wrapper/context.hpp"
35 : :
36 : : namespace wirestead {
37 : : namespace builder {
38 : :
39 : : /**
40 : : * @brief Concepts for callback signature validation
41 : : */
42 : : template <typename T>
43 : : concept DataHandler = std::invocable<T, const wrapper::MessageContext&>;
44 : :
45 : : template <typename T>
46 : : concept BatchDataHandler = std::invocable<T, const std::vector<wrapper::MessageContext>&>;
47 : :
48 : : template <typename T>
49 : : concept ErrorHandler = std::invocable<T, const wrapper::ErrorContext&>;
50 : :
51 : : template <typename T>
52 : : concept ConnectionHandler = std::invocable<T, const wrapper::ConnectionContext&>;
53 : :
54 : : /**
55 : : * @brief Generic Builder interface for fluent API pattern
56 : : */
57 : : template <typename T, typename Derived>
58 : : class BuilderInterface {
59 : : public:
60 : 252 : virtual ~BuilderInterface() = default;
61 : 252 : BuilderInterface() = default;
62 : : BuilderInterface(const BuilderInterface&) = default;
63 : : BuilderInterface(BuilderInterface&&) = default;
64 : : BuilderInterface& operator=(const BuilderInterface&) = default;
65 : : BuilderInterface& operator=(BuilderInterface&&) = default;
66 : :
67 : : /**
68 : : * @brief Build and return the configured product
69 : : */
70 : : virtual std::unique_ptr<T> build() = 0;
71 : :
72 : : /**
73 : : * @brief Enable auto-manage functionality
74 : : */
75 : : virtual Derived& auto_start(bool auto_start = true) = 0;
76 : :
77 : : /**
78 : : * @brief Set data handler callback
79 : : */
80 : : template <DataHandler F>
81 : 211 : Derived& on_data(F&& handler) {
82 : 211 : on_data_ = std::forward<F>(handler);
83 : 211 : return static_cast<Derived&>(*this);
84 : : }
85 : :
86 : : /**
87 : : * @brief Set batched data handler callback
88 : : */
89 : : template <BatchDataHandler F>
90 : 7 : Derived& on_data_batch(F&& handler) {
91 : 7 : on_data_batch_ = std::forward<F>(handler);
92 : 7 : return static_cast<Derived&>(*this);
93 : : }
94 : :
95 : : /**
96 : : * @brief Set data handler callback using member function pointer
97 : : */
98 : : template <typename U, typename F>
99 : : Derived& on_data(U* obj, F method) {
100 : : return on_data([obj, method](const wrapper::MessageContext& ctx) { (obj->*method)(ctx); });
101 : : }
102 : :
103 : : /**
104 : : * @brief Set connection handler callback
105 : : */
106 : : template <ConnectionHandler F>
107 : 39 : Derived& on_connect(F&& handler) {
108 : 39 : on_connect_ = std::forward<F>(handler);
109 : 39 : return static_cast<Derived&>(*this);
110 : : }
111 : :
112 : : /**
113 : : * @brief Set connection handler callback using member function pointer
114 : : */
115 : : template <typename U, typename F>
116 : : Derived& on_connect(U* obj, F method) {
117 : : return on_connect([obj, method](const wrapper::ConnectionContext& ctx) { (obj->*method)(ctx); });
118 : : }
119 : :
120 : : /**
121 : : * @brief Set disconnection handler callback
122 : : */
123 : : template <ConnectionHandler F>
124 : 18 : Derived& on_disconnect(F&& handler) {
125 : 18 : on_disconnect_ = std::forward<F>(handler);
126 : 18 : return static_cast<Derived&>(*this);
127 : : }
128 : :
129 : : /**
130 : : * @brief Set disconnection handler callback using member function pointer
131 : : */
132 : : template <typename U, typename F>
133 : : Derived& on_disconnect(U* obj, F method) {
134 : : return on_disconnect([obj, method](const wrapper::ConnectionContext& ctx) { (obj->*method)(ctx); });
135 : : }
136 : :
137 : : /**
138 : : * @brief Set error handler callback
139 : : */
140 : : template <ErrorHandler F>
141 : 215 : Derived& on_error(F&& handler) {
142 : 215 : on_error_ = std::forward<F>(handler);
143 : 215 : return static_cast<Derived&>(*this);
144 : : }
145 : :
146 : : /**
147 : : * @brief Set error handler callback using member function pointer
148 : : */
149 : : template <typename U, typename F>
150 : : Derived& on_error(U* obj, F method) {
151 : : return on_error([obj, method](const wrapper::ErrorContext& ctx) { (obj->*method)(ctx); });
152 : : }
153 : :
154 : : // Framing Support
155 : :
156 : : /**
157 : : * @brief Set a custom framer factory.
158 : : *
159 : : * The escape hatch for any protocol the two built-in framers do not fit -
160 : : * most often a length-prefixed binary layout, where PacketFramer's
161 : : * delimiter search is unsafe. See PacketFramer's warning.
162 : : */
163 : 4 : Derived& framer(std::function<std::unique_ptr<framer::IFramer>()> factory) {
164 : 4 : framer_factory_ = std::move(factory);
165 : 4 : return static_cast<Derived&>(*this);
166 : : }
167 : :
168 : : /**
169 : : * @brief Activate line-delimited framing
170 : : */
171 : 7 : Derived& use_line_framer(std::string_view delimiter = "\n", bool include_delimiter = false,
172 : : size_t max_length = 65536) {
173 : 7 : std::string delim(delimiter);
174 : 10 : framer_factory_ = [delim, include_delimiter, max_length]() {
175 : 23 : return std::make_unique<framer::LineFramer>(delim, include_delimiter, max_length);
176 : : };
177 : 7 : return static_cast<Derived&>(*this);
178 : 7 : }
179 : :
180 : : /**
181 : : * @brief Activate length-prefixed binary framing.
182 : : *
183 : : * The layout most binary protocols use, and the one use_packet_framer()
184 : : * cannot carry: the length says how many bytes to collect, so no byte value
185 : : * is special and the payload may contain anything.
186 : : *
187 : : * Requires the stream to start on a frame boundary - the normal case for TCP
188 : : * and UDS. See LengthPrefixFramer for what that rules out.
189 : : */
190 : 1 : Derived& use_length_prefix_framer(size_t prefix_bytes = 2,
191 : : framer::LengthPrefixFramer::Endian endian = framer::LengthPrefixFramer::Endian::Big,
192 : : size_t max_length = 65536, bool length_includes_prefix = false) {
193 : 1 : framer_factory_ = [prefix_bytes, endian, max_length, length_includes_prefix]() {
194 : 1 : return std::make_unique<framer::LengthPrefixFramer>(prefix_bytes, endian, max_length, length_includes_prefix);
195 : : };
196 : 1 : return static_cast<Derived&>(*this);
197 : : }
198 : :
199 : : /**
200 : : * @brief Activate binary packet framing between a start and end pattern.
201 : : *
202 : : * @warning Only safe when the payload cannot contain the end pattern - there
203 : : * is no escaping, so the first occurrence ends the frame wherever it falls
204 : : * and a binary payload gets silently truncated. Use
205 : : * use_length_prefix_framer() for length-prefixed protocols. See PacketFramer
206 : : * for the detail.
207 : : */
208 : 5 : Derived& use_packet_framer(const std::vector<uint8_t>& start_pattern, const std::vector<uint8_t>& end_pattern,
209 : : size_t max_length) {
210 : 7 : framer_factory_ = [start_pattern, end_pattern, max_length]() {
211 : 3 : return std::make_unique<framer::PacketFramer>(start_pattern, end_pattern, max_length);
212 : : };
213 : 5 : return static_cast<Derived&>(*this);
214 : : }
215 : :
216 : : /**
217 : : * @brief Set message handler callback
218 : : */
219 : : template <DataHandler F>
220 : 13 : Derived& on_message(F&& handler) {
221 : 13 : on_message_ = std::forward<F>(handler);
222 : 13 : return static_cast<Derived&>(*this);
223 : : }
224 : :
225 : : /**
226 : : * @brief Set batched framed-message handler callback
227 : : */
228 : : template <BatchDataHandler F>
229 : 7 : Derived& on_message_batch(F&& handler) {
230 : 7 : on_message_batch_ = std::forward<F>(handler);
231 : 7 : return static_cast<Derived&>(*this);
232 : : }
233 : :
234 : : /**
235 : : * @brief Set backpressure notification callback
236 : : */
237 : 11 : Derived& on_backpressure(std::function<void(size_t)> handler) {
238 : 11 : on_backpressure_ = std::move(handler);
239 : 11 : return static_cast<Derived&>(*this);
240 : : }
241 : :
242 : : /**
243 : : * @brief Set message handler callback using member function pointer
244 : : */
245 : : template <typename U, typename F>
246 : : Derived& on_message(U* obj, F method) {
247 : : return on_message([obj, method](const wrapper::MessageContext& ctx) { (obj->*method)(ctx); });
248 : : }
249 : :
250 : : /**
251 : : * @brief Set the backpressure strategy
252 : : */
253 : 14 : Derived& backpressure_strategy(base::constants::BackpressureStrategy strategy) {
254 : 14 : bp_strategy_ = strategy;
255 : 14 : bp_strategy_set_ = true;
256 : 14 : return static_cast<Derived&>(*this);
257 : : }
258 : :
259 : : /**
260 : : * @brief Set the backpressure threshold in bytes
261 : : */
262 : 14 : Derived& backpressure_threshold(size_t threshold) {
263 : 14 : bp_threshold_ = threshold;
264 : 14 : bp_threshold_set_ = true;
265 : 14 : return static_cast<Derived&>(*this);
266 : : }
267 : :
268 : : protected:
269 : 239 : size_t get_effective_backpressure_threshold() const {
270 [ + + ]: 239 : if (bp_threshold_set_) {
271 : 14 : return bp_threshold_;
272 : : }
273 [ - + ]: 225 : if (bp_strategy_ == base::constants::BackpressureStrategy::BestEffort) {
274 : 0 : return base::constants::DEFAULT_THRESHOLD_BEST_EFFORT;
275 : : }
276 : 225 : return base::constants::DEFAULT_THRESHOLD_RELIABLE;
277 : : }
278 : :
279 : : std::function<std::unique_ptr<framer::IFramer>()> framer_factory_;
280 : : std::function<void(const wrapper::MessageContext&)> on_data_;
281 : : std::function<void(const wrapper::ConnectionContext&)> on_connect_;
282 : : std::function<void(const wrapper::ConnectionContext&)> on_disconnect_;
283 : : std::function<void(const wrapper::ErrorContext&)> on_error_;
284 : : std::function<void(const wrapper::MessageContext&)> on_message_;
285 : : std::function<void(const std::vector<wrapper::MessageContext>&)> on_data_batch_;
286 : : std::function<void(const std::vector<wrapper::MessageContext>&)> on_message_batch_;
287 : : std::function<void(size_t)> on_backpressure_;
288 : :
289 : : base::constants::BackpressureStrategy bp_strategy_{base::constants::BackpressureStrategy::Reliable};
290 : : size_t bp_threshold_{0};
291 : : bool bp_strategy_set_{false};
292 : : bool bp_threshold_set_{false};
293 : : };
294 : :
295 : : } // namespace builder
296 : : } // namespace wirestead
|