blob: 451a250b5517e67438c90c6aa96fa3e750655008 [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#include <array>
#include "pw_bytes/span.h"
#include "pw_hdlc/decoder.h"
#include "pw_hdlc/default_addresses.h"
#include "pw_hdlc/encoder.h"
#include "pw_result/result.h"
#include "pw_rpc_transport/rpc_transport.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
#include "pw_stream/memory_stream.h"
#include "rpc_transport.h"
namespace pw::rpc {
inline constexpr size_t kHdlcProtocolOverheadBytes = 14;
template <size_t kMaxPacketSize>
class HdlcRpcPacketEncoder
: public RpcPacketEncoder<HdlcRpcPacketEncoder<kMaxPacketSize>> {
public:
// Encodes `packet` as HDLC UI frame and splits the resulting frame into
// chunks of `RpcFrame`s where every `RpcFrame` is no longer than
// `max_frame_size`. Calls `callback` for each of the resulting `RpcFrame`s.
//
// Returns:
// * FAILED_PRECONDITION if `packet` is too long or `max_frame_size` is zero.
// * The underlying HDLC encoding error if it fails to generate a UI frame.
// * The underlying callback invocation error from the first failed callback.
//
Status Encode(ConstByteSpan packet,
size_t max_frame_size,
OnRpcFrameEncodedCallback&& callback,
unsigned rpc_address = hdlc::kDefaultRpcAddress) {
if (packet.size() > kMaxPacketSize) {
return Status::FailedPrecondition();
}
if (max_frame_size == 0) {
return Status::FailedPrecondition();
}
stream::MemoryWriter writer(buffer_);
PW_TRY(hdlc::WriteUIFrame(rpc_address, packet, writer));
auto remaining = writer.WrittenData();
while (!remaining.empty()) {
auto next_fragment_size = std::min(max_frame_size, remaining.size());
auto fragment = remaining.first(next_fragment_size);
// No header needed for HDLC: frame payload is already HDLC-encoded and
// includes frame delimiters.
RpcFrame frame{.header = {}, .payload = fragment};
PW_TRY(callback(frame));
remaining = remaining.subspan(next_fragment_size);
}
return OkStatus();
}
private:
// Buffer for HDLC-encoded data. Must be 2x of the max packet size to
// accommodate HDLC escape bytes for the worst case where each payload byte
// must be escaped, plus 14 bytes for the HDLC protocol overhead.
static constexpr size_t kEncodeBufferSize =
2 * kMaxPacketSize + kHdlcProtocolOverheadBytes;
std::array<std::byte, kEncodeBufferSize> buffer_;
};
template <size_t kMaxPacketSize>
class HdlcRpcPacketDecoder
: public RpcPacketDecoder<HdlcRpcPacketDecoder<kMaxPacketSize>> {
public:
HdlcRpcPacketDecoder() : decoder_(decode_buffer_) {}
// Finds and decodes HDLC frames in `buffer` and calls `callback` for each
// well-formed frame. Malformed frames are ignored and dropped quietly.
Status Decode(ConstByteSpan buffer, OnRpcPacketDecodedCallback&& callback) {
decoder_.Process(
buffer,
[callback = std::move(callback)](Result<hdlc::Frame> hdlc_frame) {
if (hdlc_frame.ok()) {
callback(hdlc_frame->data());
}
});
return OkStatus();
}
private:
// decode_buffer_ is used to store a decoded HDLC packet, including the
// payload (of up to kMaxPacketSize), address (varint that is always 0 in our
// case), control flag and checksum. The total size of the non-payload
// components is kMinContentSizeBytes.
std::array<std::byte, kMaxPacketSize + hdlc::Frame::kMinContentSizeBytes>
decode_buffer_{};
hdlc::Decoder decoder_;
};
} // namespace pw::rpc