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 <boost/asio.hpp>
20 : : #include <functional>
21 : : #include <memory>
22 : : #include <optional>
23 : : #include <string>
24 : : #include <vector>
25 : :
26 : : #include "wirestead/base/platform.hpp"
27 : : #include "wirestead/base/visibility.hpp"
28 : :
29 : : namespace wirestead {
30 : : namespace interface {
31 : :
32 : : namespace net = boost::asio;
33 : :
34 : : /**
35 : : * @brief An interface abstracting Boost.Asio's serial_port for testability.
36 : : * This is an internal interface used for dependency injection and mocking.
37 : : */
38 : : class WIRESTEAD_API SerialPortInterface {
39 : : public:
40 : 458 : virtual ~SerialPortInterface() = default;
41 : :
42 : : virtual void open(const std::string& device, boost::system::error_code& ec) = 0;
43 : : virtual bool is_open() const = 0;
44 : : virtual void close(boost::system::error_code& ec) = 0;
45 : :
46 : : virtual void set_option(const net::serial_port_base::baud_rate& option, boost::system::error_code& ec) = 0;
47 : : virtual void set_option(const net::serial_port_base::character_size& option, boost::system::error_code& ec) = 0;
48 : : virtual void set_option(const net::serial_port_base::stop_bits& option, boost::system::error_code& ec) = 0;
49 : : virtual void set_option(const net::serial_port_base::parity& option, boost::system::error_code& ec) = 0;
50 : : virtual void set_option(const net::serial_port_base::flow_control& option, boost::system::error_code& ec) = 0;
51 : :
52 : : // Ask the driver to stop batching received bytes behind its own timer.
53 : : // Returns whether the request took effect. Not an error when it does not -
54 : : // only some drivers (notably USB serial) have such a timer to disable, so
55 : : // the caller logs the outcome and carries on. Defaults to "unsupported" so
56 : : // test doubles inherit it for free.
57 : 194 : virtual bool set_low_latency() { return false; }
58 : :
59 : : // Put the port into half-duplex RS-485, letting the driver drive the
60 : : // transceiver's direction pin around each write. Delays are milliseconds.
61 : : // Returns whether the request took effect; adapters that switch direction in
62 : : // hardware refuse it, and that is not an error. Primitives rather than a
63 : : // struct so this interface stays independent of the config layer - there is
64 : : // exactly one caller.
65 : 0 : virtual bool set_rs485(bool rts_on_send, bool rx_during_tx, unsigned delay_before_ms, unsigned delay_after_ms) {
66 : : (void)rts_on_send;
67 : : (void)rx_during_tx;
68 : : (void)delay_before_ms;
69 : : (void)delay_after_ms;
70 : 0 : return false;
71 : : }
72 : :
73 : : // Assert or clear DTR/RTS. std::nullopt leaves a line untouched, which is
74 : : // distinct from driving it low. Returns whether every requested change was
75 : : // applied.
76 : 0 : virtual bool set_modem_lines(std::optional<bool> dtr, std::optional<bool> rts) {
77 : : (void)dtr;
78 : : (void)rts;
79 : 0 : return false;
80 : : }
81 : :
82 : : // Linux: assert DTR and disable hangup-on-close. A refusal must not be
83 : : // reported as Connected when the caller explicitly requested retention.
84 : 0 : virtual bool keep_dtr_on_close() { return false; }
85 : :
86 : : virtual void async_read_some(const net::mutable_buffer& buffer,
87 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) = 0;
88 : : virtual void async_write(const net::const_buffer& buffer,
89 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) = 0;
90 : :
91 : : // Scatter-gather write: sends every buffer in `buffers` as one operation,
92 : : // completing once with the total byte count or the first error. Draining
93 : : // several queued messages this way turns N send syscalls into one.
94 : : //
95 : : // The memory the buffers point at must stay valid until the handler runs.
96 : : //
97 : : // The default flattens into one buffer and delegates to the single-buffer
98 : : // overload above: correct, but it copies, so implementations backed by a real
99 : : // socket override it. Test doubles can rely on the default.
100 : : virtual void async_write(const std::vector<net::const_buffer>& buffers,
101 : : std::function<void(const boost::system::error_code&, std::size_t)> handler);
102 : : };
103 : :
104 : : // Flattens into one contiguous buffer and delegates. Copies, which is why a
105 : : // socket-backed implementation overrides this; kept here so test doubles and
106 : : // any not-yet-converted implementation stay correct for free. `flat` is
107 : : // owned by the completion lambda, so it outlives the delegated write.
108 : 92 : inline void SerialPortInterface::async_write(
109 : : const std::vector<net::const_buffer>& buffers,
110 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) {
111 [ + - ]: 92 : auto flat = std::make_shared<std::vector<unsigned char>>();
112 : 92 : std::size_t total = 0;
113 [ + + ]: 185 : for (const auto& b : buffers) total += b.size();
114 : 92 : flat->reserve(total);
115 [ + + ]: 185 : for (const auto& b : buffers) {
116 : 93 : const auto* p = static_cast<const unsigned char*>(b.data());
117 : 93 : flat->insert(flat->end(), p, p + b.size());
118 : : }
119 : 92 : async_write(net::const_buffer(flat->data(), flat->size()),
120 : 184 : [flat, handler = std::move(handler)](const boost::system::error_code& ec, std::size_t n) {
121 [ + - ]: 92 : if (handler) handler(ec, n);
122 : 92 : });
123 : 92 : }
124 : :
125 : : } // namespace interface
126 : : } // namespace wirestead
|