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