Concept docs cover 11 functions; tests cover 14. Concept Docs is the bottleneck — every Completed function needs both. Closing the largest group would move 90% of the debt — the biggest single shift available.
constexpr Status ConvertToStatus(const rpc::SynchronousCallResult<T>& result) {
return result.status();
}
N/A
N/A
N/A
N/A
constexpr T ConvertToValue(rpc::SynchronousCallResult<T>& result) {
return std::move(result.response());
}
N/A
N/A
N/A
N/A
// RPC service with low-level RPCs for transmitting data. Used for benchmarking
// and testing.
//
// NOTE: the implementation of `BidirectionalEcho` is *not* thread-safe.
class BenchmarkService
: public pw_rpc::raw::Benchmark::Service<BenchmarkService> {
public:
static void UnaryEcho(ConstByteSpan request, RawUnaryResponder& responder);
void BidirectionalEcho(RawServerReaderWriter& reader_writer);
private:
using ReaderWriterId = uint64_t;
ReaderWriterId AllocateReaderWriterId();
ReaderWriterId next_reader_writer_id_ = 0;
N/A
TEST(BenchmarkService, Benchmark_UnaryEchoRequestMessage) {
PW_RAW_TEST_METHOD_CONTEXT(BenchmarkService, UnaryEcho) context;
auto msg = pw::bytes::Array<0x12, 0x34, 0x56, 0x78>();
context.call(msg);
EXPECT_EQ(OkStatus(), context.status());
ASSERT_TRUE(DataEqual(context.response(), msg));
}
TEST(BenchmarkService, Benchmark_EmptyRequest) {
PW_RAW_TEST_METHOD_CONTEXT(BenchmarkService, UnaryEcho) context;
context.call({});
EXPECT_EQ(OkStatus(), context.status());
ASSERT_TRUE(DataEqual(context.response(), {}));
}
N/A
N/A
void BidirectionalEcho(RawServerReaderWriter& reader_writer);
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
static void UnaryEcho(ConstByteSpan request, RawUnaryResponder& responder);
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
Status ChangeEncodedChannelId(ByteSpan rpc_packet) {
static_assert(kNewChannelId < 128u,
"Channel IDs must be less than 128 to avoid needing to "
"re-encode the packet");
return internal::OverwriteChannelId(rpc_packet, kNewChannelId);
}
:cpp:func:`pw::rpc::ChangeEncodedChannelId` on the encoded packet to replace the generic channel ID (``1``) with the server-side channel ID allocated when the client connected. The encoded packet is then passed to :cpp:func:`pw::rpc::Server::ProcessPacket`. - When the server sends pw_rpc packets, the :cpp:class:`pw::rpc::ChannelOutput` calls :cpp:func:`pw::rpc::ChangeEncodedChannelId` to set the channel ID back to the generic ``1``.
calls :cpp:func:`pw::rpc::ChangeEncodedChannelId` to set the channel ID back to the generic ``1``.
N/A
N/A
N/A
// Associates an ID with an interface for sending packets.
class Channel : public internal::ChannelBase {
public:
// Creates a channel with a static ID. The channel's output can also be
// static, or it can set to null to allow dynamically opening connections
// through the channel.
template <uint32_t kId>
constexpr static Channel Create(ChannelOutput* output) {
static_assert(kId != kUnassignedChannelId, "Channel ID cannot be 0");
return Channel(kId, output);
}
// Creates a channel with a static ID from an enum value.
N/A
TEST(Channel, MaxSafePayload) {
constexpr size_t kUint32Max = std::numeric_limits<uint32_t>::max();
constexpr size_t kMaxPayloadSize = 64;
constexpr size_t kTestPayloadSize = MaxSafePayloadSize(kMaxPayloadSize);
// Because it's impractical to test a payload that nears the limits of a
// uint32 varint, calculate the difference when using a smaller payload.
constexpr size_t kPayloadSizeTestLimitations =
varint::EncodedSize(kUint32Max) - varint::EncodedSize(kTestPayloadSize);
// The buffer to use for encoding the RPC packet.
std::array<std::byte, kMaxPayloadSize - kPayloadSizeTestLimitations> buffer;
std::array<std::byte, kTestPayloadSize> payload;
for (size_t i = 0; i < payload.size(); i++) {
payload[i] = std::byte(i % std::numeric_limits<uint8_t>::max());
}
Packet packet(pwpb::PacketType::SERVER_STREAM,
/*channel_id=*/kUint32Max, // Varint, needs to be uint32_t max.
/*service_id=*/42, // Fixed-width. Value doesn't matter.
/*method_id=*/100, // Fixed-width. Value doesn't matter.
/*call_id=*/kUint32Max, // Varint, needs to be uint32_t max.
payload,
Status::Unauthenticated());
Result<ConstByteSpan> result = packet.Encode(buffer);
ASSERT_EQ(OkStatus(), result.status());
}
TEST(Channel, MaxSafePayload_OffByOne) {
constexpr size_t kUint32Max = std::numeric_limits<uint32_t>::max();
constexpr size_t kMaxPayloadSize = 64;
constexpr size_t kTestPayloadSize = MaxSafePayloadSize(kMaxPayloadSize);
// Because it's impractical to test a payload that nears the limits of a
// uint32 varint, calculate the difference when using a smaller payload.
constexpr size_t kPayloadSizeTestLimitations =
varint::EncodedSize(kUint32Max) - varint::EncodedSize(kTestPayloadSize);
// The buffer to use for encoding the RPC packet.
std::array<std::byte, kMaxPayloadSize - kPayloadSizeTestLimitations - 1>
buffer;
std::array<std::byte, kTestPayloadSize> payload;
for (size_t i = 0; i < payload.size(); i++) {
payload[i] = std::byte(i % std::numeric_limits<uint8_t>::max());
}
Packet packet(pwpb::PacketType::SERVER_STREAM,
/*channel_id=*/kUint32Max, // Varint, needs to be uint32_t max.
/*service_id=*/42, // Fixed-width. Value doesn't matter.
/*method_id=*/100, // Fixed-width. Value doesn't matter.
/*call_id=*/kUint32Max, // Varint, needs to be uint32_t max.
payload,
Status::Unauthenticated());
Result<ConstByteSpan> result = packet.Encode(buffer);
ASSERT_EQ(Status::ResourceExhausted(), result.status());
TEST(Channel, Create_FromEnum) {
constexpr rpc::Channel one = Channel::Create<ChannelId::kOne>(nullptr);
constexpr rpc::Channel two = Channel::Create<ChannelId::kTwo>(nullptr);
static_assert(one.id() == 1);
static_assert(two.id() == 2);
}
N/A
N/A
// Creates a dynamically assignable channel without a set ID or output.
constexpr Channel() : internal::ChannelBase(kUnassignedChannelId, nullptr) {}
N/A
TEST(Channel, MaxSafePayload) {
constexpr size_t kUint32Max = std::numeric_limits<uint32_t>::max();
constexpr size_t kMaxPayloadSize = 64;
constexpr size_t kTestPayloadSize = MaxSafePayloadSize(kMaxPayloadSize);
// Because it's impractical to test a payload that nears the limits of a
// uint32 varint, calculate the difference when using a smaller payload.
constexpr size_t kPayloadSizeTestLimitations =
varint::EncodedSize(kUint32Max) - varint::EncodedSize(kTestPayloadSize);
// The buffer to use for encoding the RPC packet.
std::array<std::byte, kMaxPayloadSize - kPayloadSizeTestLimitations> buffer;
std::array<std::byte, kTestPayloadSize> payload;
for (size_t i = 0; i < payload.size(); i++) {
payload[i] = std::byte(i % std::numeric_limits<uint8_t>::max());
}
Packet packet(pwpb::PacketType::SERVER_STREAM,
/*channel_id=*/kUint32Max, // Varint, needs to be uint32_t max.
/*service_id=*/42, // Fixed-width. Value doesn't matter.
/*method_id=*/100, // Fixed-width. Value doesn't matter.
/*call_id=*/kUint32Max, // Varint, needs to be uint32_t max.
payload,
Status::Unauthenticated());
Result<ConstByteSpan> result = packet.Encode(buffer);
ASSERT_EQ(OkStatus(), result.status());
}
TEST(Channel, MaxSafePayload_OffByOne) {
constexpr size_t kUint32Max = std::numeric_limits<uint32_t>::max();
constexpr size_t kMaxPayloadSize = 64;
constexpr size_t kTestPayloadSize = MaxSafePayloadSize(kMaxPayloadSize);
// Because it's impractical to test a payload that nears the limits of a
// uint32 varint, calculate the difference when using a smaller payload.
constexpr size_t kPayloadSizeTestLimitations =
varint::EncodedSize(kUint32Max) - varint::EncodedSize(kTestPayloadSize);
// The buffer to use for encoding the RPC packet.
std::array<std::byte, kMaxPayloadSize - kPayloadSizeTestLimitations - 1>
buffer;
std::array<std::byte, kTestPayloadSize> payload;
for (size_t i = 0; i < payload.size(); i++) {
payload[i] = std::byte(i % std::numeric_limits<uint8_t>::max());
}
Packet packet(pwpb::PacketType::SERVER_STREAM,
/*channel_id=*/kUint32Max, // Varint, needs to be uint32_t max.
/*service_id=*/42, // Fixed-width. Value doesn't matter.
/*method_id=*/100, // Fixed-width. Value doesn't matter.
/*call_id=*/kUint32Max, // Varint, needs to be uint32_t max.
payload,
Status::Unauthenticated());
Result<ConstByteSpan> result = packet.Encode(buffer);
ASSERT_EQ(Status::ResourceExhausted(), result.status());
TEST(Channel, Create_FromEnum) {
constexpr rpc::Channel one = Channel::Create<ChannelId::kOne>(nullptr);
constexpr rpc::Channel two = Channel::Create<ChannelId::kTwo>(nullptr);
static_assert(one.id() == 1);
static_assert(two.id() == 2);
}
N/A · no in-tree implementor
N/A
N/A
constexpr static Channel Create(ChannelOutput* output) {
static_assert(kId != kUnassignedChannelId, "Channel ID cannot be 0");
return Channel(kId, output);
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
class ChannelOutput {
public:
// Returned from MaximumTransmissionUnit() to indicate that this ChannelOutput
// imposes no limits on the MTU.
static constexpr size_t kUnlimited = std::numeric_limits<size_t>::max();
// Creates a channel output with the provided name. The name is used for
// logging only.
constexpr ChannelOutput(const char* name) : name_(name) {}
virtual ~ChannelOutput() = default;
with the channel ID and a :cpp:class:`pw::rpc::ChannelOutput` associated with the new connection. - The server maintains a mapping between channel IDs and pw_rpc client connections. - On the client core, pw_rpc clients all use the same channel ID (e.g. ``1``). - As packets arrive from pw_rpc client connections, the server-side code calls :cpp:func:`pw::rpc::ChangeEncodedChannelId` on the encoded packet to replace the generic channel ID (``1``) with the server-side channel ID allocated when the client connected. The encoded packet is then passed to :cpp:func:`pw::rpc::Server::ProcessPacket`. - …
Users of ``pw_rpc`` must implement the :cpp:class:`pw::rpc::ChannelOutput` interface.
- When the server sends pw_rpc packets, the :cpp:class:`pw::rpc::ChannelOutput` calls :cpp:func:`pw::rpc::ChangeEncodedChannelId` to set the channel ID back to the generic ``1``.
TEST(ChannelOutput, Name) {
class NameTester : public ChannelOutput {
public:
NameTester(const char* name) : ChannelOutput(name) {}
Status Send(span<const std::byte>) override { return OkStatus(); }
};
EXPECT_STREQ("hello_world", NameTester("hello_world").name());
EXPECT_EQ(nullptr, NameTester(nullptr).name());
}
N/A
N/A
// Creates a channel output with the provided name. The name is used for
// logging only.
constexpr ChannelOutput(const char* name) : name_(name) {}
N/A
TEST(ChannelOutput, Name) {
class NameTester : public ChannelOutput {
public:
NameTester(const char* name) : ChannelOutput(name) {}
Status Send(span<const std::byte>) override { return OkStatus(); }
};
EXPECT_STREQ("hello_world", NameTester("hello_world").name());
EXPECT_EQ(nullptr, NameTester(nullptr).name());
}
N/A · no in-tree implementor
N/A
N/A
// Returns the maximum transmission unit that this ChannelOutput supports. If
// the ChannelOutput imposes no limit on the MTU, this function returns
// ChannelOutput::kUnlimited.
virtual size_t MaximumTransmissionUnit() { return kUnlimited; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Send() function or any descendent calls. Doing so will result in deadlock! // RPC APIs may be used by other threads, just not within Send(). // // The buffer provided in packet must NOT be accessed outside of this // function. It must be sent immediately or copied elsewhere before the // function returns. virtual Status Send(span<const std::byte> buffer)
:cpp:func:`pw::rpc::ChannelOutput::Send` error.
N/A
N/A · no in-tree implementor
N/A
N/A
// Returned from MaximumTransmissionUnit() to indicate that this ChannelOutput // imposes no limits on the MTU. static constexpr size_t kUnlimited = std::numeric_limits<size_t>::max();
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr const char* name() const { return name_; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
class Client : public internal::Endpoint {
public:
// If dynamic allocation is supported, it is not necessary to preallocate a
// channels list.
#if PW_RPC_DYNAMIC_ALLOCATION
_PW_RPC_CONSTEXPR Client() = default;
#endif // PW_RPC_DYNAMIC_ALLOCATION
// Creates a client that uses a set of RPC channels. Channels can be shared
// between multiple clients and servers.
_PW_RPC_CONSTEXPR Client(span<Channel> channels) : Endpoint(channels) {}
The ``pw::rpc::Client`` class is instantiated with a list of channels that it uses to communicate. These channels can be shared with a server, but multiple clients cannot use the same channels.
MUST NOT access any RPC endpoints (:cpp:class:`pw::rpc::Client`, :cpp:class:`pw::rpc::Server`) or call objects (:cpp:class:`pw::rpc::ServerReaderWriter` :cpp:class:`pw::rpc::ClientReaderWriter`, etc.) inside the :cpp:func:`Send` function or any descendent calls. Doing so will result in deadlock! RPC APIs may be used by other threads, just not within :cpp:func:`Send`.
TEST(Client, ProcessPacket_InvokesUnaryCallbacks) {
RawClientTestContext context;
CallContext call_context = StartUnaryCall<UnaryMethod>(context);
ASSERT_NE(call_context.completed, OkStatus());
context.server().SendResponse<UnaryMethod>(as_bytes(span("you nary?!?")),
OkStatus());
ASSERT_NE(call_context.payload, nullptr);
EXPECT_STREQ(call_context.payload, "you nary?!?");
EXPECT_EQ(call_context.completed, OkStatus());
EXPECT_FALSE(call_context.call.active());
}
TEST(Client, ProcessPacket_NoCallbackSet) {
RawClientTestContext context;
CallContext call_context = StartUnaryCall<UnaryMethod>(context);
call_context.call.set_on_completed(nullptr);
ASSERT_NE(call_context.completed, OkStatus());
context.server().SendResponse<UnaryMethod>(as_bytes(span("you nary?!?")),
OkStatus());
EXPECT_FALSE(call_context.call.active());
}
TEST(Client, ProcessPacket_InvokesStreamCallbacks) {
RawClientTestContext context;
auto call = StartStreamCall<BidirectionalStreamMethod>(context);
context.server().SendServerStream<BidirectionalStreamMethod>(
as_bytes(span("<=>")));
ASSERT_NE(call.payload, nullptr);
EXPECT_STREQ(call.payload, "<=>");
context.server().SendResponse<BidirectionalStreamMethod>(Status::NotFound());
EXPECT_EQ(call.completed, Status::NotFound());
}
N/A
N/A
// If dynamic allocation is supported, it is not necessary to preallocate a // channels list. #if PW_RPC_DYNAMIC_ALLOCATION _PW_RPC_CONSTEXPR Client() = default;
N/A
TEST(Client, ProcessPacket_InvokesUnaryCallbacks) {
RawClientTestContext context;
CallContext call_context = StartUnaryCall<UnaryMethod>(context);
ASSERT_NE(call_context.completed, OkStatus());
context.server().SendResponse<UnaryMethod>(as_bytes(span("you nary?!?")),
OkStatus());
ASSERT_NE(call_context.payload, nullptr);
EXPECT_STREQ(call_context.payload, "you nary?!?");
EXPECT_EQ(call_context.completed, OkStatus());
EXPECT_FALSE(call_context.call.active());
}
TEST(Client, ProcessPacket_NoCallbackSet) {
RawClientTestContext context;
CallContext call_context = StartUnaryCall<UnaryMethod>(context);
call_context.call.set_on_completed(nullptr);
ASSERT_NE(call_context.completed, OkStatus());
context.server().SendResponse<UnaryMethod>(as_bytes(span("you nary?!?")),
OkStatus());
EXPECT_FALSE(call_context.call.active());
}
TEST(Client, ProcessPacket_InvokesStreamCallbacks) {
RawClientTestContext context;
auto call = StartStreamCall<BidirectionalStreamMethod>(context);
context.server().SendServerStream<BidirectionalStreamMethod>(
as_bytes(span("<=>")));
ASSERT_NE(call.payload, nullptr);
EXPECT_STREQ(call.payload, "<=>");
context.server().SendResponse<BidirectionalStreamMethod>(Status::NotFound());
EXPECT_EQ(call.completed, Status::NotFound());
}
N/A · no in-tree implementor
N/A
N/A
// // OK - The packet was processed by the client. // DATA_LOSS - Failed to decode the packet. // INVALID_ARGUMENT - The packet is intended for a server, not a client. // UNAVAILABLE - No RPC channel with the requested ID was found. // Status ProcessPacket(ConstByteSpan data)
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Class that wraps both an RPC client and a server, simplifying RPC setup when
// a device needs to function as both.
class ClientServer {
public:
// If dynamic allocation is supported, it is not necessary to preallocate a
// channels list.
#if PW_RPC_DYNAMIC_ALLOCATION
_PW_RPC_CONSTEXPR ClientServer() = default;
#endif // PW_RPC_DYNAMIC_ALLOCATION
_PW_RPC_CONSTEXPR ClientServer(span<Channel> channels)
: client_(channels), server_(channels) {}
// Sends a packet to either the client or the server, depending on its type.
N/A
TEST(ClientServer, ProcessPacket_CallsServer) {
ClientServer client_server(channels);
client_server.server().RegisterService(service);
Packet packet(pwpb::PacketType::REQUEST,
kFakeChannelId,
kFakeServiceId,
kFakeMethodId,
kFakeCallId);
std::array<std::byte, 32> buffer;
Result result = packet.Encode(buffer);
EXPECT_EQ(result.status(), OkStatus());
EXPECT_EQ(client_server.ProcessPacket(result.value()), OkStatus());
}
TEST(ClientServer, ProcessPacket_CallsClient) {
ClientServer client_server(channels);
client_server.server().RegisterService(service);
// Same packet as above, but type RESPONSE will skip the server and call into
// the client.
Packet packet(pwpb::PacketType::RESPONSE,
kFakeChannelId,
kFakeServiceId,
kFakeMethodId,
kFakeCallId);
std::array<std::byte, 32> buffer;
Result result = packet.Encode(buffer);
EXPECT_EQ(result.status(), OkStatus());
// No calls are registered on the client, so nothing should happen. The
// ProcessPacket call still returns OK since the client handled it.
EXPECT_EQ(client_server.ProcessPacket(result.value()), OkStatus());
}
TEST(ClientServer, ProcessPacket_BadData) {
ClientServer client_server(channels);
EXPECT_EQ(client_server.ProcessPacket({}), Status::DataLoss());
}
N/A
N/A
// If dynamic allocation is supported, it is not necessary to preallocate a // channels list. #if PW_RPC_DYNAMIC_ALLOCATION _PW_RPC_CONSTEXPR ClientServer() = default;
N/A
TEST(ClientServer, ProcessPacket_CallsServer) {
ClientServer client_server(channels);
client_server.server().RegisterService(service);
Packet packet(pwpb::PacketType::REQUEST,
kFakeChannelId,
kFakeServiceId,
kFakeMethodId,
kFakeCallId);
std::array<std::byte, 32> buffer;
Result result = packet.Encode(buffer);
EXPECT_EQ(result.status(), OkStatus());
EXPECT_EQ(client_server.ProcessPacket(result.value()), OkStatus());
}
TEST(ClientServer, ProcessPacket_CallsClient) {
ClientServer client_server(channels);
client_server.server().RegisterService(service);
// Same packet as above, but type RESPONSE will skip the server and call into
// the client.
Packet packet(pwpb::PacketType::RESPONSE,
kFakeChannelId,
kFakeServiceId,
kFakeMethodId,
kFakeCallId);
std::array<std::byte, 32> buffer;
Result result = packet.Encode(buffer);
EXPECT_EQ(result.status(), OkStatus());
// No calls are registered on the client, so nothing should happen. The
// ProcessPacket call still returns OK since the client handled it.
EXPECT_EQ(client_server.ProcessPacket(result.value()), OkStatus());
}
TEST(ClientServer, ProcessPacket_BadData) {
ClientServer client_server(channels);
EXPECT_EQ(client_server.ProcessPacket({}), Status::DataLoss());
}
N/A · no in-tree implementor
N/A
N/A
// Sends a packet to either the client or the server, depending on its type. Status ProcessPacket(ConstByteSpan packet);
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr Client& client() { return client_; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr Server& server() { return server_; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
/// Extracts the channel ID from a pw_rpc packet.
///
/// @returns @Result{the channel ID}
/// * @DATA_LOSS: The packet is corrupt and the channel ID could not be found.
Result<uint32_t> ExtractChannelId(ConstByteSpan packet);
N/A
TEST(ExtractChannelId, ValidPacket) {
std::byte buffer[64] = {};
Result<ConstByteSpan> result = kTestPacket.Encode(buffer);
ASSERT_EQ(result.status(), OkStatus());
Result<uint32_t> channel_id = ExtractChannelId(*result);
ASSERT_EQ(channel_id.status(), OkStatus());
EXPECT_EQ(*channel_id, 23u);
}
TEST(ExtractChannelId, InvalidPacket) {
constexpr std::byte buffer[64] = {std::byte{1}, std::byte{2}};
Result<uint32_t> channel_id = ExtractChannelId(buffer);
EXPECT_EQ(channel_id.status(), Status::DataLoss());
}
TEST(ExtractChannelId, ChangeChannelIdToValidValue) {
std::byte buffer[64] = {};
Result<ConstByteSpan> result = kTestPacket.Encode(buffer);
ByteSpan packet(buffer, result.value().size());
EXPECT_EQ(ChangeEncodedChannelId(packet, 0), OkStatus());
EXPECT_EQ(ExtractChannelId(packet).value(), 0u);
EXPECT_EQ(ChangeEncodedChannelId(packet, 1), OkStatus());
EXPECT_EQ(ExtractChannelId(packet).value(), 1u);
EXPECT_EQ(ChangeEncodedChannelId(packet, 23), OkStatus());
EXPECT_EQ(ExtractChannelId(packet).value(), 23u);
EXPECT_EQ(ChangeEncodedChannelId(packet, 127), OkStatus());
EXPECT_EQ(ExtractChannelId(packet).value(), 127u);
EXPECT_EQ(ChangeEncodedChannelId<0>(packet), OkStatus());
EXPECT_EQ(ExtractChannelId(packet).value(), 0u);
EXPECT_EQ(ChangeEncodedChannelId<1>(packet), OkStatus());
EXPECT_EQ(ExtractChannelId(packet).value(), 1u);
EXPECT_EQ(ChangeEncodedChannelId<23>(packet), OkStatus());
EXPECT_EQ(ExtractChannelId(packet).value(), 23u);
EXPECT_EQ(ChangeEncodedChannelId<127>(packet), OkStatus());
EXPECT_EQ(ExtractChannelId(packet).value(), 127u);
}
N/A
N/A
constexpr MethodId GetMethodId() {
return internal::WrapMethodId(internal::MethodInfo<kMethod>::kMethodId);
}
N/A
N/A
N/A
N/A
constexpr ServiceId GetServiceIdForMethod() {
return internal::WrapServiceId(internal::MethodInfo<kMethod>::kServiceId);
}
N/A
N/A
N/A
N/A
// True for client and bidirectional streaming RPCs.
constexpr bool HasClientStream(MethodType type) {
return (static_cast<unsigned>(type) &
static_cast<unsigned>(MethodType::kClientStreaming)) != 0;
}
N/A
N/A
N/A
N/A
// True for unary and server streaming RPCs.
constexpr bool HasServerStream(MethodType type) {
return (static_cast<unsigned>(type) &
static_cast<unsigned>(MethodType::kServerStreaming)) != 0;
}
N/A
N/A
N/A
N/A
/// determine the largest supported payload, even when dynamic allocation is
/// enabled.
///
/// @warning `MaxSafePayloadSize` does NOT account for the channel MTU, which
/// may be smaller. Call `MaxWriteSizeBytes()` on an RPC's call object
/// (reader/writer) to account for channel MTU.
constexpr size_t MaxSafePayloadSize(
size_t encode_buffer_size = cfg::kEncodingBufferSizeBytes) {
return encode_buffer_size > internal::Packet::kMinEncodedSizeWithoutPayload
? encode_buffer_size -
internal::Packet::kMinEncodedSizeWithoutPayload
: 0;
}
N/A
TEST(RawClientWriter, MaxSafePayloadSize_UnlimitedChannel) {
RawClientTestContext ctx;
RawClientWriter call = TestService::TestClientStreamRpc(
ctx.client(), ctx.channel().id(), FailIfOnCompletedCalled, FailIfCalled);
EXPECT_EQ(call.MaxWriteSizeBytes(), pw::rpc::MaxSafePayloadSize());
PW_TEST_EXPECT_OK(call.Cancel());
EXPECT_EQ(call.MaxWriteSizeBytes(), 0u);
}
TEST(RawClientReaderWriter, MaxSafePayloadSize_LimitedChannel) {
RawClientTestContext ctx;
RawClientReaderWriter call =
TestService::TestBidirectionalStreamRpc(ctx.client(),
ctx.channel().id(),
FailIfOnNextCalled,
FailIfCalled,
FailIfCalled);
constexpr size_t kChannelMtu = 40;
static_assert(kChannelMtu < pw::rpc::MaxSafePayloadSize());
ctx.output().set_mtu(kChannelMtu);
EXPECT_EQ(call.MaxWriteSizeBytes(), pw::rpc::MaxSafePayloadSize(kChannelMtu));
PW_TEST_EXPECT_OK(call.Cancel());
EXPECT_EQ(call.MaxWriteSizeBytes(), 0u);
}
TEST(RawServerReader, MaxSafePayloadSize_UnlimitedChannel) {
ReaderWriterTestContext ctx;
RawServerReader call =
RawServerReader::Open<TestService::TestClientStreamRpc>(
ctx.server, ctx.channel.id(), ctx.service);
EXPECT_EQ(call.MaxWriteSizeBytes(), pw::rpc::MaxSafePayloadSize());
PW_TEST_EXPECT_OK(call.Finish({}));
EXPECT_EQ(call.MaxWriteSizeBytes(), 0u);
}
N/A
N/A
// An identifier for a method.
class MethodId {
private:
constexpr explicit MethodId(uint32_t id) : id_(id) {}
friend constexpr MethodId internal::WrapMethodId(uint32_t id);
friend constexpr uint32_t internal::UnwrapMethodId(MethodId id);
uint32_t id_;
};
N/A
N/A
N/A
N/A
constexpr const auto& MethodSerde() {
return internal::MethodInfo<kMethod>::serde();
}
N/A
N/A
N/A
N/A
enum class MethodType : unsigned char {
kUnary = 0b00,
kServerStreaming = 0b01,
kClientStreaming = 0b10,
kBidirectionalStreaming = 0b11,
};
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Metadata about a `pw_rpc` packet.
//
// For now, this metadata structure only includes a limited set of information
// about the contents of a packet, but it may be extended in the future.
class PacketMeta {
public:
// Parses the metadata from a serialized packet.
static Result<PacketMeta> FromBuffer(ConstByteSpan data);
constexpr uint32_t channel_id() const { return channel_id_; }
constexpr ServiceId service_id() const { return service_id_; }
constexpr MethodId method_id() const { return method_id_; }
constexpr bool destination_is_client() const {
return destination_ == internal::Packet::kClient;
}
constexpr bool destination_is_server() const {
return destination_ == internal::Packet::kServer;
N/A
TEST(PacketMeta, FromBufferDecodesValidMinimalPacketConst) {
const uint32_t kChannelId = 12;
const uint32_t kServiceId = 0xdeadbeef;
const uint32_t kMethodId = 44;
FromBufferDecodesValidMinimalPacket(kChannelId, kServiceId, kMethodId);
}
TEST(PacketMeta, FromBufferFailsOnIncompletePacket) {
internal::Packet packet;
std::byte buffer[128];
Result<ConstByteSpan> encode_result = packet.Encode(buffer);
ASSERT_EQ(encode_result.status(), OkStatus());
Result<PacketMeta> decode_result = PacketMeta::FromBuffer(*encode_result);
ASSERT_EQ(decode_result.status(), Status::DataLoss());
}
N/A
N/A
// Parses the metadata from a serialized packet. static Result<PacketMeta> FromBuffer(ConstByteSpan data);
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr uint32_t channel_id() const { return channel_id_; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr bool destination_is_client() const {
return destination_ == internal::Packet::kClient;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr bool destination_is_server() const {
return destination_ == internal::Packet::kServer;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr MethodId method_id() const { return method_id_; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Note: this `payload` is only valid so long as the original `data` buffer
// passed to `PacketMeta::FromBuffer` remains valid.
constexpr ConstByteSpan payload() const { return payload_; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr ServiceId service_id() const { return service_id_; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr bool type_is_client_error() const {
return type_ == internal::pwpb::PacketType::CLIENT_ERROR;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr bool type_is_server_error() const {
return type_ == internal::pwpb::PacketType::SERVER_ERROR;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Returns the payloads for a particular RPC in a Vector of RPC packets.
//
// Adapts a FilteredView of packets to return payloads instead of packets.
class PayloadsView {
public:
class iterator : public containers::WrappedIterator<
iterator,
internal::test::PacketsView::iterator,
ConstByteSpan> {
public:
constexpr iterator() = default;
// Access the payload (rather than packet) with operator* and operator->.
const ConstByteSpan& operator*() const { return value().payload(); }
const ConstByteSpan* operator->() const { return &value().payload(); }
N/A
N/A
N/A
N/A
std::advance(it, index);
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
const ConstByteSpan& back() const { return *std::prev(end()); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
auto it = begin();
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
bool empty() const { return begin() == end(); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
iterator end() const { return iterator(view_.end()); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Returns the first/last payload for the RPC. size() must be > 0.
const ConstByteSpan& front() const { return *begin(); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
class iterator : public containers::WrappedIterator<
iterator,
internal::test::PacketsView::iterator,
ConstByteSpan> {
public:
constexpr iterator() = default;
// Access the payload (rather than packet) with operator* and operator->.
const ConstByteSpan& operator*() const { return value().payload(); }
const ConstByteSpan* operator->() const { return &value().payload(); }
private:
N/A
N/A
N/A
N/A
constexpr iterator() = default;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Access the payload (rather than packet) with operator* and operator->.
const ConstByteSpan& operator*() const { return value().payload(); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Number of payloads for the specified RPC.
size_t size() const { return view_.size(); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
SynchronousCallResult<Response>::RpcError(Status status) {
return SynchronousCallResult(internal::SynchronousCallStatus::kRpc, status);
}
N/A
N/A
N/A
N/A
class Server : public internal::Endpoint {
public:
// If dynamic allocation is supported, it is not necessary to preallocate a
// channels list.
#if PW_RPC_DYNAMIC_ALLOCATION
_PW_RPC_CONSTEXPR Server() = default;
#endif // PW_RPC_DYNAMIC_ALLOCATION
// Creates a client that uses a set of RPC channels. Channels can be shared
// between multiple clients and servers.
_PW_RPC_CONSTEXPR Server(span<Channel> channels) : Endpoint(channels) {}
:cpp:class:`pw::rpc::Server`) or call objects (:cpp:class:`pw::rpc::ServerReaderWriter` :cpp:class:`pw::rpc::ClientReaderWriter`, etc.) inside the :cpp:func:`Send` function or any descendent calls. Doing so will result in deadlock! RPC APIs may be used by other threads, just not within :cpp:func:`Send`.
N/A
N/A
N/A
// Returns whether a service is registered. // // Calling RegisterService with a registered service will assert. So depending // on your logic you might want to check if a service is currently registered. bool IsServiceRegistered(const Service& service) const
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// the packet was able to be processed: // // OK - The packet was processed by the server. // DATA_LOSS - Failed to decode the packet. // INVALID_ARGUMENT - The packet is intended for a client, not a server. // UNAVAILABLE - No RPC channel with the requested ID was found. Status ProcessPacket(ConstByteSpan packet_data)
:cpp:func:`pw::rpc::Server::ProcessPacket`. - When the server sends pw_rpc packets, the :cpp:class:`pw::rpc::ChannelOutput` calls :cpp:func:`pw::rpc::ChangeEncodedChannelId` to set the channel ID back to the generic ``1``.
N/A
N/A · no in-tree implementor
N/A
N/A
void RegisterService(Service& service, OtherServices&... services)
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// If dynamic allocation is supported, it is not necessary to preallocate a // channels list. #if PW_RPC_DYNAMIC_ALLOCATION _PW_RPC_CONSTEXPR Server() = default;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
void UnregisterService(Service& service, OtherServices&... services)
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Base class for all RPC services. This cannot be instantiated directly; use a
// generated subclass instead.
//
// Services store a span of concrete method implementation classes. To support
// different Method implementations, Service stores a base MethodUnion* and the
// size of the concrete MethodUnion object.
class Service : public IntrusiveList<Service>::Item {
public:
Service(const Service&) = delete;
Service(Service&&) = delete;
Service& operator=(const Service&) = delete;
Service& operator=(Service&&) = delete;
ServiceId service_id() const { return internal::WrapServiceId(id_); }
protected:
// Note: despite being non-`::internal` and non-`private`, this constructor
N/A
TEST(Service, MultipleMethods_FindMethod_Present) {
TestService service;
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 123),
&TestService::kMethods[0].method());
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 456),
&TestService::kMethods[1].method());
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 789),
&TestService::kMethods[2].method());
}
TEST(Service, MultipleMethods_FindMethod_NotPresent) {
TestService service;
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 0), nullptr);
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 457), nullptr);
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 999), nullptr);
}
TEST(Service, NoMethods_FindMethod_NotPresent) {
EmptyTestService service;
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 123), nullptr);
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 456), nullptr);
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 789), nullptr);
}
N/A
N/A
Service(const Service&) = delete;
N/A
TEST(Service, MultipleMethods_FindMethod_Present) {
TestService service;
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 123),
&TestService::kMethods[0].method());
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 456),
&TestService::kMethods[1].method());
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 789),
&TestService::kMethods[2].method());
}
TEST(Service, MultipleMethods_FindMethod_NotPresent) {
TestService service;
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 0), nullptr);
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 457), nullptr);
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 999), nullptr);
}
TEST(Service, NoMethods_FindMethod_NotPresent) {
EmptyTestService service;
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 123), nullptr);
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 456), nullptr);
EXPECT_EQ(ServiceTestHelper::FindMethod(service, 789), nullptr);
}
N/A · no in-tree implementor
N/A
N/A
ServiceId service_id() const { return internal::WrapServiceId(id_); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// An identifier for a service.
//
// Note: this does not identify instances of a service (Servers), only the
// service itself.
class ServiceId {
private:
constexpr explicit ServiceId(uint32_t id) : id_(id) {}
friend constexpr ServiceId internal::WrapServiceId(uint32_t id);
friend constexpr uint32_t internal::UnwrapServiceId(ServiceId id);
uint32_t id_;
};
N/A
N/A
N/A
N/A
// Class for iterating over RPC statuses associated witha particular RPC. This
// is used to iterate over the user RPC statuses and or protocol errors for a
// particular RPC.
class StatusView {
public:
class iterator : public containers::WrappedIterator<
iterator,
internal::test::PacketsView::iterator,
Status> {
public:
constexpr iterator() = default;
// Access the status (rather than packet) with operator* and operator->.
const Status& operator*() const { return value().status(); }
const Status* operator->() const { return &value().status(); }
N/A
N/A
N/A
N/A
std::advance(it, index);
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
const Status& back() const { return *std::prev(end()); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
auto it = begin();
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
bool empty() const { return begin() == end(); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
iterator end() const { return iterator(view_.end()); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Returns the first/last payload for the RPC. size() must be > 0.
const Status& front() const { return *begin(); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
class iterator : public containers::WrappedIterator<
iterator,
internal::test::PacketsView::iterator,
Status> {
public:
constexpr iterator() = default;
// Access the status (rather than packet) with operator* and operator->.
const Status& operator*() const { return value().status(); }
const Status* operator->() const { return &value().status(); }
private:
N/A
N/A
N/A
N/A
constexpr iterator() = default;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Access the status (rather than packet) with operator* and operator->.
const Status& operator*() const { return value().status(); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Number of statuses in this view.
size_t size() const { return view_.size(); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
SynchronousCallResult<Response> SynchronousCall(
Client& client,
uint32_t channel_id,
const typename internal::MethodInfo<kRpcMethod>::Request& request) {
return internal::StructSynchronousCall<kRpcMethod, Response>(
internal::CallFreeFunctionWithCustomResponse<kRpcMethod, Response>(
client, channel_id, request));
}
synchronous API. The :cc:`SynchronousCall\<RpcMethod\> <pw::rpc::SynchronousCall>` functions wrap the asynchronous client RPC call with a timed thread notification and returns once a result is known or a timeout has occurred. Only unary methods are supported.
N/A
N/A
N/A
SynchronousCallFor(
Client& client,
uint32_t channel_id,
const typename internal::MethodInfo<kRpcMethod>::Request& request,
chrono::SystemClock::duration timeout) {
return internal::StructSynchronousCall<kRpcMethod>(
internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
timeout);
}
:cc:`SynchronousCallFor\<RpcMethod\> <pw::rpc::SynchronousCallFor>` and :cc:`SynchronousCallUntil\<RpcMethod\> <pw::rpc::SynchronousCallUntil>` block for a given timeout or until a deadline, respectively. All wrappers work with either the standalone static RPC functions or the generated service client member methods.
N/A
N/A
N/A
class SynchronousCallResult {
public:
// Error Constructors
constexpr static SynchronousCallResult Timeout();
constexpr static SynchronousCallResult RpcError(Status status);
// Server Response Constructor
constexpr explicit SynchronousCallResult(Status status, Response response)
: call_status_(internal::SynchronousCallStatus::kServer),
status_(status),
response_(std::move(response)) {}
The Nanopb and pwpb APIs return a :cc:`SynchronousCallResult\<Response\> <pw::rpc::SynchronousCallResult>` object, which can be queried to determine whether any error scenarios occurred and, if not, access the response. The raw API executes a function when the call completes or returns a :cc:`Status <pw::Status>` if it does not.
N/A
N/A
N/A
constexpr static SynchronousCallResult RpcError(Status status);
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Server Response Constructor constexpr explicit SynchronousCallResult(Status status, Response response)
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Error Constructors constexpr static SynchronousCallResult Timeout();
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Returns true if there was a timeout, an rpc error or the server returned a // non-Ok status. [[nodiscard]] constexpr bool is_error() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
[[nodiscard]] constexpr bool is_rpc_error() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Returns true if the server responded with a non-Ok status. [[nodiscard]] constexpr bool is_server_error() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
[[nodiscard]] constexpr bool is_server_response() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
[[nodiscard]] constexpr bool is_timeout() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Returns true if the server returned a response with an Ok status. [[nodiscard]] constexpr bool ok() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// SynchronousCallResult<Response>::response() // SynchronousCallResult<Response>::operator*() // SynchronousCallResult<Response>::operator->() // // Accessors to the held value if `this->is_server_response()`. Otherwise, // terminates the process. constexpr const Response& response() const&;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
[[nodiscard]] constexpr Status status() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
SynchronousCallUntil(
Client& client,
uint32_t channel_id,
const typename internal::MethodInfo<kRpcMethod>::Request& request,
chrono::SystemClock::time_point deadline) {
return internal::StructSynchronousCall<kRpcMethod>(
internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
deadline);
}
:cc:`SynchronousCallUntil\<RpcMethod\> <pw::rpc::SynchronousCallUntil>` block for a given timeout or until a deadline, respectively. All wrappers work with either the standalone static RPC functions or the generated service client member methods.
N/A
N/A
N/A
SynchronousCallResult<Response>::Timeout() {
return SynchronousCallResult(internal::SynchronousCallStatus::kTimeout,
Status::DeadlineExceeded());
}
N/A
N/A
N/A
N/A
// The Writer class allows writing requests or responses to a streaming RPC.
// ClientWriter, ClientReaderWriter, ServerWriter, and ServerReaderWriter
// classes can be used as a generic Writer.
class Writer {
public:
Writer(const Writer&) = delete;
Writer(Writer&&) = delete;
Writer& operator=(const Writer&) = delete;
Writer& operator=(Writer&&) = delete;
bool active() const;
uint32_t channel_id() const;
Status Write(ConstByteSpan payload) PW_LOCKS_EXCLUDED(internal::rpc_lock());
``pw::rpc::Writer`` interface. On the client side, a client or bidirectional streaming RPC call object (``ClientWriter`` or ``ClientReaderWriter``) can be used as a ``pw::rpc::Writer&``. On the server side, a server or bidirectional streaming RPC call object (``ServerWriter`` or ``ServerReaderWriter``) can be used as a ``pw::rpc::Writer&``. Call ``as_writer()`` to get a ``Writer&`` of the client or server call object.
N/A
N/A
N/A
Status Write(ConstByteSpan payload) PW_LOCKS_EXCLUDED(internal::rpc_lock());
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
Writer(const Writer&) = delete;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
bool active() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
uint32_t channel_id() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// server integrations may provide an opportunity to inject a
// ``ChannelManipulator`` for this use case.
//
// A ``ChannelManipulator`` should not set send_packet_, as the consumer of a
// ``ChannelManipulator`` will use ``send_packet`` to insert the provided
// ``ChannelManipulator`` into a packet processing path.
class ChannelManipulator {
public:
// The expected function signature of the send callback used by a
// ChannelManipulator to forward packets to the final destination.
//
// The only argument is a byte span containing the RPC packet that should
// be sent.
//
// Returns:
// OK - Packet successfully sent.
// Other - Failed to send packet.
using SendCallback = Function<Status(span<const std::byte>)>;
N/A
N/A
N/A
N/A
ChannelManipulator()
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Processes an incoming packet before optionally sending it. // // Implementations of this method may send the processed packet, multiple // packets, or no packets at all via the registered `send_packet()` // handler. virtual Status ProcessAndSend(span<const std::byte> packet) = 0;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Sets the true send callback that a ChannelManipulator will use to forward
// packets to the final destination.
//
// This should not be used by a ChannelManipulator. The consumer of a
// ChannelManipulator will set the send callback.
void set_send_packet(SendCallback&& send) { send_packet_ = std::move(send); }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Initializes logging and the global RPC client for integration testing. Starts
// a background thread that processes incoming.
Status InitializeClient(int argc,
char* argv[],
const char* usage_args = "PORT");
N/A
N/A
N/A
N/A
void SocketClientContext<kMaxTransmissionUnit>::ProcessPackets() {
constexpr size_t kDecoderBufferSize =
hdlc::Decoder::RequiredBufferSizeForFrameSize(kMaxTransmissionUnit);
std::array<std::byte, kDecoderBufferSize> decode_buffer;
hdlc::Decoder decoder(decode_buffer);
while (true) {
std::byte byte[1];
Result<ByteSpan> read = stream_.Read(byte);
if (should_terminate_.test()) {
return;
N/A
N/A
N/A
N/A
// Configure options for the socket associated with the client.
int SetClientSockOpt(int level,
int optname,
const void* optval,
unsigned int optlen);
N/A
N/A
N/A
N/A
void SetEgressChannelManipulator(ChannelManipulator* new_channel_manipulator);
N/A
N/A
N/A
N/A
void SetIngressChannelManipulator(ChannelManipulator* new_channel_manipulator);
N/A
N/A
N/A
N/A
class SocketClientContext {
public:
constexpr SocketClientContext()
: rpc_dispatch_thread_handle_(std::nullopt),
channel_output_(stream_, hdlc::kDefaultRpcAddress, "socket"),
channel_output_with_manipulator_(channel_output_),
channel_(
Channel::Create<kChannelId>(&channel_output_with_manipulator_)),
client_(span(&channel_, 1)) {}
Client& client() { return client_; }
N/A
N/A
N/A
N/A
class ChannelOutputWithManipulator : public ChannelOutput {
public:
ChannelOutputWithManipulator(ChannelOutput& actual_output)
: ChannelOutput(actual_output.name()),
actual_output_(actual_output),
channel_manipulator_(nullptr) {}
void set_channel_manipulator(ChannelManipulator* new_channel_manipulator) {
if (new_channel_manipulator != nullptr) {
new_channel_manipulator->set_send_packet(
ChannelManipulator::SendCallback(
[&](ConstByteSpan payload)
N/A
N/A
N/A
N/A
ChannelOutputWithManipulator(ChannelOutput& actual_output)
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
size_t MaximumTransmissionUnit() override {
return actual_output_.MaximumTransmissionUnit();
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
Status Send(span<const std::byte> buffer) override
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
void set_channel_manipulator(ChannelManipulator* new_channel_manipulator) {
if (new_channel_manipulator != nullptr) {
new_channel_manipulator->set_send_packet(
ChannelManipulator::SendCallback(
[&](ConstByteSpan payload)
__attribute__((no_thread_safety_analysis)) {
return actual_output_.Send(payload);
}));
}
channel_manipulator_ = new_channel_manipulator;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
void SetEgressChannelManipulator(
ChannelManipulator* new_channel_manipulator) {
channel_output_with_manipulator_.set_channel_manipulator(
new_channel_manipulator);
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
void SetIngressChannelManipulator(
ChannelManipulator* new_channel_manipulator) {
if (new_channel_manipulator != nullptr) {
new_channel_manipulator->set_send_packet([&](ConstByteSpan payload) {
return client_.ProcessPacket(payload);
});
}
ingress_channel_manipulator_ = new_channel_manipulator;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Configure options for the socket associated with the client.
int SetSockOpt(int level,
int optname,
const void* optval,
unsigned int optlen) {
return stream_.SetSockOpt(level, optname, optval, optlen);
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr SocketClientContext()
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Connects to the specified host:port and starts a background thread to read
// packets from the socket.
Status Start(const char* host, uint16_t port) {
PW_TRY(stream_.Connect(host, port));
rpc_dispatch_thread_handle_.emplace(&SocketClientContext::ProcessPackets,
this);
return OkStatus();
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Terminates the client, joining the RPC dispatch thread.
void Terminate() {
PW_ASSERT(rpc_dispatch_thread_handle_.has_value());
should_terminate_.test_and_set();
// Close the stream to avoid blocking forever on a socket read.
stream_.Close();
rpc_dispatch_thread_handle_->join();
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
Client& client() { return client_; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Terminates the client, joining the RPC dispatch thread. void TerminateClient();
N/A
N/A
N/A
N/A
// Returns the global RPC client for integration test use. Client& client();
N/A
N/A
N/A
N/A
// Base class for rpc::Channel with internal-only public methods, which are
// hidden in the public derived class.
class ChannelBase {
public:
static constexpr uint32_t kUnassignedChannelId = 0;
// TODO: b/234876441 - Remove the Configure and set_channel_output functions.
// Users should call CloseChannel() / OpenChannel() to change a channel.
// This ensures calls are properly update and works consistently between
// static and dynamic channel allocation.
// Manually configures a dynamically-assignable channel with a specified ID
// and output. This is useful when a channel's parameters are not known until
// runtime. This can only be called once per channel.
N/A
N/A
N/A
N/A
constexpr void Close() {
PW_ASSERT(id_ != kUnassignedChannelId);
id_ = kUnassignedChannelId;
output_ = nullptr;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr void Configure(uint32_t id, ChannelOutput& output) {
static_assert(
!cfg::kDynamicAllocationEnabled<UnusedType>,
"Configure() may not be used if PW_RPC_DYNAMIC_ALLOCATION is "
"enabled. Call CloseChannel/OpenChannel on the endpoint instead.");
PW_ASSERT(id_ == kUnassignedChannelId);
PW_ASSERT(id != kUnassignedChannelId);
id_ = id;
output_ = &output;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Returns the maximum payload size for this channel, factoring in the // ChannelOutput's MTU and the RPC system's `pw::rpc::MaxSafePayloadSize()`. size_t MaxWriteSizeBytes() const;
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
// Invokes ChannelOutput::Send and returns its status. Any non-OK status // indicates that the Channel is permanently closed. Status Send(const Packet& packet) PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr bool assigned() const { return id_ != kUnassignedChannelId; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr uint32_t id() const { return id_; }
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr void set_channel_output(ChannelOutput& output) {
static_assert(
!cfg::kDynamicAllocationEnabled<UnusedType>,
"set_channel_output() may not be used if PW_RPC_DYNAMIC_ALLOCATION is "
"enabled. Call CloseChannel/OpenChannel on the endpoint instead.");
PW_ASSERT(id_ != kUnassignedChannelId);
output_ = &output;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
Status OverwriteChannelId(ByteSpan rpc_packet, uint32_t channel_id_under_128);
N/A
N/A
N/A
N/A
enum class SynchronousCallStatus {
kInvalid,
kTimeout,
kRpc,
kServer,
};
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr uint32_t UnwrapMethodId(MethodId id);
N/A
N/A
N/A
N/A
constexpr uint32_t UnwrapServiceId(ServiceId id);
N/A
N/A
N/A
N/A
constexpr MethodId WrapMethodId(uint32_t id);
N/A
N/A
N/A
N/A
constexpr ServiceId WrapServiceId(uint32_t id);
N/A
N/A
N/A
N/A
// Finds packets of a specified type for a particular method.
class PacketFilter {
public:
// Use Channel::kUnassignedChannelId to ignore the channel.
constexpr PacketFilter(internal::pwpb::PacketType packet_type_1,
internal::pwpb::PacketType packet_type_2,
uint32_t channel_id,
uint32_t service_id,
uint32_t method_id)
: packet_type_1_(packet_type_1),
packet_type_2_(packet_type_2),
channel_id_(channel_id),
service_id_(service_id),
N/A
N/A
N/A
N/A
// Use Channel::kUnassignedChannelId to ignore the channel.
constexpr PacketFilter(internal::pwpb::PacketType packet_type_1,
internal::pwpb::PacketType packet_type_2,
uint32_t channel_id,
uint32_t service_id,
uint32_t method_id)
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr bool operator()(const Packet& packet) const {
return (packet.type() == packet_type_1_ ||
packet.type() == packet_type_2_) &&
(channel_id_ == Channel::kUnassignedChannelId ||
packet.channel_id() == channel_id_) &&
packet.service_id() == service_id_ &&
packet.method_id() == method_id_;
}
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
constexpr bool SynchronousCallResult<Response>::is_error() const {
return !ok();
}
N/A
N/A
N/A
N/A
constexpr bool SynchronousCallResult<Response>::is_rpc_error() const {
return call_status_ == internal::SynchronousCallStatus::kRpc;
}
N/A
N/A
N/A
N/A
constexpr bool SynchronousCallResult<Response>::is_server_error() const {
return is_server_response() && !status_.ok();
}
N/A
N/A
N/A
N/A
constexpr bool SynchronousCallResult<Response>::is_server_response() const {
return call_status_ == internal::SynchronousCallStatus::kServer;
}
N/A
N/A
N/A
N/A
constexpr bool SynchronousCallResult<Response>::is_timeout() const {
return call_status_ == internal::SynchronousCallStatus::kTimeout;
}
N/A
N/A
N/A
N/A
constexpr bool SynchronousCallResult<Response>::ok() const {
return is_server_response() && status_.ok();
}
N/A
N/A
N/A
N/A
constexpr const Response& SynchronousCallResult<Response>::response() const& {
PW_ASSERT(is_server_response());
return response_;
}
N/A
N/A
N/A
N/A
constexpr Status SynchronousCallResult<Response>::status() const {
PW_ASSERT(call_status_ != internal::SynchronousCallStatus::kInvalid);
return status_;
}
N/A
N/A
N/A
N/A
Status SendResponseIfCalled(
Context& client_context,
const MethodResponseType<kMethod>& response,
Status status = OkStatus(),
chrono::SystemClock::duration timeout =
chrono::SystemClock::for_at_least(std::chrono::milliseconds(100))) {
const auto start_time = chrono::SystemClock::now();
while (chrono::SystemClock::now() - start_time < timeout) {
const auto count =
client_context.output().template total_payloads<kMethod>();
if (count > 0) {
client_context.server().template SendResponse<kMethod>(response, status);
N/A
N/A
N/A
N/A
chrono::SystemClock::for_at_least(std::chrono::milliseconds(100))) {
N/A
N/A
N/A · no in-tree implementor
N/A
N/A
void WaitForPackets(internal::test::FakeChannelOutput& output,
int count,
Function&& run_before) {
sync::CountingSemaphore sem;
output.set_on_send([&sem](ConstByteSpan, Status) { sem.release(); });
run_before();
for (int i = 0; i < count; ++i) {
PW_ASSERT(sem.try_acquire_for(std::chrono::seconds(kTimeoutSeconds)));
}
N/A
N/A
N/A
N/A
workflows rendered-reference block to surface this layer.