• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_rpc/nanopb/internal/method.h"
16 
17 #include <array>
18 
19 #include "gtest/gtest.h"
20 #include "pw_rpc/internal/lock.h"
21 #include "pw_rpc/internal/method_impl_tester.h"
22 #include "pw_rpc/internal/test_utils.h"
23 #include "pw_rpc/nanopb/internal/method_union.h"
24 #include "pw_rpc/service.h"
25 #include "pw_rpc_nanopb_private/internal_test_utils.h"
26 #include "pw_rpc_test_protos/test.pb.h"
27 
28 PW_MODIFY_DIAGNOSTICS_PUSH();
29 PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
30 
31 namespace pw::rpc::internal {
32 namespace {
33 
34 using std::byte;
35 
36 struct FakePb {};
37 
38 // Create a fake service for use with the MethodImplTester.
39 class TestNanopbService final : public Service {
40  public:
41   // Unary signatures
42 
Unary(const FakePb &,FakePb &)43   Status Unary(const FakePb&, FakePb&) { return Status(); }
44 
StaticUnary(const FakePb &,FakePb &)45   static Status StaticUnary(const FakePb&, FakePb&) { return Status(); }
46 
AsyncUnary(const FakePb &,NanopbUnaryResponder<FakePb> &)47   void AsyncUnary(const FakePb&, NanopbUnaryResponder<FakePb>&) {}
48 
StaticAsyncUnary(const FakePb &,NanopbUnaryResponder<FakePb> &)49   static void StaticAsyncUnary(const FakePb&, NanopbUnaryResponder<FakePb>&) {}
50 
UnaryWrongArg(FakePb &,FakePb &)51   Status UnaryWrongArg(FakePb&, FakePb&) { return Status(); }
52 
StaticUnaryVoidReturn(const FakePb &,FakePb &)53   static void StaticUnaryVoidReturn(const FakePb&, FakePb&) {}
54 
55   // Server streaming signatures
56 
ServerStreaming(const FakePb &,NanopbServerWriter<FakePb> &)57   void ServerStreaming(const FakePb&, NanopbServerWriter<FakePb>&) {}
58 
StaticServerStreaming(const FakePb &,NanopbServerWriter<FakePb> &)59   static void StaticServerStreaming(const FakePb&,
60                                     NanopbServerWriter<FakePb>&) {}
61 
ServerStreamingBadReturn(const FakePb &,NanopbServerWriter<FakePb> &)62   int ServerStreamingBadReturn(const FakePb&, NanopbServerWriter<FakePb>&) {
63     return 5;
64   }
65 
StaticServerStreamingMissingArg(NanopbServerWriter<FakePb> &)66   static void StaticServerStreamingMissingArg(NanopbServerWriter<FakePb>&) {}
67 
68   // Client streaming signatures
69 
ClientStreaming(NanopbServerReader<FakePb,FakePb> &)70   void ClientStreaming(NanopbServerReader<FakePb, FakePb>&) {}
71 
StaticClientStreaming(NanopbServerReader<FakePb,FakePb> &)72   static void StaticClientStreaming(NanopbServerReader<FakePb, FakePb>&) {}
73 
ClientStreamingBadReturn(NanopbServerReader<FakePb,FakePb> &)74   int ClientStreamingBadReturn(NanopbServerReader<FakePb, FakePb>&) {
75     return 0;
76   }
77 
StaticClientStreamingMissingArg()78   static void StaticClientStreamingMissingArg() {}
79 
80   // Bidirectional streaming signatures
81 
BidirectionalStreaming(NanopbServerReaderWriter<FakePb,FakePb> &)82   void BidirectionalStreaming(NanopbServerReaderWriter<FakePb, FakePb>&) {}
83 
StaticBidirectionalStreaming(NanopbServerReaderWriter<FakePb,FakePb> &)84   static void StaticBidirectionalStreaming(
85       NanopbServerReaderWriter<FakePb, FakePb>&) {}
86 
BidirectionalStreamingBadReturn(NanopbServerReaderWriter<FakePb,FakePb> &)87   int BidirectionalStreamingBadReturn(
88       NanopbServerReaderWriter<FakePb, FakePb>&) {
89     return 0;
90   }
91 
StaticBidirectionalStreamingMissingArg()92   static void StaticBidirectionalStreamingMissingArg() {}
93 };
94 
95 struct WrongPb;
96 
97 // Test matches() rejects incorrect request/response types.
98 // clang-format off
99 static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary, WrongPb, FakePb>());
100 static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary, FakePb, WrongPb>());
101 static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary, WrongPb, WrongPb>());
102 static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticUnary, FakePb, WrongPb>());
103 
104 static_assert(!NanopbMethod::template matches<&TestNanopbService::ServerStreaming, WrongPb, FakePb>());
105 static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticServerStreaming, FakePb, WrongPb>());
106 
107 static_assert(!NanopbMethod::template matches<&TestNanopbService::ClientStreaming, WrongPb, FakePb>());
108 static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticClientStreaming, FakePb, WrongPb>());
109 
110 static_assert(!NanopbMethod::template matches<&TestNanopbService::BidirectionalStreaming, WrongPb, FakePb>());
111 static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticBidirectionalStreaming, FakePb, WrongPb>());
112 // clang-format on
113 
114 static_assert(MethodImplTests<NanopbMethod, TestNanopbService>().Pass(
115     MatchesTypes<FakePb, FakePb>(),
116     std::tuple<const NanopbMethodSerde&>(
117         kNanopbMethodSerde<nullptr, nullptr>)));
118 
119 template <typename Impl>
120 class FakeServiceBase : public Service {
121  public:
FakeServiceBase(uint32_t id)122   FakeServiceBase(uint32_t id) : Service(id, kMethods) {}
123 
124   static constexpr std::array<NanopbMethodUnion, 5> kMethods = {
125       NanopbMethod::SynchronousUnary<&Impl::DoNothing>(
126           10u,
127           kNanopbMethodSerde<pw_rpc_test_Empty_fields,
128                              pw_rpc_test_Empty_fields>),
129       NanopbMethod::AsynchronousUnary<&Impl::AddFive>(
130           11u,
131           kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
132                              pw_rpc_test_TestResponse_fields>),
133       NanopbMethod::ServerStreaming<&Impl::StartStream>(
134           12u,
135           kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
136                              pw_rpc_test_TestResponse_fields>),
137       NanopbMethod::ClientStreaming<&Impl::ClientStream>(
138           13u,
139           kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
140                              pw_rpc_test_TestResponse_fields>),
141       NanopbMethod::BidirectionalStreaming<&Impl::BidirectionalStream>(
142           14u,
143           kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
144                              pw_rpc_test_TestResponse_fields>)};
145 };
146 
147 class FakeService : public FakeServiceBase<FakeService> {
148  public:
FakeService(uint32_t id)149   FakeService(uint32_t id) : FakeServiceBase(id) {}
150 
DoNothing(const pw_rpc_test_Empty &,pw_rpc_test_Empty &)151   Status DoNothing(const pw_rpc_test_Empty&, pw_rpc_test_Empty&) {
152     return Status::Unknown();
153   }
154 
AddFive(const pw_rpc_test_TestRequest & request,NanopbUnaryResponder<pw_rpc_test_TestResponse> & responder)155   void AddFive(const pw_rpc_test_TestRequest& request,
156                NanopbUnaryResponder<pw_rpc_test_TestResponse>& responder) {
157     last_request = request;
158 
159     if (fail_to_encode_async_unary_response) {
160       pw_rpc_test_TestResponse response = pw_rpc_test_TestResponse_init_default;
161       response.repeated_field.funcs.encode =
162           [](pb_ostream_t*, const pb_field_t*, void* const*) { return false; };
163       ASSERT_EQ(OkStatus(), responder.Finish(response, Status::NotFound()));
164     } else {
165       ASSERT_EQ(
166           OkStatus(),
167           responder.Finish({.value = static_cast<int32_t>(request.integer + 5)},
168                            Status::Unauthenticated()));
169     }
170   }
171 
StartStream(const pw_rpc_test_TestRequest & request,NanopbServerWriter<pw_rpc_test_TestResponse> & writer)172   void StartStream(const pw_rpc_test_TestRequest& request,
173                    NanopbServerWriter<pw_rpc_test_TestResponse>& writer) {
174     last_request = request;
175     last_writer = std::move(writer);
176   }
177 
ClientStream(NanopbServerReader<pw_rpc_test_TestRequest,pw_rpc_test_TestResponse> & reader)178   void ClientStream(NanopbServerReader<pw_rpc_test_TestRequest,
179                                        pw_rpc_test_TestResponse>& reader) {
180     last_reader = std::move(reader);
181   }
182 
BidirectionalStream(NanopbServerReaderWriter<pw_rpc_test_TestRequest,pw_rpc_test_TestResponse> & reader_writer)183   void BidirectionalStream(
184       NanopbServerReaderWriter<pw_rpc_test_TestRequest,
185                                pw_rpc_test_TestResponse>& reader_writer) {
186     last_reader_writer = std::move(reader_writer);
187   }
188 
189   bool fail_to_encode_async_unary_response = false;
190 
191   pw_rpc_test_TestRequest last_request;
192   NanopbServerWriter<pw_rpc_test_TestResponse> last_writer;
193   NanopbServerReader<pw_rpc_test_TestRequest, pw_rpc_test_TestResponse>
194       last_reader;
195   NanopbServerReaderWriter<pw_rpc_test_TestRequest, pw_rpc_test_TestResponse>
196       last_reader_writer;
197 };
198 
199 constexpr const NanopbMethod& kSyncUnary =
200     std::get<0>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
201 constexpr const NanopbMethod& kAsyncUnary =
202     std::get<1>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
203 constexpr const NanopbMethod& kServerStream =
204     std::get<2>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
205 constexpr const NanopbMethod& kClientStream =
206     std::get<3>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
207 constexpr const NanopbMethod& kBidirectionalStream =
208     std::get<4>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
209 
TEST(NanopbMethod,AsyncUnaryRpc_SendsResponse)210 TEST(NanopbMethod, AsyncUnaryRpc_SendsResponse) {
211   PW_ENCODE_PB(
212       pw_rpc_test_TestRequest, request, .integer = 123, .status_code = 0);
213 
214   ServerContextForTest<FakeService> context(kAsyncUnary);
215   rpc_lock().lock();
216   kAsyncUnary.Invoke(context.get(), context.request(request));
217 
218   const Packet& response = context.output().last_packet();
219   EXPECT_EQ(response.status(), Status::Unauthenticated());
220 
221   // Field 1 (encoded as 1 << 3) with 128 as the value.
222   constexpr std::byte expected[]{
223       std::byte{0x08}, std::byte{0x80}, std::byte{0x01}};
224 
225   EXPECT_EQ(sizeof(expected), response.payload().size());
226   EXPECT_EQ(0,
227             std::memcmp(expected, response.payload().data(), sizeof(expected)));
228 
229   EXPECT_EQ(123, context.service().last_request.integer);
230 }
231 
TEST(NanopbMethod,SyncUnaryRpc_InvalidPayload_SendsError)232 TEST(NanopbMethod, SyncUnaryRpc_InvalidPayload_SendsError) {
233   std::array<byte, 8> bad_payload{byte{0xFF}, byte{0xAA}, byte{0xDD}};
234 
235   ServerContextForTest<FakeService> context(kSyncUnary);
236   rpc_lock().lock();
237   kSyncUnary.Invoke(context.get(), context.request(bad_payload));
238 
239   const Packet& packet = context.output().last_packet();
240   EXPECT_EQ(PacketType::SERVER_ERROR, packet.type());
241   EXPECT_EQ(Status::DataLoss(), packet.status());
242   EXPECT_EQ(context.service_id(), packet.service_id());
243   EXPECT_EQ(kSyncUnary.id(), packet.method_id());
244 }
245 
TEST(NanopbMethod,AsyncUnaryRpc_ResponseEncodingFails_SendsInternalError)246 TEST(NanopbMethod, AsyncUnaryRpc_ResponseEncodingFails_SendsInternalError) {
247   constexpr int64_t value = 0x7FFFFFFF'FFFFFF00ll;
248   PW_ENCODE_PB(
249       pw_rpc_test_TestRequest, request, .integer = value, .status_code = 0);
250 
251   ServerContextForTest<FakeService> context(kAsyncUnary);
252   context.service().fail_to_encode_async_unary_response = true;
253 
254   rpc_lock().lock();
255   kAsyncUnary.Invoke(context.get(), context.request(request));
256 
257   const Packet& packet = context.output().last_packet();
258   EXPECT_EQ(PacketType::SERVER_ERROR, packet.type());
259   EXPECT_EQ(Status::Internal(), packet.status());
260   EXPECT_EQ(context.service_id(), packet.service_id());
261   EXPECT_EQ(kAsyncUnary.id(), packet.method_id());
262 
263   EXPECT_EQ(value, context.service().last_request.integer);
264 }
265 
TEST(NanopbMethod,ServerStreamingRpc_SendsNothingWhenInitiallyCalled)266 TEST(NanopbMethod, ServerStreamingRpc_SendsNothingWhenInitiallyCalled) {
267   PW_ENCODE_PB(
268       pw_rpc_test_TestRequest, request, .integer = 555, .status_code = 0);
269 
270   ServerContextForTest<FakeService> context(kServerStream);
271 
272   rpc_lock().lock();
273   kServerStream.Invoke(context.get(), context.request(request));
274 
275   EXPECT_EQ(0u, context.output().total_packets());
276   EXPECT_EQ(555, context.service().last_request.integer);
277 }
278 
TEST(NanopbMethod,ServerWriter_SendsResponse)279 TEST(NanopbMethod, ServerWriter_SendsResponse) {
280   ServerContextForTest<FakeService> context(kServerStream);
281 
282   rpc_lock().lock();
283   kServerStream.Invoke(context.get(), context.request({}));
284 
285   EXPECT_EQ(OkStatus(), context.service().last_writer.Write({.value = 100}));
286 
287   PW_ENCODE_PB(pw_rpc_test_TestResponse, payload, .value = 100);
288   std::array<byte, 128> encoded_response = {};
289   auto encoded = context.server_stream(payload).Encode(encoded_response);
290   ASSERT_EQ(OkStatus(), encoded.status());
291 
292   ConstByteSpan sent_payload = context.output().last_packet().payload();
293   EXPECT_TRUE(std::equal(payload.begin(),
294                          payload.end(),
295                          sent_payload.begin(),
296                          sent_payload.end()));
297 }
298 
TEST(NanopbMethod,ServerWriter_WriteWhenClosed_ReturnsFailedPrecondition)299 TEST(NanopbMethod, ServerWriter_WriteWhenClosed_ReturnsFailedPrecondition) {
300   ServerContextForTest<FakeService> context(kServerStream);
301 
302   rpc_lock().lock();
303   kServerStream.Invoke(context.get(), context.request({}));
304 
305   EXPECT_EQ(OkStatus(), context.service().last_writer.Finish());
306   EXPECT_TRUE(context.service()
307                   .last_writer.Write({.value = 100})
308                   .IsFailedPrecondition());
309 }
310 
TEST(NanopbMethod,ServerWriter_WriteAfterMoved_ReturnsFailedPrecondition)311 TEST(NanopbMethod, ServerWriter_WriteAfterMoved_ReturnsFailedPrecondition) {
312   ServerContextForTest<FakeService> context(kServerStream);
313 
314   rpc_lock().lock();
315   kServerStream.Invoke(context.get(), context.request({}));
316   NanopbServerWriter<pw_rpc_test_TestResponse> new_writer =
317       std::move(context.service().last_writer);
318 
319   EXPECT_EQ(OkStatus(), new_writer.Write({.value = 100}));
320 
321   EXPECT_EQ(Status::FailedPrecondition(),
322             context.service().last_writer.Write({.value = 100}));
323   EXPECT_EQ(Status::FailedPrecondition(),
324             context.service().last_writer.Finish());
325 
326   EXPECT_EQ(OkStatus(), new_writer.Finish());
327 }
328 
TEST(NanopbMethod,ServerStreamingRpc_ResponseEncodingFails_InternalError)329 TEST(NanopbMethod, ServerStreamingRpc_ResponseEncodingFails_InternalError) {
330   ServerContextForTest<FakeService> context(kServerStream);
331 
332   rpc_lock().lock();
333   kServerStream.Invoke(context.get(), context.request({}));
334 
335   EXPECT_EQ(OkStatus(), context.service().last_writer.Write({}));
336 
337   pw_rpc_test_TestResponse response = pw_rpc_test_TestResponse_init_default;
338   response.repeated_field.funcs.encode =
339       [](pb_ostream_t*, const pb_field_t*, void* const*) { return false; };
340   EXPECT_EQ(Status::Internal(), context.service().last_writer.Write(response));
341 }
342 
TEST(NanopbMethod,ServerReader_HandlesRequests)343 TEST(NanopbMethod, ServerReader_HandlesRequests) {
344   ServerContextForTest<FakeService> context(kClientStream);
345 
346   rpc_lock().lock();
347   kClientStream.Invoke(context.get(), context.request({}));
348 
349   pw_rpc_test_TestRequest request_struct{};
350   context.service().last_reader.set_on_next(
351       [&request_struct](const pw_rpc_test_TestRequest& req) {
352         request_struct = req;
353       });
354 
355   PW_ENCODE_PB(
356       pw_rpc_test_TestRequest, request, .integer = 1 << 30, .status_code = 9);
357   std::array<byte, 128> encoded_request = {};
358   auto encoded = context.client_stream(request).Encode(encoded_request);
359   ASSERT_EQ(OkStatus(), encoded.status());
360   ASSERT_EQ(OkStatus(),
361             context.server().ProcessPacket(*encoded, context.output()));
362 
363   EXPECT_EQ(request_struct.integer, 1 << 30);
364   EXPECT_EQ(request_struct.status_code, 9u);
365 }
366 
TEST(NanopbMethod,ServerReaderWriter_WritesResponses)367 TEST(NanopbMethod, ServerReaderWriter_WritesResponses) {
368   ServerContextForTest<FakeService> context(kBidirectionalStream);
369 
370   rpc_lock().lock();
371   kBidirectionalStream.Invoke(context.get(), context.request({}));
372 
373   EXPECT_EQ(OkStatus(),
374             context.service().last_reader_writer.Write({.value = 100}));
375 
376   PW_ENCODE_PB(pw_rpc_test_TestResponse, payload, .value = 100);
377   std::array<byte, 128> encoded_response = {};
378   auto encoded = context.server_stream(payload).Encode(encoded_response);
379   ASSERT_EQ(OkStatus(), encoded.status());
380 
381   ConstByteSpan sent_payload = context.output().last_packet().payload();
382   EXPECT_TRUE(std::equal(payload.begin(),
383                          payload.end(),
384                          sent_payload.begin(),
385                          sent_payload.end()));
386 }
387 
TEST(NanopbMethod,ServerReaderWriter_HandlesRequests)388 TEST(NanopbMethod, ServerReaderWriter_HandlesRequests) {
389   ServerContextForTest<FakeService> context(kBidirectionalStream);
390 
391   rpc_lock().lock();
392   kBidirectionalStream.Invoke(context.get(), context.request({}));
393 
394   pw_rpc_test_TestRequest request_struct{};
395   context.service().last_reader_writer.set_on_next(
396       [&request_struct](const pw_rpc_test_TestRequest& req) {
397         request_struct = req;
398       });
399 
400   PW_ENCODE_PB(
401       pw_rpc_test_TestRequest, request, .integer = 1 << 29, .status_code = 8);
402   std::array<byte, 128> encoded_request = {};
403   auto encoded = context.client_stream(request).Encode(encoded_request);
404   ASSERT_EQ(OkStatus(), encoded.status());
405   ASSERT_EQ(OkStatus(),
406             context.server().ProcessPacket(*encoded, context.output()));
407 
408   EXPECT_EQ(request_struct.integer, 1 << 29);
409   EXPECT_EQ(request_struct.status_code, 8u);
410 }
411 
412 }  // namespace
413 }  // namespace pw::rpc::internal
414 
415 PW_MODIFY_DIAGNOSTICS_POP();
416