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