xref: /aosp_15_r20/external/pigweed/pw_rpc_transport/public/pw_rpc_transport/hdlc_framing.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <array>
17 
18 #include "pw_bytes/span.h"
19 #include "pw_hdlc/decoder.h"
20 #include "pw_hdlc/default_addresses.h"
21 #include "pw_hdlc/encoder.h"
22 #include "pw_result/result.h"
23 #include "pw_rpc_transport/rpc_transport.h"
24 #include "pw_status/status.h"
25 #include "pw_status/try.h"
26 #include "pw_stream/memory_stream.h"
27 
28 namespace pw::rpc {
29 
30 inline constexpr size_t kHdlcProtocolOverheadBytes = 14;
31 
32 template <size_t kMaxPacketSize>
33 class HdlcRpcPacketEncoder
34     : public RpcPacketEncoder<HdlcRpcPacketEncoder<kMaxPacketSize>> {
35  public:
36   // Encodes `packet` as HDLC UI frame and splits the resulting frame into
37   // chunks of `RpcFrame`s where every `RpcFrame` is no longer than
38   // `max_frame_size`. Calls `callback` for each of the resulting `RpcFrame`s.
39   //
40   // Returns:
41   // * FAILED_PRECONDITION if `packet` is too long or `max_frame_size` is zero.
42   // * The underlying HDLC encoding error if it fails to generate a UI frame.
43   // * The underlying callback invocation error from the first failed callback.
44   //
45   Status Encode(ConstByteSpan packet,
46                 size_t max_frame_size,
47                 OnRpcFrameEncodedCallback&& callback,
48                 unsigned rpc_address = hdlc::kDefaultRpcAddress) {
49     if (packet.size() > kMaxPacketSize) {
50       return Status::FailedPrecondition();
51     }
52     if (max_frame_size == 0) {
53       return Status::FailedPrecondition();
54     }
55     stream::MemoryWriter writer(buffer_);
56     PW_TRY(hdlc::WriteUIFrame(rpc_address, packet, writer));
57 
58     auto remaining = writer.WrittenData();
59     while (!remaining.empty()) {
60       auto next_fragment_size = std::min(max_frame_size, remaining.size());
61       auto fragment = remaining.first(next_fragment_size);
62       // No header needed for HDLC: frame payload is already HDLC-encoded and
63       // includes frame delimiters.
64       RpcFrame frame{.header = {}, .payload = fragment};
65       PW_TRY(callback(frame));
66       remaining = remaining.subspan(next_fragment_size);
67     }
68 
69     return OkStatus();
70   }
71 
72  private:
73   // Buffer for HDLC-encoded data. Must be 2x of the max packet size to
74   // accommodate HDLC escape bytes for the worst case where each payload byte
75   // must be escaped, plus 14 bytes for the HDLC protocol overhead.
76   static constexpr size_t kEncodeBufferSize =
77       2 * kMaxPacketSize + kHdlcProtocolOverheadBytes;
78   std::array<std::byte, kEncodeBufferSize> buffer_;
79 };
80 
81 template <size_t kMaxPacketSize>
82 class HdlcRpcPacketDecoder
83     : public RpcPacketDecoder<HdlcRpcPacketDecoder<kMaxPacketSize>> {
84  public:
HdlcRpcPacketDecoder()85   HdlcRpcPacketDecoder() : decoder_(decode_buffer_) {}
86 
87   // Finds and decodes HDLC frames in `buffer` and calls `callback` for each
88   // well-formed frame. Malformed frames are ignored and dropped quietly.
Decode(ConstByteSpan buffer,OnRpcPacketDecodedCallback && callback)89   Status Decode(ConstByteSpan buffer, OnRpcPacketDecodedCallback&& callback) {
90     decoder_.Process(
91         buffer,
92         [function = std::move(callback)](Result<hdlc::Frame> hdlc_frame) {
93           if (hdlc_frame.ok()) {
94             function(hdlc_frame->data());
95           }
96         });
97     return OkStatus();
98   }
99 
100  private:
101   // decode_buffer_ is used to store a decoded HDLC packet, including the
102   // payload (of up to kMaxPacketSize), address (varint that is always 0 in our
103   // case), control flag and checksum. The total size of the non-payload
104   // components is kMinContentSizeBytes.
105   std::array<std::byte, kMaxPacketSize + hdlc::Frame::kMinContentSizeBytes>
106       decode_buffer_{};
107   hdlc::Decoder decoder_;
108 };
109 
110 }  // namespace pw::rpc
111