• 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/internal/nanopb_method.h"
16 
17 #include <array>
18 
19 #include "gtest/gtest.h"
20 #include "pw_rpc/internal/nanopb_method_union.h"
21 #include "pw_rpc/server_context.h"
22 #include "pw_rpc/service.h"
23 #include "pw_rpc_nanopb_private/internal_test_utils.h"
24 #include "pw_rpc_private/internal_test_utils.h"
25 #include "pw_rpc_private/method_impl_tester.h"
26 #include "pw_rpc_test_protos/test.pb.h"
27 
28 namespace pw::rpc::internal {
29 namespace {
30 
31 using std::byte;
32 
33 struct FakePb {};
34 
35 // Create a fake service for use with the MethodImplTester.
36 class TestNanopbService final : public Service {
37  public:
Unary(ServerContext &,const FakePb &,FakePb &)38   Status Unary(ServerContext&, const FakePb&, FakePb&) { return Status(); }
39 
StaticUnary(ServerContext &,const FakePb &,FakePb &)40   static Status StaticUnary(ServerContext&, const FakePb&, FakePb&) {
41     return Status();
42   }
43 
ServerStreaming(ServerContext &,const FakePb &,ServerWriter<FakePb> &)44   void ServerStreaming(ServerContext&, const FakePb&, ServerWriter<FakePb>&) {}
45 
StaticServerStreaming(ServerContext &,const FakePb &,ServerWriter<FakePb> &)46   static void StaticServerStreaming(ServerContext&,
47                                     const FakePb&,
48                                     ServerWriter<FakePb>&) {}
49 
UnaryWrongArg(ServerContext &,FakePb &,FakePb &)50   Status UnaryWrongArg(ServerContext&, FakePb&, FakePb&) { return Status(); }
51 
StaticUnaryVoidReturn(ServerContext &,const FakePb &,FakePb &)52   static void StaticUnaryVoidReturn(ServerContext&, const FakePb&, FakePb&) {}
53 
ServerStreamingBadReturn(ServerContext &,const FakePb &,ServerWriter<FakePb> &)54   int ServerStreamingBadReturn(ServerContext&,
55                                const FakePb&,
56                                ServerWriter<FakePb>&) {
57     return 5;
58   }
59 
StaticServerStreamingMissingArg(const FakePb &,ServerWriter<FakePb> &)60   static void StaticServerStreamingMissingArg(const FakePb&,
61                                               ServerWriter<FakePb>&) {}
62 };
63 
64 // Test that the matches() function matches valid signatures.
65 static_assert(NanopbMethod::template matches<&TestNanopbService::Unary,
66                                              FakePb,
67                                              FakePb>());
68 static_assert(
69     NanopbMethod::template matches<&TestNanopbService::ServerStreaming,
70                                    FakePb,
71                                    FakePb>());
72 static_assert(NanopbMethod::template matches<&TestNanopbService::StaticUnary,
73                                              FakePb,
74                                              FakePb>());
75 static_assert(
76     NanopbMethod::template matches<&TestNanopbService::StaticServerStreaming,
77                                    FakePb,
78                                    FakePb>());
79 
80 // Test that the matches() function does not match the wrong method type.
81 static_assert(!NanopbMethod::template matches<&TestNanopbService::UnaryWrongArg,
82                                               FakePb,
83                                               FakePb>());
84 static_assert(
85     !NanopbMethod::template matches<&TestNanopbService::StaticUnaryVoidReturn,
86                                     FakePb,
87                                     FakePb>());
88 static_assert(!NanopbMethod::template matches<
89               &TestNanopbService::ServerStreamingBadReturn,
90               FakePb,
91               FakePb>());
92 static_assert(!NanopbMethod::template matches<
93               &TestNanopbService::StaticServerStreamingMissingArg,
94               FakePb,
95               FakePb>());
96 
97 struct WrongPb;
98 
99 // Test matches() rejects incorrect request/response types.
100 static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary,
101                                               WrongPb,
102                                               FakePb>());
103 static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary,
104                                               FakePb,
105                                               WrongPb>());
106 static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary,
107                                               WrongPb,
108                                               WrongPb>());
109 static_assert(
110     !NanopbMethod::template matches<&TestNanopbService::ServerStreaming,
111                                     WrongPb,
112                                     FakePb>());
113 
114 static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticUnary,
115                                               FakePb,
116                                               WrongPb>());
117 static_assert(
118     !NanopbMethod::template matches<&TestNanopbService::StaticServerStreaming,
119                                     FakePb,
120                                     WrongPb>());
121 
TEST(MethodImplTester,NanopbMethod)122 TEST(MethodImplTester, NanopbMethod) {
123   constexpr MethodImplTester<NanopbMethod, TestNanopbService, nullptr, nullptr>
124       method_tester;
125   EXPECT_TRUE(method_tester.MethodImplIsValid());
126 }
127 
128 pw_rpc_test_TestRequest last_request;
129 ServerWriter<pw_rpc_test_TestResponse> last_writer;
130 
AddFive(ServerContext &,const pw_rpc_test_TestRequest & request,pw_rpc_test_TestResponse & response)131 Status AddFive(ServerContext&,
132                const pw_rpc_test_TestRequest& request,
133                pw_rpc_test_TestResponse& response) {
134   last_request = request;
135   response.value = request.integer + 5;
136   return Status::Unauthenticated();
137 }
138 
DoNothing(ServerContext &,const pw_rpc_test_Empty &,pw_rpc_test_Empty &)139 Status DoNothing(ServerContext&, const pw_rpc_test_Empty&, pw_rpc_test_Empty&) {
140   return Status::Unknown();
141 }
142 
StartStream(ServerContext &,const pw_rpc_test_TestRequest & request,ServerWriter<pw_rpc_test_TestResponse> & writer)143 void StartStream(ServerContext&,
144                  const pw_rpc_test_TestRequest& request,
145                  ServerWriter<pw_rpc_test_TestResponse>& writer) {
146   last_request = request;
147   last_writer = std::move(writer);
148 }
149 
150 class FakeService : public Service {
151  public:
FakeService(uint32_t id)152   FakeService(uint32_t id) : Service(id, kMethods) {}
153 
154   static constexpr std::array<NanopbMethodUnion, 3> kMethods = {
155       NanopbMethod::Unary<DoNothing>(
156           10u, pw_rpc_test_Empty_fields, pw_rpc_test_Empty_fields),
157       NanopbMethod::Unary<AddFive>(
158           11u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
159       NanopbMethod::ServerStreaming<StartStream>(
160           12u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
161   };
162 };
163 
TEST(NanopbMethod,UnaryRpc_SendsResponse)164 TEST(NanopbMethod, UnaryRpc_SendsResponse) {
165   PW_ENCODE_PB(
166       pw_rpc_test_TestRequest, request, .integer = 123, .status_code = 0);
167 
168   const NanopbMethod& method =
169       std::get<1>(FakeService::kMethods).nanopb_method();
170   ServerContextForTest<FakeService> context(method);
171   method.Invoke(context.get(), context.packet(request));
172 
173   const Packet& response = context.output().sent_packet();
174   EXPECT_EQ(response.status(), Status::Unauthenticated());
175 
176   // Field 1 (encoded as 1 << 3) with 128 as the value.
177   constexpr std::byte expected[]{
178       std::byte{0x08}, std::byte{0x80}, std::byte{0x01}};
179 
180   EXPECT_EQ(sizeof(expected), response.payload().size());
181   EXPECT_EQ(0,
182             std::memcmp(expected, response.payload().data(), sizeof(expected)));
183 
184   EXPECT_EQ(123, last_request.integer);
185 }
186 
TEST(NanopbMethod,UnaryRpc_InvalidPayload_SendsError)187 TEST(NanopbMethod, UnaryRpc_InvalidPayload_SendsError) {
188   std::array<byte, 8> bad_payload{byte{0xFF}, byte{0xAA}, byte{0xDD}};
189 
190   const NanopbMethod& method =
191       std::get<0>(FakeService::kMethods).nanopb_method();
192   ServerContextForTest<FakeService> context(method);
193   method.Invoke(context.get(), context.packet(bad_payload));
194 
195   const Packet& packet = context.output().sent_packet();
196   EXPECT_EQ(PacketType::SERVER_ERROR, packet.type());
197   EXPECT_EQ(Status::DataLoss(), packet.status());
198   EXPECT_EQ(context.service_id(), packet.service_id());
199   EXPECT_EQ(method.id(), packet.method_id());
200 }
201 
TEST(NanopbMethod,UnaryRpc_BufferTooSmallForResponse_SendsInternalError)202 TEST(NanopbMethod, UnaryRpc_BufferTooSmallForResponse_SendsInternalError) {
203   constexpr int64_t value = 0x7FFFFFFF'FFFFFF00ll;
204   PW_ENCODE_PB(
205       pw_rpc_test_TestRequest, request, .integer = value, .status_code = 0);
206 
207   const NanopbMethod& method =
208       std::get<1>(FakeService::kMethods).nanopb_method();
209   // Output buffer is too small for the response, but can fit an error packet.
210   ServerContextForTest<FakeService, 22> context(method);
211   ASSERT_LT(context.output().buffer_size(),
212             context.packet(request).MinEncodedSizeBytes() + request.size() + 1);
213 
214   method.Invoke(context.get(), context.packet(request));
215 
216   const Packet& packet = context.output().sent_packet();
217   EXPECT_EQ(PacketType::SERVER_ERROR, packet.type());
218   EXPECT_EQ(Status::Internal(), packet.status());
219   EXPECT_EQ(context.service_id(), packet.service_id());
220   EXPECT_EQ(method.id(), packet.method_id());
221 
222   EXPECT_EQ(value, last_request.integer);
223 }
224 
TEST(NanopbMethod,ServerStreamingRpc_SendsNothingWhenInitiallyCalled)225 TEST(NanopbMethod, ServerStreamingRpc_SendsNothingWhenInitiallyCalled) {
226   PW_ENCODE_PB(
227       pw_rpc_test_TestRequest, request, .integer = 555, .status_code = 0);
228 
229   const NanopbMethod& method =
230       std::get<2>(FakeService::kMethods).nanopb_method();
231   ServerContextForTest<FakeService> context(method);
232 
233   method.Invoke(context.get(), context.packet(request));
234 
235   EXPECT_EQ(0u, context.output().packet_count());
236   EXPECT_EQ(555, last_request.integer);
237 }
238 
TEST(NanopbMethod,ServerWriter_SendsResponse)239 TEST(NanopbMethod, ServerWriter_SendsResponse) {
240   const NanopbMethod& method =
241       std::get<2>(FakeService::kMethods).nanopb_method();
242   ServerContextForTest<FakeService> context(method);
243 
244   method.Invoke(context.get(), context.packet({}));
245 
246   EXPECT_EQ(OkStatus(), last_writer.Write({.value = 100}));
247 
248   PW_ENCODE_PB(pw_rpc_test_TestResponse, payload, .value = 100);
249   std::array<byte, 128> encoded_response = {};
250   auto encoded = context.packet(payload).Encode(encoded_response);
251   ASSERT_EQ(OkStatus(), encoded.status());
252 
253   ASSERT_EQ(encoded.value().size(), context.output().sent_data().size());
254   EXPECT_EQ(0,
255             std::memcmp(encoded.value().data(),
256                         context.output().sent_data().data(),
257                         encoded.value().size()));
258 }
259 
TEST(NanopbMethod,ServerWriter_WriteWhenClosed_ReturnsFailedPrecondition)260 TEST(NanopbMethod, ServerWriter_WriteWhenClosed_ReturnsFailedPrecondition) {
261   const NanopbMethod& method =
262       std::get<2>(FakeService::kMethods).nanopb_method();
263   ServerContextForTest<FakeService> context(method);
264 
265   method.Invoke(context.get(), context.packet({}));
266 
267   EXPECT_EQ(OkStatus(), last_writer.Finish());
268   EXPECT_TRUE(last_writer.Write({.value = 100}).IsFailedPrecondition());
269 }
270 
TEST(NanopbMethod,ServerWriter_WriteAfterMoved_ReturnsFailedPrecondition)271 TEST(NanopbMethod, ServerWriter_WriteAfterMoved_ReturnsFailedPrecondition) {
272   const NanopbMethod& method =
273       std::get<2>(FakeService::kMethods).nanopb_method();
274   ServerContextForTest<FakeService> context(method);
275 
276   method.Invoke(context.get(), context.packet({}));
277   ServerWriter<pw_rpc_test_TestResponse> new_writer = std::move(last_writer);
278 
279   EXPECT_EQ(OkStatus(), new_writer.Write({.value = 100}));
280 
281   EXPECT_EQ(Status::FailedPrecondition(), last_writer.Write({.value = 100}));
282   EXPECT_EQ(Status::FailedPrecondition(), last_writer.Finish());
283 
284   EXPECT_EQ(OkStatus(), new_writer.Finish());
285 }
286 
TEST(NanopbMethod,ServerStreamingRpc_ServerWriterBufferTooSmall_InternalError)287 TEST(NanopbMethod,
288      ServerStreamingRpc_ServerWriterBufferTooSmall_InternalError) {
289   const NanopbMethod& method =
290       std::get<2>(FakeService::kMethods).nanopb_method();
291 
292   constexpr size_t kNoPayloadPacketSize = 2 /* type */ + 2 /* channel */ +
293                                           5 /* service */ + 5 /* method */ +
294                                           2 /* payload */ + 2 /* status */;
295 
296   // Make the buffer barely fit a packet with no payload.
297   ServerContextForTest<FakeService, kNoPayloadPacketSize> context(method);
298 
299   // Verify that the encoded size of a packet with an empty payload is correct.
300   std::array<byte, 128> encoded_response = {};
301   auto encoded = context.packet({}).Encode(encoded_response);
302   ASSERT_EQ(OkStatus(), encoded.status());
303   ASSERT_EQ(kNoPayloadPacketSize, encoded.value().size());
304 
305   method.Invoke(context.get(), context.packet({}));
306 
307   EXPECT_EQ(OkStatus(), last_writer.Write({}));  // Barely fits
308   EXPECT_EQ(Status::Internal(), last_writer.Write({.value = 1}));  // Too big
309 }
310 
311 }  // namespace
312 }  // namespace pw::rpc::internal
313