blob: 8ed4723002b9c13791de87afdc8c336b4d3cb24a [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
#include <cstdint>
#include <mutex>
#include <optional>
#include "pw_async2/dispatcher.h"
#include "pw_async2/poll.h"
#include "pw_channel/channel.h"
#include "pw_sync/lock_annotations.h"
#include "pw_sync/mutex.h"
namespace pw::channel {
namespace internal {
// Internal Channel implementation for use with ForwardingChannelPair. It is
// specialized for kDatagram and kByte.
template <DataType kType>
class ForwardingChannel;
} // namespace internal
/// @defgroup pw_channel_forwarding
/// @{
/// Forwards either datagrams or bytes between two channels. Writes to the first
/// channel appear as reads on the second, and vice versa.
///
/// `ForwardingChannelPair` enables connecting two subsystems that communicate
/// with channels without implementing a custom channel.
template <DataType kType>
class ForwardingChannelPair {
public:
explicit constexpr ForwardingChannelPair(
multibuf::MultiBufAllocator& allocator);
ForwardingChannelPair(const ForwardingChannelPair&) = delete;
ForwardingChannelPair& operator=(const ForwardingChannelPair&) = delete;
ForwardingChannelPair(ForwardingChannelPair&&) = delete;
ForwardingChannelPair& operator=(ForwardingChannelPair&&) = delete;
/// Returns the first channel in the pair.
Channel<kType, kReliable, kReadable, kWritable>& first() { return first_; }
/// Returns a const reference to the first channel in the pair.
const Channel<kType, kReliable, kReadable, kWritable>& first() const {
return first_;
}
/// Returns the second channel in the pair.
Channel<kType, kReliable, kReadable, kWritable>& second() { return second_; }
/// Returns a const reference to the second channel in the pair.
const Channel<kType, kReliable, kReadable, kWritable>& second() const {
return second_;
}
private:
template <DataType>
friend class internal::ForwardingChannel;
sync::Mutex mutex_;
multibuf::MultiBufAllocator& allocator_;
bool closed_ PW_GUARDED_BY(mutex_) = false;
// These channels refer to each other, so their lifetimes must match.
internal::ForwardingChannel<kType> first_;
internal::ForwardingChannel<kType> second_;
};
/// Alias for a pair of forwarding datagram channels.
using ForwardingDatagramChannelPair =
ForwardingChannelPair<DataType::kDatagram>;
/// Alias for a pair of forwarding byte channels.
using ForwardingByteChannelPair = ForwardingChannelPair<DataType::kByte>;
/// @}
namespace internal {
template <>
class ForwardingChannel<DataType::kDatagram>
: public ReliableDatagramReaderWriter {
public:
ForwardingChannel(const ForwardingChannel&) = delete;
ForwardingChannel& operator=(const ForwardingChannel&) = delete;
ForwardingChannel(ForwardingChannel&&) = delete;
ForwardingChannel& operator=(ForwardingChannel&&) = delete;
private:
friend class ForwardingChannelPair<DataType::kDatagram>;
constexpr ForwardingChannel(ForwardingChannelPair<DataType::kDatagram>& pair,
ForwardingChannel* sibling)
: pair_(pair), sibling_(*sibling), write_token_(0) {}
async2::Poll<Result<multibuf::MultiBuf>> DoPendRead(
async2::Context& cx) override;
async2::Poll<Status> DoPendReadyToWrite(async2::Context& cx) override;
multibuf::MultiBufAllocator& DoGetWriteAllocator() override {
return pair_.allocator_;
}
Result<channel::WriteToken> DoWrite(multibuf::MultiBuf&& data) override;
async2::Poll<Result<channel::WriteToken>> DoPendFlush(
async2::Context&) override;
async2::Poll<Status> DoPendClose(async2::Context&) override;
// The two channels share one mutex. Lock safty analysis doesn't understand
// that, so has to be disabled for some functions.
ForwardingChannelPair<DataType::kDatagram>& pair_;
ForwardingChannel& sibling_;
// Could use a queue here.
std::optional<multibuf::MultiBuf> read_queue_ PW_GUARDED_BY(pair_.mutex_);
uint32_t write_token_ PW_GUARDED_BY(pair_.mutex_);
async2::Waker waker_ PW_GUARDED_BY(pair_.mutex_);
};
template <>
class ForwardingChannel<DataType::kByte> : public ReliableByteReaderWriter {
public:
ForwardingChannel(const ForwardingChannel&) = delete;
ForwardingChannel& operator=(const ForwardingChannel&) = delete;
ForwardingChannel(ForwardingChannel&&) = delete;
ForwardingChannel& operator=(ForwardingChannel&&) = delete;
private:
friend class ForwardingChannelPair<DataType::kByte>;
constexpr ForwardingChannel(ForwardingChannelPair<DataType::kByte>& pair,
ForwardingChannel* sibling)
: pair_(pair), sibling_(*sibling), write_token_(0) {}
async2::Poll<Result<multibuf::MultiBuf>> DoPendRead(
async2::Context& cx) override;
async2::Poll<Status> DoPendReadyToWrite(async2::Context&) override {
return async2::Ready(OkStatus());
}
multibuf::MultiBufAllocator& DoGetWriteAllocator() override {
return pair_.allocator_;
}
Result<channel::WriteToken> DoWrite(multibuf::MultiBuf&& data) override;
async2::Poll<Result<channel::WriteToken>> DoPendFlush(
async2::Context&) override;
async2::Poll<Status> DoPendClose(async2::Context&) override;
ForwardingChannelPair<DataType::kByte>& pair_;
ForwardingChannel& sibling_;
multibuf::MultiBuf read_queue_ PW_GUARDED_BY(pair_.mutex_);
uint32_t write_token_ PW_GUARDED_BY(pair_.mutex_);
async2::Waker read_waker_ PW_GUARDED_BY(pair_.mutex_);
};
} // namespace internal
// Define the constructor out-of-line, after ForwardingChannel is defined.
template <DataType kType>
constexpr ForwardingChannelPair<kType>::ForwardingChannelPair(
multibuf::MultiBufAllocator& allocator)
: allocator_(allocator), first_(*this, &second_), second_(*this, &first_) {}
} // namespace pw::channel