|  | // 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/server.h" | 
|  |  | 
|  | #include <array> | 
|  | #include <cstdint> | 
|  |  | 
|  | #include "gtest/gtest.h" | 
|  | #include "pw_assert/assert.h" | 
|  | #include "pw_rpc/internal/packet.h" | 
|  | #include "pw_rpc/internal/service.h" | 
|  | #include "pw_rpc_private/test_utils.h" | 
|  |  | 
|  | namespace pw::rpc { | 
|  | namespace { | 
|  |  | 
|  | using std::byte; | 
|  |  | 
|  | using internal::Method; | 
|  | using internal::Packet; | 
|  | using internal::PacketType; | 
|  |  | 
|  | class TestService : public internal::Service { | 
|  | public: | 
|  | TestService(uint32_t service_id) | 
|  | : internal::Service(service_id, methods_), | 
|  | methods_{ | 
|  | Method(100), | 
|  | Method(200), | 
|  | } {} | 
|  |  | 
|  | Method& method(uint32_t id) { | 
|  | for (Method& method : methods_) { | 
|  | if (method.id() == id) { | 
|  | return method; | 
|  | } | 
|  | } | 
|  |  | 
|  | PW_CRASH("Invalid method ID %u", static_cast<unsigned>(id)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::array<Method, 2> methods_; | 
|  | }; | 
|  |  | 
|  | class BasicServer : public ::testing::Test { | 
|  | protected: | 
|  | static constexpr byte kDefaultPayload[] = { | 
|  | byte(0x82), byte(0x02), byte(0xff), byte(0xff)}; | 
|  |  | 
|  | BasicServer() | 
|  | : channels_{ | 
|  | Channel::Create<1>(&output_), | 
|  | Channel::Create<2>(&output_), | 
|  | Channel(),  // available for assignment | 
|  | }, | 
|  | server_(channels_), | 
|  | service_(42) { | 
|  | server_.RegisterService(service_); | 
|  | } | 
|  |  | 
|  | TestOutput<128> output_; | 
|  | std::array<Channel, 3> channels_; | 
|  | Server server_; | 
|  | TestService service_; | 
|  |  | 
|  | span<const byte> EncodeRequest(PacketType type, | 
|  | uint32_t channel_id, | 
|  | uint32_t service_id, | 
|  | uint32_t method_id, | 
|  | span<const byte> payload = kDefaultPayload) { | 
|  | auto sws = Packet(type, channel_id, service_id, method_id, payload) | 
|  | .Encode(request_buffer_); | 
|  | EXPECT_EQ(Status::OK, sws.status()); | 
|  | return span(request_buffer_, sws.size()); | 
|  | } | 
|  |  | 
|  | private: | 
|  | byte request_buffer_[64]; | 
|  | }; | 
|  |  | 
|  | TEST_F(BasicServer, ProcessPacket_ValidMethod_InvokesMethod) { | 
|  | server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 42, 100), output_); | 
|  |  | 
|  | const Method& method = service_.method(100); | 
|  | EXPECT_EQ(1u, method.last_channel_id()); | 
|  | EXPECT_EQ(sizeof(kDefaultPayload), method.last_request().size()); | 
|  | EXPECT_EQ(std::memcmp(kDefaultPayload, | 
|  | method.last_request().data(), | 
|  | method.last_request().size()), | 
|  | 0); | 
|  | } | 
|  |  | 
|  | TEST_F(BasicServer, ProcessPacket_ValidMethod_SendsOkResponse) { | 
|  | server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 42, 100), output_); | 
|  |  | 
|  | Packet packet = Packet::FromBuffer(output_.sent_packet()); | 
|  | EXPECT_EQ(packet.channel_id(), 1u); | 
|  | EXPECT_EQ(packet.service_id(), 42u); | 
|  | EXPECT_EQ(packet.method_id(), 100u); | 
|  | EXPECT_TRUE(packet.payload().empty()); | 
|  | EXPECT_EQ(packet.status(), Status::OK); | 
|  | } | 
|  |  | 
|  | TEST_F(BasicServer, ProcessPacket_ValidMethod_SendsErrorResponse) { | 
|  | constexpr byte resp[] = {byte{0xf0}, byte{0x0d}}; | 
|  | service_.method(200).set_response(resp); | 
|  | service_.method(200).set_status(Status::FAILED_PRECONDITION); | 
|  |  | 
|  | server_.ProcessPacket(EncodeRequest(PacketType::RPC, 2, 42, 200), output_); | 
|  |  | 
|  | Packet packet = Packet::FromBuffer(output_.sent_packet()); | 
|  | EXPECT_EQ(packet.channel_id(), 2u); | 
|  | EXPECT_EQ(packet.service_id(), 42u); | 
|  | EXPECT_EQ(packet.method_id(), 200u); | 
|  | EXPECT_EQ(packet.status(), Status::FAILED_PRECONDITION); | 
|  | ASSERT_EQ(sizeof(resp), packet.payload().size()); | 
|  | EXPECT_EQ(std::memcmp(packet.payload().data(), resp, sizeof(resp)), 0); | 
|  | } | 
|  |  | 
|  | TEST_F(BasicServer, ProcessPacket_InvalidMethod_NothingIsInvoked) { | 
|  | server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 42, 101), output_); | 
|  |  | 
|  | EXPECT_EQ(0u, service_.method(100).last_channel_id()); | 
|  | EXPECT_EQ(0u, service_.method(200).last_channel_id()); | 
|  | } | 
|  |  | 
|  | TEST_F(BasicServer, ProcessPacket_InvalidMethod_SendsNotFound) { | 
|  | server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 42, 27), output_); | 
|  |  | 
|  | Packet packet = Packet::FromBuffer(output_.sent_packet()); | 
|  | EXPECT_EQ(packet.channel_id(), 1u); | 
|  | EXPECT_EQ(packet.service_id(), 42u); | 
|  | EXPECT_EQ(packet.method_id(), 0u);  // No method ID 27 | 
|  | EXPECT_EQ(packet.status(), Status::NOT_FOUND); | 
|  | } | 
|  |  | 
|  | TEST_F(BasicServer, ProcessPacket_InvalidService_SendsNotFound) { | 
|  | server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 43, 27), output_); | 
|  |  | 
|  | Packet packet = Packet::FromBuffer(output_.sent_packet()); | 
|  | EXPECT_EQ(packet.status(), Status::NOT_FOUND); | 
|  | EXPECT_EQ(packet.channel_id(), 1u); | 
|  | EXPECT_EQ(packet.service_id(), 0u); | 
|  | } | 
|  |  | 
|  | TEST_F(BasicServer, ProcessPacket_UnassignedChannel_AssignsToAvalableSlot) { | 
|  | TestOutput<128> unassigned_output; | 
|  | server_.ProcessPacket( | 
|  | EncodeRequest(PacketType::RPC, /*channel_id=*/99, 42, 27), | 
|  | unassigned_output); | 
|  | ASSERT_EQ(channels_[2].id(), 99u); | 
|  |  | 
|  | Packet packet = Packet::FromBuffer(unassigned_output.sent_packet()); | 
|  | EXPECT_EQ(packet.channel_id(), 99u); | 
|  | EXPECT_EQ(packet.service_id(), 42u); | 
|  | EXPECT_EQ(packet.method_id(), 0u);  // No method ID 27 | 
|  | EXPECT_EQ(packet.status(), Status::NOT_FOUND); | 
|  | } | 
|  |  | 
|  | TEST_F(BasicServer, | 
|  | ProcessPacket_UnassignedChannel_SendsResourceExhaustedIfCannotAssign) { | 
|  | channels_[2] = Channel::Create<3>(&output_);  // Occupy only available channel | 
|  |  | 
|  | server_.ProcessPacket( | 
|  | EncodeRequest(PacketType::RPC, /*channel_id=*/99, 42, 27), output_); | 
|  |  | 
|  | Packet packet = Packet::FromBuffer(output_.sent_packet()); | 
|  | EXPECT_EQ(packet.status(), Status::RESOURCE_EXHAUSTED); | 
|  | EXPECT_EQ(packet.channel_id(), 0u); | 
|  | EXPECT_EQ(packet.service_id(), 0u); | 
|  | EXPECT_EQ(packet.method_id(), 0u); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace pw::rpc |