|  | // 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/internal/base_server_writer.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <array> | 
|  | #include <cstdint> | 
|  | #include <cstring> | 
|  |  | 
|  | #include "gtest/gtest.h" | 
|  | #include "pw_rpc/internal/test_method.h" | 
|  | #include "pw_rpc/server_context.h" | 
|  | #include "pw_rpc/service.h" | 
|  | #include "pw_rpc_private/internal_test_utils.h" | 
|  |  | 
|  | namespace pw::rpc { | 
|  |  | 
|  | class TestService : public Service { | 
|  | public: | 
|  | constexpr TestService(uint32_t id) : Service(id, method) {} | 
|  |  | 
|  | static constexpr internal::TestMethodUnion method = internal::TestMethod(8); | 
|  | }; | 
|  |  | 
|  | namespace internal { | 
|  | namespace { | 
|  |  | 
|  | using std::byte; | 
|  |  | 
|  | TEST(BaseServerWriter, ConstructWithContext_StartsOpen) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  |  | 
|  | BaseServerWriter writer(context.get()); | 
|  |  | 
|  | EXPECT_TRUE(writer.open()); | 
|  | } | 
|  |  | 
|  | TEST(BaseServerWriter, Move_ClosesOriginal) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  |  | 
|  | BaseServerWriter moved(context.get()); | 
|  | BaseServerWriter writer(std::move(moved)); | 
|  |  | 
|  | #ifndef __clang_analyzer__ | 
|  | EXPECT_FALSE(moved.open()); | 
|  | #endif  // ignore use-after-move | 
|  | EXPECT_TRUE(writer.open()); | 
|  | } | 
|  |  | 
|  | class FakeServerWriter : public BaseServerWriter { | 
|  | public: | 
|  | FakeServerWriter(ServerCall& context) : BaseServerWriter(context) {} | 
|  |  | 
|  | constexpr FakeServerWriter() = default; | 
|  |  | 
|  | Status Write(std::span<const byte> response) { | 
|  | std::span buffer = AcquirePayloadBuffer(); | 
|  | std::memcpy(buffer.data(), | 
|  | response.data(), | 
|  | std::min(buffer.size(), response.size())); | 
|  | return ReleasePayloadBuffer(buffer.first(response.size())); | 
|  | } | 
|  |  | 
|  | ByteSpan PayloadBuffer() { return AcquirePayloadBuffer(); } | 
|  | const Channel::OutputBuffer& output_buffer() { return buffer(); } | 
|  | }; | 
|  |  | 
|  | TEST(ServerWriter, DefaultConstruct_Closed) { | 
|  | FakeServerWriter writer; | 
|  |  | 
|  | EXPECT_FALSE(writer.open()); | 
|  | } | 
|  |  | 
|  | TEST(ServerWriter, Construct_RegistersWithServer) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  | FakeServerWriter writer(context.get()); | 
|  |  | 
|  | auto& writers = context.server().writers(); | 
|  | EXPECT_FALSE(writers.empty()); | 
|  | auto it = std::find_if( | 
|  | writers.begin(), writers.end(), [&](auto& w) { return &w == &writer; }); | 
|  | ASSERT_NE(it, writers.end()); | 
|  | } | 
|  |  | 
|  | TEST(ServerWriter, Destruct_RemovesFromServer) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  | { FakeServerWriter writer(context.get()); } | 
|  |  | 
|  | auto& writers = context.server().writers(); | 
|  | EXPECT_TRUE(writers.empty()); | 
|  | } | 
|  |  | 
|  | TEST(ServerWriter, Finish_RemovesFromServer) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  | FakeServerWriter writer(context.get()); | 
|  |  | 
|  | EXPECT_EQ(OkStatus(), writer.Finish()); | 
|  |  | 
|  | auto& writers = context.server().writers(); | 
|  | EXPECT_TRUE(writers.empty()); | 
|  | } | 
|  |  | 
|  | TEST(ServerWriter, Finish_SendsCancellationPacket) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  | FakeServerWriter writer(context.get()); | 
|  |  | 
|  | EXPECT_EQ(OkStatus(), writer.Finish()); | 
|  |  | 
|  | const Packet& packet = context.output().sent_packet(); | 
|  | EXPECT_EQ(packet.type(), PacketType::SERVER_STREAM_END); | 
|  | EXPECT_EQ(packet.channel_id(), context.channel_id()); | 
|  | EXPECT_EQ(packet.service_id(), context.service_id()); | 
|  | EXPECT_EQ(packet.method_id(), context.get().method().id()); | 
|  | EXPECT_TRUE(packet.payload().empty()); | 
|  | EXPECT_EQ(packet.status(), OkStatus()); | 
|  | } | 
|  |  | 
|  | TEST(ServerWriter, Finish_ReturnsStatusFromChannelSend) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  | FakeServerWriter writer(context.get()); | 
|  | context.output().set_send_status(Status::Unauthenticated()); | 
|  |  | 
|  | EXPECT_EQ(Status::Unauthenticated(), writer.Finish()); | 
|  | } | 
|  |  | 
|  | TEST(ServerWriter, Close) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  | FakeServerWriter writer(context.get()); | 
|  |  | 
|  | ASSERT_TRUE(writer.open()); | 
|  | EXPECT_EQ(OkStatus(), writer.Finish()); | 
|  | EXPECT_FALSE(writer.open()); | 
|  | EXPECT_EQ(Status::FailedPrecondition(), writer.Finish()); | 
|  | } | 
|  |  | 
|  | TEST(ServerWriter, Close_ReleasesBuffer) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  | FakeServerWriter writer(context.get()); | 
|  |  | 
|  | ASSERT_TRUE(writer.open()); | 
|  | auto buffer = writer.PayloadBuffer(); | 
|  | buffer[0] = std::byte{0}; | 
|  | EXPECT_FALSE(writer.output_buffer().empty()); | 
|  | EXPECT_EQ(OkStatus(), writer.Finish()); | 
|  | EXPECT_FALSE(writer.open()); | 
|  | EXPECT_TRUE(writer.output_buffer().empty()); | 
|  | } | 
|  |  | 
|  | TEST(ServerWriter, Open_SendsPacketWithPayload) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  | FakeServerWriter writer(context.get()); | 
|  |  | 
|  | constexpr byte data[] = {byte{0xf0}, byte{0x0d}}; | 
|  | ASSERT_EQ(OkStatus(), writer.Write(data)); | 
|  |  | 
|  | byte encoded[64]; | 
|  | auto result = context.packet(data).Encode(encoded); | 
|  | ASSERT_EQ(OkStatus(), result.status()); | 
|  |  | 
|  | EXPECT_EQ(result.value().size(), context.output().sent_data().size()); | 
|  | EXPECT_EQ( | 
|  | 0, | 
|  | std::memcmp( | 
|  | encoded, context.output().sent_data().data(), result.value().size())); | 
|  | } | 
|  |  | 
|  | TEST(ServerWriter, Closed_IgnoresFinish) { | 
|  | ServerContextForTest<TestService> context(TestService::method.method()); | 
|  | FakeServerWriter writer(context.get()); | 
|  |  | 
|  | EXPECT_EQ(OkStatus(), writer.Finish()); | 
|  | EXPECT_EQ(Status::FailedPrecondition(), writer.Finish()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace internal | 
|  | }  // namespace pw::rpc |