blob: 3b01931d2a08e9a6e0904b7bd9d67f02180f44a1 [file] [log] [blame]
// Copyright 2024 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
// __ ___ ___ _ _ ___ _ _ ___
// \ \ / /_\ | _ \ \| |_ _| \| |/ __|
// \ \/\/ / _ \| / .` || || .` | (_ |
// \_/\_/_/ \_\_|_\_|\_|___|_|\_|\___|
// _____ _____ ___ ___ ___ __ __ ___ _ _ _____ _ _
// | __\ \/ / _ \ __| _ \_ _| \/ | __| \| |_ _/_\ | |
// | _| > <| _/ _|| /| || |\/| | _|| .` | | |/ _ \| |__
// |___/_/\_\_| |___|_|_\___|_| |_|___|_|\_| |_/_/ \_\____|
//
// This module is in an early, experimental state. The APIs are in flux and may
// change without notice. Please do not rely on it in production code, but feel
// free to explore and share feedback with the Pigweed team!
#include "pw_async2/dispatcher.h"
#include "pw_async2/poll.h"
#include "pw_channel/channel.h"
#include "pw_containers/vector.h"
#include "pw_hdlc/decoder.h"
#include "pw_multibuf/allocator.h"
#include "pw_multibuf/multibuf.h"
#include "pw_status/status.h"
namespace pw::hdlc {
/// A router that multiplexes multiple datagram-oriented ``Channel`` s
/// over a single byte-oriented ``Channel`` using HDLC framing.
class Router final {
public:
class Registration;
/// Constructs a ``Router``
///
/// @param[in] io_channel The channel on which to send and receive encoded
/// HDLC packets.
///
/// @param[in] decode_buffer The memory to use for storing partially-decoded
/// HDLC frames. This buffer should be at least
/// ``Decoder::RequiredBufferSizeForFrameSize(frame_size)`` bytes in order
/// to ensure that HDLC frames of size ``frame_size`` can be successfully
/// decoded.
Router(pw::channel::ByteReaderWriter& io_channel, ByteSpan decode_buffer)
: io_channel_(io_channel), decoder_(decode_buffer) {}
// Router is not copyable or movable.
Router(const Router&) = delete;
Router& operator=(const Router&) = delete;
Router(Router&&) = delete;
Router& operator=(Router&&) = delete;
/// Registers a ``Channel`` tied to the provided addresses.
///
/// All incoming HDLC messages received on ``io_channel`` with HDLC address
/// ``receive_address`` will be decoded and routed to the provided
/// ``channel``.
///
/// Data read from ``channel`` will be HDLC-encoded and sent to
/// ``io_channel``.
///
/// Note that a non-writeable channel will exert backpressure on the entire
/// router, so channels should strive to consume or discard incoming data as
/// quickly as possible in order to prevent starvation of other channels.
///
/// @param[in] receive_address Incoming HDLC messages received on the
/// external ``io_channel`` with an address matching ``receive_address``
/// will be decoded and written to ``channel``.
///
/// @param[in] send_address Data read from ``channel`` will be written
/// to ``io_channel`` with the HDLC address ``send_address``.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: ``Channel`` was successfully registered.
///
/// ALREADY_EXISTS: A registration already exists for either
/// ``channel``, ``receive_address``, or ``send_address``. Channels may
/// not be registered with multiple addresses, nor may addresses be
/// used with multiple channels.
///
/// @endrst
Status AddChannel(pw::channel::DatagramReaderWriter& channel,
uint64_t receive_address,
uint64_t send_address);
/// Removes a previously registered ``Channel`` tied to the provided
/// addresses.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: The channel was successfully deregistered.
///
/// NOT_FOUND: A registration of the channel for the provided
/// addresses was not found.
///
/// @endrst
Status RemoveChannel(pw::channel::DatagramReaderWriter& channel,
uint64_t receive_address,
uint64_t send_address);
/// Progress the router as far as possible, waking the provided ``cx``
/// when more progress can be made.
///
/// This will only return ``Ready`` if ``io_channel`` has been observed as
/// closed, after which all messages have been flushed to the remaining
/// channels and the channels have been closed.
pw::async2::Poll<> Pend(pw::async2::Context& cx);
/// Closes all underlying channels, attempting to flush any data.
pw::async2::Poll<> PendClose(pw::async2::Context& cx);
private:
// TODO: https://pwbug.dev/329902904 - Use allocator-based collections and
// remove this arbitrary limit.
constexpr static size_t kSomeNumberOfChannels = 16;
/// A channel associated with an incoming and outgoing address.
struct ChannelData {
ChannelData(pw::channel::DatagramReaderWriter& channel_arg,
uint64_t receive_address_arg,
uint64_t send_address_arg)
: channel(&channel_arg),
receive_address(receive_address_arg),
send_address(send_address_arg) {}
ChannelData(const ChannelData&) = delete;
ChannelData& operator=(const ChannelData&) = delete;
ChannelData(ChannelData&&) = default;
ChannelData& operator=(ChannelData&&) = default;
/// A channel which reads and writes datagrams.
pw::channel::DatagramReaderWriter* channel;
/// Data received over HDLC with this address will be sent to ``channel``.
uint64_t receive_address;
/// Data read from ``channel`` will be sent out over HDLC with this
/// address.
uint64_t send_address;
};
/// Returns a pointer to the ``ChannelData`` corresponding to the provided
/// ``receive_address`` or nullptr if no such entry is found.
ChannelData* FindChannelForReceiveAddress(uint64_t receive_address);
/// Decodes and writes buffers from ``io_channel_`` and writes them into
/// the corresponding channel.
void DecodeAndWriteIncoming(pw::async2::Context& cx);
/// Attempts to send the decoded ``frame`` contents to the corresponding
/// channel.
pw::async2::Poll<> PollDeliverIncomingFrame(pw::async2::Context& cx,
const Frame& frame);
/// Searches channels for a ``buffer_to_encode_and_send_`` if there is none.
void TryFillBufferToEncodeAndSend(pw::async2::Context& cx);
/// Reads from ``channel_datas_``, HDLC encodes the packets, and sends them
/// out over ``io_channel_``.
void WriteOutgoingMessages(pw::async2::Context& cx);
/// Removes any entries in ``channel_datas_`` that have closed.
///
/// Consolidating this into one operation allows for a minimal amount of
/// shifting of the various channel elements.
void RemoveClosedChannels();
//////////////////////////////////////////////////////////
/// Channels used for both incoming and outgoing data. ///
//////////////////////////////////////////////////////////
/// The underlying channel over which HDLC-encoded messages are sent and
/// received. This is frequently a low-level driver e.g. UART.
pw::channel::ByteReaderWriter& io_channel_;
/// The channels which send and receive unencoded data.
pw::Vector<ChannelData, kSomeNumberOfChannels> channel_datas_;
///////////////////////////////////////////////////////////
/// State associated with the incoming data being read. ///
///////////////////////////////////////////////////////////
/// Incoming data that has not yet been processed by ``decoder_``.
pw::multibuf::MultiBuf incoming_data_;
/// An HDLC decoder.
pw::hdlc::Decoder decoder_;
/// The most recent frame returned by ``decoder_``.
std::optional<pw::hdlc::Frame> decoded_frame_;
/// Used by ``PollDeliverIncomingFrame`` to store an ongoing allocation.
std::optional<pw::multibuf::MultiBufAllocationFuture>
incoming_allocation_future_;
///////////////////////////////////////////////////////////
/// State associated with the outgoing data being sent. ///
///////////////////////////////////////////////////////////
/// The last buffer read from one of ``channel_datas_`` but not yet encoded
/// and sent to ``io_channel_`.
std::optional<pw::multibuf::MultiBuf> buffer_to_encode_and_send_;
/// The target address of the most recent ``buffer_to_encode_and_send_``.
uint64_t address_to_encode_and_send_to_;
/// A future waiting for a ``MultiBuf`` to use for sending data into
/// ``io_channel_``.
///
/// This will contain an allocation future if and only if
/// ``io_channel->PendReadyToWrite`` returned true but
/// ``outgoing_allocation_future_`` did not immediately return an output
/// buffer to send.
std::optional<pw::multibuf::MultiBufAllocationFuture>
outgoing_allocation_future_;
/// The next index of ``channel_datas_`` to read an outgoing packet from.
///
/// This is used to provide fairness between the channel outputs.
size_t next_first_read_index_ = 0;
};
} // namespace pw::hdlc