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