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 : 44 : 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 : 4 : 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 : : virtual void async_read_some(const net::mutable_buffer& buffer,
83 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) = 0;
84 : : virtual void async_write(const net::const_buffer& buffer,
85 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) = 0;
86 : :
87 : : // Scatter-gather write: sends every buffer in `buffers` as one operation,
88 : : // completing once with the total byte count or the first error. Draining
89 : : // several queued messages this way turns N send syscalls into one.
90 : : //
91 : : // The memory the buffers point at must stay valid until the handler runs.
92 : : //
93 : : // The default flattens into one buffer and delegates to the single-buffer
94 : : // overload above: correct, but it copies, so implementations backed by a real
95 : : // socket override it. Test doubles can rely on the default.
96 : : virtual void async_write(const std::vector<net::const_buffer>& buffers,
97 : : std::function<void(const boost::system::error_code&, std::size_t)> handler);
98 : : };
99 : :
100 : : // Flattens into one contiguous buffer and delegates. Copies, which is why a
101 : : // socket-backed implementation overrides this; kept here so test doubles and
102 : : // any not-yet-converted implementation stay correct for free. `flat` is
103 : : // owned by the completion lambda, so it outlives the delegated write.
104 : 7 : inline void SerialPortInterface::async_write(
105 : : const std::vector<net::const_buffer>& buffers,
106 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) {
107 [ + - ]: 7 : auto flat = std::make_shared<std::vector<unsigned char>>();
108 : 7 : std::size_t total = 0;
109 [ + + ]: 14 : for (const auto& b : buffers) total += b.size();
110 : 7 : flat->reserve(total);
111 [ + + ]: 14 : for (const auto& b : buffers) {
112 : 7 : const auto* p = static_cast<const unsigned char*>(b.data());
113 : 7 : flat->insert(flat->end(), p, p + b.size());
114 : : }
115 : 7 : async_write(net::const_buffer(flat->data(), flat->size()),
116 : 14 : [flat, handler = std::move(handler)](const boost::system::error_code& ec, std::size_t n) {
117 [ + - ]: 7 : if (handler) handler(ec, n);
118 : 7 : });
119 : 7 : }
120 : :
121 : : } // namespace interface
122 : : } // namespace wirestead
|