|  | // Copyright 2020 The Pigweed Authors | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); you may not | 
|  | // use this file except in compliance with the License. You may obtain a copy of | 
|  | // the License at | 
|  | // | 
|  | //     https://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | 
|  | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | 
|  | // License for the specific language governing permissions and limitations under | 
|  | // the License. | 
|  |  | 
|  | #include "pw_rpc/nanopb_client_call.h" | 
|  |  | 
|  | #include "gtest/gtest.h" | 
|  | #include "pw_rpc_nanopb_private/internal_test_utils.h" | 
|  | #include "pw_rpc_private/internal_test_utils.h" | 
|  | #include "pw_rpc_test_protos/test.pb.h" | 
|  |  | 
|  | namespace pw::rpc { | 
|  | namespace { | 
|  |  | 
|  | constexpr uint32_t kServiceId = 16; | 
|  | constexpr uint32_t kUnaryMethodId = 111; | 
|  | constexpr uint32_t kServerStreamingMethodId = 112; | 
|  |  | 
|  | class FakeGeneratedServiceClient { | 
|  | public: | 
|  | static NanopbClientCall<UnaryResponseHandler<pw_rpc_test_TestResponse>> | 
|  | TestRpc(Channel& channel, | 
|  | const pw_rpc_test_TestRequest& request, | 
|  | UnaryResponseHandler<pw_rpc_test_TestResponse>& callback) { | 
|  | auto call = NanopbClientCall(&channel, | 
|  | kServiceId, | 
|  | kUnaryMethodId, | 
|  | callback, | 
|  | pw_rpc_test_TestRequest_fields, | 
|  | pw_rpc_test_TestResponse_fields); | 
|  | call.SendRequest(&request); | 
|  | return call; | 
|  | } | 
|  |  | 
|  | static NanopbClientCall< | 
|  | ServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse>> | 
|  | TestStreamRpc(Channel& channel, | 
|  | const pw_rpc_test_TestRequest& request, | 
|  | ServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse>& | 
|  | callback) { | 
|  | auto call = NanopbClientCall(&channel, | 
|  | kServiceId, | 
|  | kServerStreamingMethodId, | 
|  | callback, | 
|  | pw_rpc_test_TestRequest_fields, | 
|  | pw_rpc_test_TestStreamResponse_fields); | 
|  | call.SendRequest(&request); | 
|  | return call; | 
|  | } | 
|  | }; | 
|  |  | 
|  | using internal::TestServerStreamingResponseHandler; | 
|  | using internal::TestUnaryResponseHandler; | 
|  |  | 
|  | TEST(NanopbClientCall, Unary_SendsRequestPacket) { | 
|  | ClientContextForTest context; | 
|  | TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler; | 
|  |  | 
|  | auto call = FakeGeneratedServiceClient::TestRpc( | 
|  | context.channel(), {.integer = 123, .status_code = 0}, handler); | 
|  |  | 
|  | EXPECT_EQ(context.output().packet_count(), 1u); | 
|  | auto packet = context.output().sent_packet(); | 
|  | EXPECT_EQ(packet.channel_id(), context.channel().id()); | 
|  | EXPECT_EQ(packet.service_id(), kServiceId); | 
|  | EXPECT_EQ(packet.method_id(), kUnaryMethodId); | 
|  |  | 
|  | PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload()); | 
|  | EXPECT_EQ(sent_proto.integer, 123); | 
|  | } | 
|  |  | 
|  | TEST(NanopbClientCall, Unary_InvokesCallbackOnValidResponse) { | 
|  | ClientContextForTest context; | 
|  | TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler; | 
|  |  | 
|  | auto call = FakeGeneratedServiceClient::TestRpc( | 
|  | context.channel(), {.integer = 123, .status_code = 0}, handler); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestResponse, response, .value = 42); | 
|  | context.SendResponse(OkStatus(), response); | 
|  |  | 
|  | ASSERT_EQ(handler.responses_received(), 1u); | 
|  | EXPECT_EQ(handler.last_status(), OkStatus()); | 
|  | EXPECT_EQ(handler.last_response().value, 42); | 
|  | } | 
|  |  | 
|  | TEST(NanopbClientCall, Unary_InvokesErrorCallbackOnInvalidResponse) { | 
|  | ClientContextForTest context; | 
|  | TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler; | 
|  |  | 
|  | auto call = FakeGeneratedServiceClient::TestRpc( | 
|  | context.channel(), {.integer = 123, .status_code = 0}, handler); | 
|  |  | 
|  | constexpr std::byte bad_payload[]{ | 
|  | std::byte{0xab}, std::byte{0xcd}, std::byte{0xef}}; | 
|  | context.SendResponse(OkStatus(), bad_payload); | 
|  |  | 
|  | EXPECT_EQ(handler.responses_received(), 0u); | 
|  | EXPECT_EQ(handler.rpc_error(), Status::DataLoss()); | 
|  | } | 
|  |  | 
|  | TEST(NanopbClientCall, Unary_InvokesErrorCallbackOnServerError) { | 
|  | ClientContextForTest context; | 
|  | TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler; | 
|  |  | 
|  | auto call = FakeGeneratedServiceClient::TestRpc( | 
|  | context.channel(), {.integer = 123, .status_code = 0}, handler); | 
|  |  | 
|  | context.SendPacket(internal::PacketType::SERVER_ERROR, Status::NotFound()); | 
|  |  | 
|  | EXPECT_EQ(handler.responses_received(), 0u); | 
|  | EXPECT_EQ(handler.rpc_error(), Status::NotFound()); | 
|  | } | 
|  |  | 
|  | TEST(NanopbClientCall, Unary_OnlyReceivesOneResponse) { | 
|  | ClientContextForTest context; | 
|  | TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler; | 
|  |  | 
|  | auto call = FakeGeneratedServiceClient::TestRpc( | 
|  | context.channel(), {.integer = 123, .status_code = 0}, handler); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestResponse, r1, .value = 42); | 
|  | context.SendResponse(Status::Unimplemented(), r1); | 
|  | PW_ENCODE_PB(pw_rpc_test_TestResponse, r2, .value = 44); | 
|  | context.SendResponse(Status::OutOfRange(), r2); | 
|  | PW_ENCODE_PB(pw_rpc_test_TestResponse, r3, .value = 46); | 
|  | context.SendResponse(Status::Internal(), r3); | 
|  |  | 
|  | EXPECT_EQ(handler.responses_received(), 1u); | 
|  | EXPECT_EQ(handler.last_status(), Status::Unimplemented()); | 
|  | EXPECT_EQ(handler.last_response().value, 42); | 
|  | } | 
|  |  | 
|  | TEST(NanopbClientCall, ServerStreaming_SendsRequestPacket) { | 
|  | ClientContextForTest<128, 128, 99, kServiceId, kServerStreamingMethodId> | 
|  | context; | 
|  | TestServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse> handler; | 
|  |  | 
|  | auto call = FakeGeneratedServiceClient::TestStreamRpc( | 
|  | context.channel(), {.integer = 71, .status_code = 0}, handler); | 
|  |  | 
|  | EXPECT_EQ(context.output().packet_count(), 1u); | 
|  | auto packet = context.output().sent_packet(); | 
|  | EXPECT_EQ(packet.channel_id(), context.channel().id()); | 
|  | EXPECT_EQ(packet.service_id(), kServiceId); | 
|  | EXPECT_EQ(packet.method_id(), kServerStreamingMethodId); | 
|  |  | 
|  | PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload()); | 
|  | EXPECT_EQ(sent_proto.integer, 71); | 
|  | } | 
|  |  | 
|  | TEST(NanopbClientCall, ServerStreaming_InvokesCallbackOnValidResponse) { | 
|  | ClientContextForTest<128, 128, 99, kServiceId, kServerStreamingMethodId> | 
|  | context; | 
|  | TestServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse> handler; | 
|  |  | 
|  | auto call = FakeGeneratedServiceClient::TestStreamRpc( | 
|  | context.channel(), {.integer = 71, .status_code = 0}, handler); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r1, .chunk = {}, .number = 11u); | 
|  | context.SendResponse(OkStatus(), r1); | 
|  | EXPECT_TRUE(handler.active()); | 
|  | EXPECT_EQ(handler.responses_received(), 1u); | 
|  | EXPECT_EQ(handler.last_response().number, 11u); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r2, .chunk = {}, .number = 22u); | 
|  | context.SendResponse(OkStatus(), r2); | 
|  | EXPECT_TRUE(handler.active()); | 
|  | EXPECT_EQ(handler.responses_received(), 2u); | 
|  | EXPECT_EQ(handler.last_response().number, 22u); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r3, .chunk = {}, .number = 33u); | 
|  | context.SendResponse(OkStatus(), r3); | 
|  | EXPECT_TRUE(handler.active()); | 
|  | EXPECT_EQ(handler.responses_received(), 3u); | 
|  | EXPECT_EQ(handler.last_response().number, 33u); | 
|  | } | 
|  |  | 
|  | TEST(NanopbClientCall, ServerStreaming_ClosesOnFinish) { | 
|  | ClientContextForTest<128, 128, 99, kServiceId, kServerStreamingMethodId> | 
|  | context; | 
|  | TestServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse> handler; | 
|  |  | 
|  | auto call = FakeGeneratedServiceClient::TestStreamRpc( | 
|  | context.channel(), {.integer = 71, .status_code = 0}, handler); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r1, .chunk = {}, .number = 11u); | 
|  | context.SendResponse(OkStatus(), r1); | 
|  | EXPECT_TRUE(handler.active()); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r2, .chunk = {}, .number = 22u); | 
|  | context.SendResponse(OkStatus(), r2); | 
|  | EXPECT_TRUE(handler.active()); | 
|  |  | 
|  | // Close the stream. | 
|  | context.SendPacket(internal::PacketType::SERVER_STREAM_END, | 
|  | Status::NotFound()); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r3, .chunk = {}, .number = 33u); | 
|  | context.SendResponse(OkStatus(), r3); | 
|  | EXPECT_FALSE(handler.active()); | 
|  |  | 
|  | EXPECT_EQ(handler.responses_received(), 2u); | 
|  | } | 
|  |  | 
|  | TEST(NanopbClientCall, ServerStreaming_InvokesErrorCallbackOnInvalidResponses) { | 
|  | ClientContextForTest<128, 128, 99, kServiceId, kServerStreamingMethodId> | 
|  | context; | 
|  | TestServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse> handler; | 
|  |  | 
|  | auto call = FakeGeneratedServiceClient::TestStreamRpc( | 
|  | context.channel(), {.integer = 71, .status_code = 0}, handler); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r1, .chunk = {}, .number = 11u); | 
|  | context.SendResponse(OkStatus(), r1); | 
|  | EXPECT_TRUE(handler.active()); | 
|  | EXPECT_EQ(handler.responses_received(), 1u); | 
|  | EXPECT_EQ(handler.last_response().number, 11u); | 
|  |  | 
|  | constexpr std::byte bad_payload[]{ | 
|  | std::byte{0xab}, std::byte{0xcd}, std::byte{0xef}}; | 
|  | context.SendResponse(OkStatus(), bad_payload); | 
|  | EXPECT_EQ(handler.responses_received(), 1u); | 
|  | EXPECT_EQ(handler.rpc_error(), Status::DataLoss()); | 
|  |  | 
|  | PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r2, .chunk = {}, .number = 22u); | 
|  | context.SendResponse(OkStatus(), r2); | 
|  | EXPECT_TRUE(handler.active()); | 
|  | EXPECT_EQ(handler.responses_received(), 2u); | 
|  | EXPECT_EQ(handler.last_response().number, 22u); | 
|  |  | 
|  | context.SendPacket(internal::PacketType::SERVER_ERROR, Status::NotFound()); | 
|  | EXPECT_EQ(handler.responses_received(), 2u); | 
|  | EXPECT_EQ(handler.rpc_error(), Status::NotFound()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace pw::rpc |