pw_rpc: Simplify ServerReader/Writer Open API
- Update the Reader/Writer Open function to take a single, simpler
template argument.
Before:
RawServerWriter::Open<&ServiceNameImpl::MethodName,
CalculateMethodId("MethodName")>(...)
After:
RawServerWriter::Open<pw_rpc::raw::ServiceName::MethodName>(...)
This uses the new MethodInfo type to extract information about the
RPC method, avoiding the need for the user to calculate the method ID.
- Remove the CalculateMethodId() function, which is no longer necessary.
This keeps method IDs as an implementation detail that is hidden from
pw_rpc users.
- Add tests for opening responder objects for unary RPCs.
Change-Id: Ifa7cabbc35fa28986df1a2a2449695adb2d24eb2
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/61060
Reviewed-by: Alexei Frolov <frolv@google.com>
Commit-Queue: Wyatt Hepler <hepler@google.com>
Pigweed-Auto-Submit: Wyatt Hepler <hepler@google.com>
diff --git a/pw_rpc/docs.rst b/pw_rpc/docs.rst
index 555fc6a..b379e7b 100644
--- a/pw_rpc/docs.rst
+++ b/pw_rpc/docs.rst
@@ -52,10 +52,23 @@
reboot. After the reboot, the device opens the writer object and sends its
response to the client.
-.. admonition:: Under construction
+The C++ API for opening a server reader/writer takes the generated RPC function
+as a template parameter. The server to use, channel ID, and service instance are
+passed as arguments. The API is the same for all RPC types, except the
+appropriate reader/writer class must be used.
- The ``ReaderWriter::Open()`` API is cumbersome, but a more streamlined API
- will be added to the generated RPC code.
+.. code-block:: c++
+
+ // Open a ServerWriter for a server streaming RPC.
+ auto writer = RawServerWriter::Open<pw_rpc::raw::ServiceName::MethodName>(
+ server, channel_id, service_instance);
+
+ // Send some responses, even though the client has not yet called this RPC.
+ CHECK_OK(writer.Write(encoded_response_1));
+ CHECK_OK(writer.Write(encoded_response_2));
+
+ // Finish the RPC.
+ CHECK_OK(writer.Finish(OkStatus()));
Creating an RPC
===============
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/server_reader_writer.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/server_reader_writer.h
index 2cc36bf..2e8cbee 100644
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/server_reader_writer.h
+++ b/pw_rpc/nanopb/public/pw_rpc/nanopb/server_reader_writer.h
@@ -20,6 +20,7 @@
#include "pw_bytes/span.h"
#include "pw_rpc/channel.h"
#include "pw_rpc/internal/call.h"
+#include "pw_rpc/internal/method_info.h"
#include "pw_rpc/internal/method_lookup.h"
#include "pw_rpc/internal/open_call.h"
#include "pw_rpc/server.h"
@@ -98,21 +99,23 @@
// Creates a NanopbServerReaderWriter that is ready to send responses for a
// particular RPC. This can be used for testing or to send responses to an RPC
// that has not been started by a client.
- template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
+ template <auto kMethod, typename ServiceImpl>
[[nodiscard]] static NanopbServerReaderWriter Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
- static_assert(std::is_same_v<Request, internal::Request<kMethod>>,
+ using Info = internal::MethodInfo<kMethod>;
+ static_assert(std::is_same_v<Request, typename Info::Request>,
"The request type of a NanopbServerReaderWriter must match "
"the method.");
- static_assert(std::is_same_v<Response, internal::Response<kMethod>>,
+ static_assert(std::is_same_v<Response, typename Info::Response>,
"The response type of a NanopbServerReaderWriter must match "
"the method.");
- return {internal::OpenCall<kMethod, MethodType::kBidirectionalStreaming>(
+ return {internal::OpenContext<kMethod, MethodType::kBidirectionalStreaming>(
server,
channel_id,
service,
- internal::MethodLookup::GetNanopbMethod<ServiceImpl, kMethodId>())};
+ internal::MethodLookup::GetNanopbMethod<ServiceImpl,
+ Info::kMethodId>())};
}
constexpr NanopbServerReaderWriter()
@@ -166,21 +169,23 @@
// Creates a NanopbServerReader that is ready to send a response to a
// particular RPC. This can be used for testing or to finish an RPC that has
// not been started by the client.
- template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
+ template <auto kMethod, typename ServiceImpl>
[[nodiscard]] static NanopbServerReader Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
+ using Info = internal::MethodInfo<kMethod>;
static_assert(
- std::is_same_v<Request, internal::Request<kMethod>>,
+ std::is_same_v<Request, typename Info::Request>,
"The request type of a NanopbServerReader must match the method.");
static_assert(
- std::is_same_v<Response, internal::Response<kMethod>>,
+ std::is_same_v<Response, typename Info::Response>,
"The response type of a NanopbServerReader must match the method.");
- return {internal::OpenCall<kMethod, MethodType::kClientStreaming>(
+ return {internal::OpenContext<kMethod, MethodType::kClientStreaming>(
server,
channel_id,
service,
- internal::MethodLookup::GetNanopbMethod<ServiceImpl, kMethodId>())};
+ internal::MethodLookup::GetNanopbMethod<ServiceImpl,
+ Info::kMethodId>())};
}
// Allow default construction so that users can declare a variable into which
@@ -224,18 +229,20 @@
// Creates a NanopbServerWriter that is ready to send responses for a
// particular RPC. This can be used for testing or to send responses to an RPC
// that has not been started by a client.
- template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
+ template <auto kMethod, typename ServiceImpl>
[[nodiscard]] static NanopbServerWriter Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
+ using Info = internal::MethodInfo<kMethod>;
static_assert(
- std::is_same_v<Response, internal::Response<kMethod>>,
+ std::is_same_v<Response, typename Info::Response>,
"The response type of a NanopbServerWriter must match the method.");
- return {internal::OpenCall<kMethod, MethodType::kServerStreaming>(
+ return {internal::OpenContext<kMethod, MethodType::kServerStreaming>(
server,
channel_id,
service,
- internal::MethodLookup::GetNanopbMethod<ServiceImpl, kMethodId>())};
+ internal::MethodLookup::GetNanopbMethod<ServiceImpl,
+ Info::kMethodId>())};
}
// Allow default construction so that users can declare a variable into which
@@ -282,18 +289,20 @@
// Creates a NanopbServerResponder that is ready to send a response for a
// particular RPC. This can be used for testing or to send responses to an RPC
// that has not been started by a client.
- template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
+ template <auto kMethod, typename ServiceImpl>
[[nodiscard]] static NanopbServerResponder Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
+ using Info = internal::MethodInfo<kMethod>;
static_assert(
- std::is_same_v<Response, internal::Response<kMethod>>,
+ std::is_same_v<Response, typename Info::Response>,
"The response type of a NanopbServerResponder must match the method.");
- return {internal::OpenCall<kMethod, MethodType::kUnary>(
+ return {internal::OpenContext<kMethod, MethodType::kUnary>(
server,
channel_id,
service,
- internal::MethodLookup::GetNanopbMethod<ServiceImpl, kMethodId>())};
+ internal::MethodLookup::GetNanopbMethod<ServiceImpl,
+ Info::kMethodId>())};
}
// Allow default construction so that users can declare a variable into which
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/test_method_context.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/test_method_context.h
index 77874cd..51269a0 100644
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/test_method_context.h
+++ b/pw_rpc/nanopb/public/pw_rpc/nanopb/test_method_context.h
@@ -71,10 +71,10 @@
// PW_NANOPB_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context;
// ASSERT_EQ(3u, context.responses().max_size());
//
-#define PW_NANOPB_TEST_METHOD_CONTEXT(service, method, ...) \
- ::pw::rpc::NanopbTestMethodContext<service, \
- &service::method, \
- ::pw::rpc::CalculateMethodId(#method) \
+#define PW_NANOPB_TEST_METHOD_CONTEXT(service, method, ...) \
+ ::pw::rpc::NanopbTestMethodContext<service, \
+ &service::method, \
+ ::pw::rpc::internal::Hash(#method) \
PW_COMMA_ARGS(__VA_ARGS__)>
template <typename Service,
auto kMethod,
diff --git a/pw_rpc/nanopb/server_reader_writer_test.cc b/pw_rpc/nanopb/server_reader_writer_test.cc
index ad624d9..a508631 100644
--- a/pw_rpc/nanopb/server_reader_writer_test.cc
+++ b/pw_rpc/nanopb/server_reader_writer_test.cc
@@ -21,7 +21,8 @@
namespace pw::rpc {
-class TestService final : public test::generated::TestService<TestService> {
+class TestServiceImpl final
+ : public test::generated::TestService<TestServiceImpl> {
public:
Status TestUnaryRpc(ServerContext&,
const pw_rpc_test_TestRequest&,
@@ -49,28 +50,43 @@
pw_rpc_test_TestStreamResponse>&) {}
};
-template <auto kMethod, uint32_t kMethodId>
+template <auto kMethod>
struct ReaderWriterTestContext {
+ using Info = internal::MethodInfo<kMethod>;
+
ReaderWriterTestContext()
- : output(decltype(output)::
- template Create<TestService, kMethod, kMethodId>()),
+ : output(decltype(output)::template Create<
+ TestServiceImpl,
+ Info::template Function<TestServiceImpl>(),
+ Info::kMethodId>()),
channel(Channel::Create<1>(&output)),
server(std::span(&channel, 1)) {}
- TestService service;
- NanopbFakeChannelOutput<internal::Response<kMethod>, 4, 128> output;
+ TestServiceImpl service;
+ NanopbFakeChannelOutput<typename Info::Response, 4, 128> output;
Channel channel;
Server server;
};
+using test::pw_rpc::nanopb::TestService;
+
+TEST(NanopbServerResponder, Open_ReturnsUsableResponder) {
+ ReaderWriterTestContext<TestService::TestUnaryRpc> ctx;
+ NanopbServerResponder responder =
+ NanopbServerResponder<pw_rpc_test_TestResponse>::Open<
+ TestService::TestUnaryRpc>(ctx.server, ctx.channel.id(), ctx.service);
+
+ responder.Finish({.value = 4321});
+
+ EXPECT_EQ(ctx.output.last_response().value, 4321);
+ EXPECT_EQ(ctx.output.last_status(), OkStatus());
+}
+
TEST(NanopbServerWriter, Open_ReturnsUsableWriter) {
- ReaderWriterTestContext<&TestService::TestServerStreamRpc,
- CalculateMethodId("TestServerStreamRpc")>
- ctx;
+ ReaderWriterTestContext<TestService::TestServerStreamRpc> ctx;
NanopbServerWriter responder =
NanopbServerWriter<pw_rpc_test_TestStreamResponse>::Open<
- &TestService::TestServerStreamRpc,
- CalculateMethodId("TestServerStreamRpc")>(
+ TestService::TestServerStreamRpc>(
ctx.server, ctx.channel.id(), ctx.service);
responder.Write({.chunk = {}, .number = 321});
@@ -81,14 +97,11 @@
}
TEST(NanopbServerReader, Open_ReturnsUsableReader) {
- ReaderWriterTestContext<&TestService::TestClientStreamRpc,
- CalculateMethodId("TestClientStreamRpc")>
- ctx;
+ ReaderWriterTestContext<TestService::TestClientStreamRpc> ctx;
NanopbServerReader responder =
NanopbServerReader<pw_rpc_test_TestRequest,
pw_rpc_test_TestStreamResponse>::
- Open<&TestService::TestClientStreamRpc,
- CalculateMethodId("TestClientStreamRpc")>(
+ Open<TestService::TestClientStreamRpc>(
ctx.server, ctx.channel.id(), ctx.service);
responder.Finish({.chunk = {}, .number = 321});
@@ -97,14 +110,11 @@
}
TEST(NanopbServerReaderWriter, Open_ReturnsUsableReaderWriter) {
- ReaderWriterTestContext<&TestService::TestBidirectionalStreamRpc,
- CalculateMethodId("TestBidirectionalStreamRpc")>
- ctx;
+ ReaderWriterTestContext<TestService::TestBidirectionalStreamRpc> ctx;
NanopbServerReaderWriter responder =
NanopbServerReaderWriter<pw_rpc_test_TestRequest,
pw_rpc_test_TestStreamResponse>::
- Open<&TestService::TestBidirectionalStreamRpc,
- CalculateMethodId("TestBidirectionalStreamRpc")>(
+ Open<TestService::TestBidirectionalStreamRpc>(
ctx.server, ctx.channel.id(), ctx.service);
responder.Write({.chunk = {}, .number = 321});
diff --git a/pw_rpc/public/pw_rpc/internal/open_call.h b/pw_rpc/public/pw_rpc/internal/open_call.h
index 2578b03..d784462 100644
--- a/pw_rpc/public/pw_rpc/internal/open_call.h
+++ b/pw_rpc/public/pw_rpc/internal/open_call.h
@@ -24,24 +24,28 @@
// Creates a call context for a particular RPC. Unlike the CallContext
// constructor, this function checks the type of RPC at compile time.
-template <auto kMethod, MethodType kExpected, typename MethodImpl>
-CallContext OpenCall(Server& server,
- uint32_t channel_id,
- Service& service,
- const MethodImpl& method) {
+template <auto kMethod,
+ MethodType kExpected,
+ typename ServiceImpl,
+ typename MethodImpl>
+CallContext OpenContext(Server& server,
+ uint32_t channel_id,
+ ServiceImpl& service,
+ const MethodImpl& method) {
+ using Info = internal::MethodInfo<kMethod>;
if constexpr (kExpected == MethodType::kUnary) {
- static_assert(internal::MethodTraits<decltype(kMethod)>::kType == kExpected,
+ static_assert(Info::kType == kExpected,
"ServerResponse objects may only be opened for unary RPCs.");
} else if constexpr (kExpected == MethodType::kServerStreaming) {
static_assert(
- MethodTraits<decltype(kMethod)>::kType == kExpected,
+ Info::kType == kExpected,
"ServerWriters may only be opened for server streaming RPCs.");
} else if constexpr (kExpected == MethodType::kClientStreaming) {
static_assert(
- MethodTraits<decltype(kMethod)>::kType == kExpected,
+ Info::kType == kExpected,
"ServerReaders may only be opened for client streaming RPCs.");
} else if constexpr (kExpected == MethodType::kBidirectionalStreaming) {
- static_assert(internal::MethodTraits<decltype(kMethod)>::kType == kExpected,
+ static_assert(Info::kType == kExpected,
"ServerReaderWriters may only be opened for bidirectional "
"streaming RPCs.");
}
diff --git a/pw_rpc/public/pw_rpc/service.h b/pw_rpc/public/pw_rpc/service.h
index 5dd1288..a4f2dbf 100644
--- a/pw_rpc/public/pw_rpc/service.h
+++ b/pw_rpc/public/pw_rpc/service.h
@@ -19,7 +19,6 @@
#include "pw_containers/intrusive_list.h"
#include "pw_preprocessor/compiler.h"
-#include "pw_rpc/internal/hash.h"
#include "pw_rpc/internal/method.h"
#include "pw_rpc/internal/method_union.h"
@@ -73,10 +72,4 @@
const uint16_t method_count_;
};
-// Calculates the method ID of the method with the given name. Services track
-// methods by this ID.
-constexpr uint32_t CalculateMethodId(std::string_view method_name) {
- return internal::Hash(method_name);
-}
-
} // namespace pw::rpc
diff --git a/pw_rpc/raw/public/pw_rpc/raw/server_reader_writer.h b/pw_rpc/raw/public/pw_rpc/raw/server_reader_writer.h
index c973d85..4abed67 100644
--- a/pw_rpc/raw/public/pw_rpc/raw/server_reader_writer.h
+++ b/pw_rpc/raw/public/pw_rpc/raw/server_reader_writer.h
@@ -20,6 +20,7 @@
#include "pw_bytes/span.h"
#include "pw_rpc/channel.h"
#include "pw_rpc/internal/call.h"
+#include "pw_rpc/internal/method_info.h"
#include "pw_rpc/internal/method_lookup.h"
#include "pw_rpc/internal/open_call.h"
#include "pw_rpc/server.h"
@@ -54,15 +55,17 @@
// Creates a RawServerReaderWriter that is ready to send responses for a
// particular RPC. This can be used for testing or to send responses to an RPC
// that has not been started by a client.
- template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
+ template <auto kMethod, typename ServiceImpl>
[[nodiscard]] static RawServerReaderWriter Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
- return {internal::OpenCall<kMethod, MethodType::kBidirectionalStreaming>(
+ return {internal::OpenContext<kMethod, MethodType::kBidirectionalStreaming>(
server,
channel_id,
service,
- internal::MethodLookup::GetRawMethod<ServiceImpl, kMethodId>())};
+ internal::MethodLookup::GetRawMethod<
+ ServiceImpl,
+ internal::MethodInfo<kMethod>::kMethodId>())};
}
using internal::Call::active;
@@ -113,15 +116,17 @@
// Creates a RawServerReader that is ready to send a response to a particular
// RPC. This can be used for testing or to finish an RPC that has not been
// started by the client.
- template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
+ template <auto kMethod, typename ServiceImpl>
[[nodiscard]] static RawServerReader Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
- return {internal::OpenCall<kMethod, MethodType::kClientStreaming>(
+ return {internal::OpenContext<kMethod, MethodType::kClientStreaming>(
server,
channel_id,
service,
- internal::MethodLookup::GetRawMethod<ServiceImpl, kMethodId>())};
+ internal::MethodLookup::GetRawMethod<
+ ServiceImpl,
+ internal::MethodInfo<kMethod>::kMethodId>())};
}
constexpr RawServerReader()
@@ -159,15 +164,17 @@
// Creates a RawServerWriter that is ready to send responses for a particular
// RPC. This can be used for testing or to send responses to an RPC that has
// not been started by a client.
- template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
+ template <auto kMethod, typename ServiceImpl>
[[nodiscard]] static RawServerWriter Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
- return {internal::OpenCall<kMethod, MethodType::kServerStreaming>(
+ return {internal::OpenContext<kMethod, MethodType::kServerStreaming>(
server,
channel_id,
service,
- internal::MethodLookup::GetRawMethod<ServiceImpl, kMethodId>())};
+ internal::MethodLookup::GetRawMethod<
+ ServiceImpl,
+ internal::MethodInfo<kMethod>::kMethodId>())};
}
constexpr RawServerWriter()
@@ -203,15 +210,17 @@
// Creates a RawServerResponder that is ready to send responses for a
// particular RPC. This can be used for testing or to send responses to an RPC
// that has not been started by a client.
- template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
+ template <auto kMethod, typename ServiceImpl>
[[nodiscard]] static RawServerResponder Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
- return {internal::OpenCall<kMethod, MethodType::kUnary>(
+ return {internal::OpenContext<kMethod, MethodType::kUnary>(
server,
channel_id,
service,
- internal::MethodLookup::GetRawMethod<ServiceImpl, kMethodId>())};
+ internal::MethodLookup::GetRawMethod<
+ ServiceImpl,
+ internal::MethodInfo<kMethod>::kMethodId>())};
}
constexpr RawServerResponder() : RawServerReaderWriter(MethodType::kUnary) {}
diff --git a/pw_rpc/raw/public/pw_rpc/raw/test_method_context.h b/pw_rpc/raw/public/pw_rpc/raw/test_method_context.h
index 0e0825d..afe9edc 100644
--- a/pw_rpc/raw/public/pw_rpc/raw/test_method_context.h
+++ b/pw_rpc/raw/public/pw_rpc/raw/test_method_context.h
@@ -74,10 +74,10 @@
// PW_RAW_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context;
// ASSERT_EQ(3u, context.responses().max_size());
//
-#define PW_RAW_TEST_METHOD_CONTEXT(service, method, ...) \
- ::pw::rpc::RawTestMethodContext<service, \
- &service::method, \
- ::pw::rpc::CalculateMethodId(#method) \
+#define PW_RAW_TEST_METHOD_CONTEXT(service, method, ...) \
+ ::pw::rpc::RawTestMethodContext<service, \
+ &service::method, \
+ ::pw::rpc::internal::Hash(#method) \
PW_COMMA_ARGS(__VA_ARGS__)>
template <typename Service,
auto kMethod,
diff --git a/pw_rpc/raw/server_reader_writer_test.cc b/pw_rpc/raw/server_reader_writer_test.cc
index ab12a6f..472f378 100644
--- a/pw_rpc/raw/server_reader_writer_test.cc
+++ b/pw_rpc/raw/server_reader_writer_test.cc
@@ -21,7 +21,8 @@
namespace pw::rpc {
-class TestService final : public test::generated::TestService<TestService> {
+class TestServiceImpl final
+ : public test::generated::TestService<TestServiceImpl> {
public:
static StatusWithSize TestUnaryRpc(ServerContext&, ConstByteSpan, ByteSpan) {
return StatusWithSize(0);
@@ -43,17 +44,30 @@
channel(Channel::Create<1>(&output)),
server(std::span(&channel, 1)) {}
- TestService service;
+ TestServiceImpl service;
RawFakeChannelOutput<128, 4> output;
Channel channel;
Server server;
};
+using test::pw_rpc::raw::TestService;
+
+TEST(RawServerResponder, Open_ReturnsUsableResponder) {
+ ReaderWriterTestContext ctx(MethodType::kUnary);
+ RawServerResponder call = RawServerResponder::Open<TestService::TestUnaryRpc>(
+ ctx.server, ctx.channel.id(), ctx.service);
+
+ EXPECT_EQ(call.channel_id(), ctx.channel.id());
+ call.Finish(std::as_bytes(std::span("hello from pw_rpc")));
+
+ EXPECT_STREQ(reinterpret_cast<const char*>(ctx.output.last_response().data()),
+ "hello from pw_rpc");
+}
+
TEST(RawServerWriter, Open_ReturnsUsableWriter) {
ReaderWriterTestContext ctx(MethodType::kServerStreaming);
RawServerWriter call =
- RawServerWriter::Open<&TestService::TestServerStreamRpc,
- CalculateMethodId("TestServerStreamRpc")>(
+ RawServerWriter::Open<TestService::TestServerStreamRpc>(
ctx.server, ctx.channel.id(), ctx.service);
EXPECT_EQ(call.channel_id(), ctx.channel.id());
@@ -66,8 +80,7 @@
TEST(RawServerReader, Open_ReturnsUsableReader) {
ReaderWriterTestContext ctx(MethodType::kClientStreaming);
RawServerReader call =
- RawServerReader::Open<&TestService::TestClientStreamRpc,
- CalculateMethodId("TestClientStreamRpc")>(
+ RawServerReader::Open<TestService::TestClientStreamRpc>(
ctx.server, ctx.channel.id(), ctx.service);
EXPECT_EQ(call.channel_id(), ctx.channel.id());
@@ -80,9 +93,7 @@
TEST(RawServerReaderWriter, Open_ReturnsUsableReaderWriter) {
ReaderWriterTestContext ctx(MethodType::kBidirectionalStreaming);
RawServerReaderWriter call =
- RawServerReaderWriter::Open<&TestService::TestBidirectionalStreamRpc,
- CalculateMethodId(
- "TestBidirectionalStreamRpc")>(
+ RawServerReaderWriter::Open<TestService::TestBidirectionalStreamRpc>(
ctx.server, ctx.channel.id(), ctx.service);
EXPECT_EQ(call.channel_id(), ctx.channel.id());