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 <functional>
20 : : #include <future>
21 : : #include <memory>
22 : : #include <string>
23 : : #include <string_view>
24 : : #include <vector>
25 : :
26 : : #include "wirestead/base/visibility.hpp"
27 : : #include "wirestead/diagnostics/logger.hpp"
28 : : #include "wirestead/framer/iframer.hpp"
29 : : #include "wirestead/wrapper/context.hpp"
30 : : #include "wirestead/wrapper/runtime_stats.hpp"
31 : : #include "wirestead/wrapper/send_result.hpp"
32 : :
33 : : namespace wirestead {
34 : : namespace wrapper {
35 : :
36 : : /**
37 : : * @brief Common interface for 1:1 point-to-point communication (e.g., TcpClient, Serial, Udp)
38 : : */
39 : : class WIRESTEAD_API ChannelInterface {
40 : : public:
41 : : using MessageHandler = std::function<void(const MessageContext&)>;
42 : : using BatchMessageHandler = std::function<void(const std::vector<MessageContext>&)>;
43 : : using ConnectionHandler = std::function<void(const ConnectionContext&)>;
44 : : using ErrorHandler = std::function<void(const ErrorContext&)>;
45 : :
46 : 1177 : virtual ~ChannelInterface() = default;
47 : :
48 : : // Lifecycle
49 : :
50 : : /**
51 : : * @brief Start the channel asynchronously.
52 : : *
53 : : * @return A future that resolves to true when the channel is connected/listening,
54 : : * or false if startup failed (e.g. connection refused, max retries exhausted).
55 : : * On false, the registered on_error() callback will have been invoked with
56 : : * the specific failure reason. This future always resolves - including on a
57 : : * restart after stop() (#444) - never blocks indefinitely.
58 : : */
59 : : [[nodiscard]] virtual std::future<bool> start() = 0;
60 : :
61 : : /**
62 : : * @brief Synchronously start the channel and wait for the result.
63 : : *
64 : : * @return true if connected/listening, false on failure. On false, the registered
65 : : * on_error() callback will have been invoked with the specific failure reason.
66 : : */
67 : 40 : [[nodiscard]] virtual bool start_sync() { return start().get(); }
68 : :
69 : : /**
70 : : * @brief Request channel shutdown and wait when the calling thread permits it.
71 : : *
72 : : * Outside callers wait for shutdown completion, including concurrent callers
73 : : * that find a stop already in progress. No user callback remains running or can
74 : : * start from that run when a waiting stop returns. Repeated completed stops are no-ops.
75 : : *
76 : : * On an executor needed by shutdown (including this object's own callback),
77 : : * stop() only requests shutdown and returns without waiting. Such a return does
78 : : * not permit destruction, restart, or stopped-only configuration. An outside
79 : : * caller must observe shutdown completion first. Destroying the object from its
80 : : * own callback and concurrent destruction/use or start/stop are unsupported.
81 : : * Externally managed executors must keep making progress while callers wait;
82 : : * user callbacks must return. Internal handlers may remain if they own their
83 : : * lifetime and cannot affect a later run.
84 : : *
85 : : * Restart contract (#444): completed shutdown fully tears down the underlying transport
86 : : * (socket/serial port, queues, timers) rather than leaving it in a reusable
87 : : * half-alive state. Every on_*() callback and every config setter ever
88 : : * called on this wrapper remains in force across any number of stop()/
89 : : * start() cycles - a subsequent start() reconstructs the transport from
90 : : * this wrapper's retained configuration and re-registers all callbacks
91 : : * automatically, including config changes made while stopped. Session/
92 : : * client-id state and stats() reset on restart. Callers never need to
93 : : * re-register callbacks or reconfigure after a stop()/start() cycle.
94 : : */
95 : : virtual void stop() = 0;
96 : : virtual bool connected() const = 0;
97 : : virtual RuntimeStats stats() const = 0;
98 : : virtual void reset_stats() = 0;
99 : :
100 : : // Transmission
101 : : //
102 : : // SendResult reports local queue admission, not delivery. Check accepted()
103 : : // before reason(); contextual bool checks remain valid. Payload validation
104 : : // precedes lifecycle/readiness and capacity checks. Rejected moves retain
105 : : // their storage. Reliable waits pin their original connection and preserve
106 : : // the first stop/loss cause; callback scopes never wait for capacity.
107 : : //
108 : : // Strategy-aware API (recommended):
109 : : //
110 : : // send() / send_line()
111 : : // Behaviour depends on the configured backpressure strategy:
112 : : // BestEffort — non-blocking; drops data when the send queue is full.
113 : : // Reliable — blocks the calling thread until queue pressure is relieved,
114 : : // then attempts admission, with retries for capacity races until admission or cancellation.
115 : : //
116 : : // Explicit API (escape hatch):
117 : : //
118 : : // try_send() / try_send_line()
119 : : // Always non-blocking and always rejects a full/backpressured queue,
120 : : // regardless of strategy. Reliable channels do not enqueue into pending_
121 : : // for try_send().
122 : : // Rejects payloads that would exceed the current non-blocking queue
123 : : // threshold; payloads below MAX_BUFFER_SIZE can still be rejected when
124 : : // they exceed the current backpressure high-water budget.
125 : : // Use when you need BestEffort behaviour on a Reliable channel for a specific
126 : : // call (e.g. fire-and-forget heartbeat).
127 : : //
128 : : // send_blocking() / send_line_blocking()
129 : : // Always blocks until queue pressure is relieved, regardless of strategy.
130 : : // This call may block indefinitely while the channel remains active and
131 : : // backpressure does not clear. stop() from another thread is expected to
132 : : // unblock waiting senders.
133 : :
134 : : /**
135 : : * @brief Enqueue data for transmission, honouring the backpressure strategy.
136 : : *
137 : : * BestEffort: non-blocking, drops if the send queue is full.
138 : : * Reliable: blocks until queue pressure is relieved, then enqueues.
139 : : *
140 : : * @return Local acceptance or the actual SendRejection reason.
141 : : */
142 : : [[nodiscard]] virtual SendResult send(std::string_view data) = 0;
143 : :
144 : : /**
145 : : * @brief Enqueue a line (data + "\n") for transmission, honouring the backpressure strategy.
146 : : * @return Local acceptance or the actual SendRejection reason.
147 : : */
148 : : [[nodiscard]] virtual SendResult send_line(std::string_view line) = 0;
149 : :
150 : : /**
151 : : * @brief Block the calling thread until queue pressure is relieved, then enqueue.
152 : : *
153 : : * Ignores the configured strategy — always blocks. Prefer send() unless you need
154 : : * to override BestEffort behaviour for a specific call.
155 : : * This call may block indefinitely while the channel remains active and
156 : : * backpressure does not clear. stop() from another thread is expected to unblock
157 : : * waiting senders.
158 : : *
159 : : * @return Local acceptance or the actual SendRejection reason.
160 : : */
161 : : [[nodiscard]] virtual SendResult send_blocking(std::string_view data) = 0;
162 : :
163 : : /**
164 : : * @brief Blocking variant of send_line(). Always blocks regardless of strategy.
165 : : *
166 : : * This call may block indefinitely while the channel remains active and
167 : : * backpressure does not clear. stop() from another thread is expected to unblock
168 : : * waiting senders.
169 : : *
170 : : * @return Local acceptance or the actual SendRejection reason.
171 : : */
172 : : [[nodiscard]] virtual SendResult send_line_blocking(std::string_view line) = 0;
173 : :
174 : : /**
175 : : * @brief Non-blocking send that always drops on a full queue, ignoring strategy.
176 : : *
177 : : * Use as an escape hatch when you need drop-on-full behaviour on a Reliable channel.
178 : : * Rejects payloads that would exceed the current non-blocking queue threshold.
179 : : * A payload may be rejected even if it is below MAX_BUFFER_SIZE when it is larger
180 : : * than the current backpressure high-water budget. Use send() or send_blocking()
181 : : * when Reliable enqueue semantics are required for large payloads.
182 : : *
183 : : * @return Local acceptance or the actual SendRejection reason.
184 : : */
185 : : [[nodiscard]] virtual SendResult try_send(std::string_view data) = 0;
186 : :
187 : : /**
188 : : * @brief Non-blocking send_line that always drops on a full queue, ignoring strategy.
189 : : *
190 : : * Uses the same non-blocking queue threshold policy as try_send().
191 : : *
192 : : * @return Local acceptance or the actual SendRejection reason.
193 : : */
194 : : [[nodiscard]] virtual SendResult try_send_line(std::string_view line) = 0;
195 : :
196 : : /**
197 : : * @brief Enqueue a vector payload by transferring ownership, honouring the backpressure strategy.
198 : : *
199 : : * Ownership transfers only on acceptance; rejection leaves the vector intact. Existing string_view send APIs remain
200 : : * available for borrowed data.
201 : : *
202 : : * @return Local acceptance or the actual SendRejection reason.
203 : : */
204 : : [[nodiscard]] virtual SendResult send_move(std::vector<uint8_t>&& data) = 0;
205 : :
206 : : /**
207 : : * @brief Non-blocking ownership-transfer send.
208 : : *
209 : : * Always returns without waiting for backpressure. Rejection leaves the vector intact.
210 : : * Rejects payloads that would exceed the current non-blocking queue threshold,
211 : : * even below MAX_BUFFER_SIZE when they exceed the current high-water budget.
212 : : *
213 : : * @return Local acceptance or the actual SendRejection reason.
214 : : */
215 : : [[nodiscard]] virtual SendResult try_send_move(std::vector<uint8_t>&& data) = 0;
216 : :
217 : : /**
218 : : * @brief Enqueue an immutable shared vector payload, honouring the backpressure strategy.
219 : : *
220 : : * The shared buffer must be non-null and non-empty.
221 : : *
222 : : * @return Local acceptance or the actual SendRejection reason.
223 : : */
224 : : [[nodiscard]] virtual SendResult send_shared(std::shared_ptr<const std::vector<uint8_t>> data) = 0;
225 : :
226 : : /**
227 : : * @brief Non-blocking shared-buffer send.
228 : : *
229 : : * The shared buffer must be non-null and non-empty.
230 : : * Rejects payloads that would exceed the current non-blocking queue threshold,
231 : : * even below MAX_BUFFER_SIZE when they exceed the current high-water budget.
232 : : *
233 : : * @return Local acceptance or the actual SendRejection reason.
234 : : */
235 : : [[nodiscard]] virtual SendResult try_send_shared(std::shared_ptr<const std::vector<uint8_t>> data) = 0;
236 : :
237 : : // Event handlers
238 : : virtual ChannelInterface& on_data(MessageHandler handler) = 0;
239 : :
240 : : /** @brief Register a callback for batched data reception */
241 : : virtual ChannelInterface& on_data_batch(BatchMessageHandler handler) = 0;
242 : :
243 : : virtual ChannelInterface& on_connect(ConnectionHandler handler) = 0;
244 : : virtual ChannelInterface& on_disconnect(ConnectionHandler handler) = 0;
245 : : virtual ChannelInterface& on_error(ErrorHandler handler) = 0;
246 : :
247 : : /**
248 : : * @brief Register a callback to be notified when send queue congestion changes.
249 : : * @param handler Callback receiving the current number of queued bytes.
250 : : *
251 : : * Default implementation is a no-op. Concrete implementations that support
252 : : * backpressure reporting must override this method; otherwise the handler
253 : : * is silently discarded.
254 : : */
255 : 0 : virtual ChannelInterface& on_backpressure(std::function<void(size_t)> handler) {
256 [ # # ]: 0 : if (handler) {
257 : 0 : WIRESTEAD_LOG_WARNING("channel_interface", "on_backpressure",
258 : : "Backpressure reporting is not supported by this channel; handler is discarded.");
259 : : }
260 : 0 : return *this;
261 : : }
262 : :
263 : : /**
264 : : * @brief Set a message framer for this channel.
265 : : * @param framer The framer instance to use.
266 : : */
267 : : virtual ChannelInterface& framer(std::unique_ptr<framer::IFramer> framer) = 0;
268 : :
269 : : /**
270 : : * @brief Set a handler for complete messages extracted by the framer.
271 : : * @param handler The callback for framed messages.
272 : : */
273 : : virtual ChannelInterface& on_message(MessageHandler handler) = 0;
274 : :
275 : : /** @brief Register a callback for batched framed message reception */
276 : : virtual ChannelInterface& on_message_batch(BatchMessageHandler handler) = 0;
277 : :
278 : : // Management
279 : : virtual ChannelInterface& auto_start(bool manage = true) = 0;
280 : : };
281 : :
282 : : } // namespace wrapper
283 : : } // namespace wirestead
|