| // Copyright 2024 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_hdlc/router.h" |
| |
| #include "pw_allocator/testing.h" |
| #include "pw_async2/pend_func_task.h" |
| #include "pw_bytes/suffix.h" |
| #include "pw_channel/forwarding_channel.h" |
| #include "pw_channel/loopback_channel.h" |
| #include "pw_containers/inline_queue.h" |
| #include "pw_containers/vector.h" |
| #include "pw_multibuf/simple_allocator.h" |
| |
| namespace pw::hdlc { |
| namespace { |
| |
| using ::pw::allocator::test::AllocatorForTest; |
| using ::pw::async2::Context; |
| using ::pw::async2::Dispatcher; |
| using ::pw::async2::PendFuncTask; |
| using ::pw::async2::Pending; |
| using ::pw::async2::Poll; |
| using ::pw::async2::Ready; |
| using ::pw::async2::Task; |
| using ::pw::async2::Waker; |
| using ::pw::operator"" _b; |
| using ::pw::channel::DatagramReader; |
| using ::pw::channel::DatagramWriter; |
| using ::pw::channel::ForwardingByteChannelPair; |
| using ::pw::channel::ForwardingDatagramChannelPair; |
| using ::pw::channel::LoopbackByteChannel; |
| using ::pw::multibuf::MultiBuf; |
| using ::pw::multibuf::MultiBufAllocator; |
| using ::pw::multibuf::SimpleAllocator; |
| |
| class SimpleAllocatorForTest { |
| public: |
| SimpleAllocatorForTest() : simple_allocator_(data_area_, meta_alloc_) {} |
| MultiBufAllocator& operator*() { return simple_allocator_; } |
| MultiBufAllocator* operator->() { return &simple_allocator_; } |
| |
| private: |
| static constexpr size_t kArbitraryDataSize = 256; |
| static constexpr size_t kArbitraryMetaSize = 2048; |
| std::array<std::byte, kArbitraryDataSize> data_area_; |
| AllocatorForTest<kArbitraryMetaSize> meta_alloc_; |
| SimpleAllocator simple_allocator_; |
| }; |
| |
| class SendDatagrams : public Task { |
| public: |
| SendDatagrams(pw::InlineQueue<MultiBuf>& to_send, DatagramWriter& channel) |
| : to_send_(to_send), channel_(channel) {} |
| |
| private: |
| Poll<> DoPend(Context& cx) final { |
| while (!to_send_.empty()) { |
| if (channel_.PendReadyToWrite(cx).IsPending()) { |
| return Pending(); |
| } |
| EXPECT_EQ(channel_.Write(std::move(to_send_.front())).status(), |
| pw::OkStatus()); |
| to_send_.pop(); |
| } |
| return Ready(); |
| } |
| |
| pw::InlineQueue<MultiBuf>& to_send_; |
| DatagramWriter& channel_; |
| }; |
| |
| static constexpr size_t kMaxReceiveDatagrams = 16; |
| class ReceiveDatagramsUntilClosed : public Task { |
| public: |
| ReceiveDatagramsUntilClosed(DatagramReader& channel) : channel_(channel) {} |
| |
| pw::Vector<MultiBuf, kMaxReceiveDatagrams> received; |
| |
| private: |
| Poll<> DoPend(Context& cx) final { |
| while (true) { |
| Poll<Result<MultiBuf>> result = channel_.PendRead(cx); |
| if (result.IsPending()) { |
| return Pending(); |
| } |
| if (!result->ok()) { |
| EXPECT_EQ(result->status(), pw::Status::FailedPrecondition()); |
| return Ready(); |
| } |
| received.push_back(std::move(**result)); |
| } |
| // Unreachable. |
| return Ready(); |
| } |
| |
| DatagramReader& channel_; |
| }; |
| |
| template <typename ActualIterable, typename ExpectedIterable> |
| void ExpectElementsEqual(const ActualIterable& actual, |
| const ExpectedIterable& expected) { |
| auto actual_iter = actual.begin(); |
| auto expected_iter = expected.begin(); |
| for (; expected_iter != expected.end(); ++actual_iter, ++expected_iter) { |
| ASSERT_NE(actual_iter, actual.end()); |
| EXPECT_EQ(*actual_iter, *expected_iter); |
| } |
| } |
| |
| template <typename ActualIterable, typename T> |
| void ExpectElementsEqual(const ActualIterable& actual, |
| std::initializer_list<T> expected) { |
| ExpectElementsEqual<ActualIterable, std::initializer_list<T>>(actual, |
| expected); |
| } |
| |
| // TODO: b/331285977 - Fuzz test this function. |
| void ExpectSendAndReceive( |
| std::initializer_list<std::initializer_list<std::byte>> data) { |
| SimpleAllocatorForTest alloc; |
| |
| LoopbackByteChannel io_loopback(*alloc); |
| ForwardingDatagramChannelPair outgoing_pair(*alloc); |
| ForwardingDatagramChannelPair incoming_pair(*alloc); |
| |
| static constexpr size_t kMaxSendDatagrams = 16; |
| ASSERT_LE(data.size(), kMaxSendDatagrams); |
| |
| pw::InlineQueue<MultiBuf, kMaxSendDatagrams> datagrams_to_send; |
| for (size_t i = 0; i < data.size(); i++) { |
| std::optional<MultiBuf> buf = alloc->Allocate(std::data(data)[i].size()); |
| ASSERT_TRUE(buf.has_value()); |
| std::copy( |
| std::data(data)[i].begin(), std::data(data)[i].end(), buf->begin()); |
| datagrams_to_send.push(std::move(*buf)); |
| } |
| |
| static constexpr uint64_t kAddress = 27; |
| static constexpr uint64_t kArbitraryAddressOne = 13802183; |
| static constexpr uint64_t kArbitraryAddressTwo = 4284900; |
| static constexpr size_t kDecodeBufferSize = 256; |
| |
| std::array<std::byte, kDecodeBufferSize> decode_buffer; |
| Router router(io_loopback, decode_buffer); |
| PendFuncTask router_task([&router](Context& cx) { return router.Pend(cx); }); |
| |
| SendDatagrams send_task(datagrams_to_send, outgoing_pair.first()); |
| ReceiveDatagramsUntilClosed recv_task(incoming_pair.first()); |
| |
| EXPECT_EQ( |
| router.AddChannel(outgoing_pair.second(), kArbitraryAddressOne, kAddress), |
| OkStatus()); |
| EXPECT_EQ( |
| router.AddChannel(incoming_pair.second(), kAddress, kArbitraryAddressTwo), |
| OkStatus()); |
| |
| Dispatcher dispatcher; |
| dispatcher.Post(router_task); |
| dispatcher.Post(send_task); |
| dispatcher.Post(recv_task); |
| |
| EXPECT_EQ(dispatcher.RunUntilStalled(), Pending()); |
| ASSERT_EQ(recv_task.received.size(), data.size()); |
| for (size_t i = 0; i < data.size(); i++) { |
| ExpectElementsEqual(recv_task.received[i], std::data(data)[i]); |
| } |
| } |
| |
| TEST(Router, SendsAndReceivesSingleDatagram) { |
| ExpectSendAndReceive({{2_b, 4_b, 6_b, 0_b, 1_b}}); |
| } |
| |
| TEST(Router, SendsAndReceivesMultipleDatagrams) { |
| ExpectSendAndReceive({ |
| {1_b, 3_b, 5_b}, |
| {2_b, 4_b, 6_b, 7_b}, |
| }); |
| } |
| |
| TEST(Router, SendsAndReceivesReservedBytes) { |
| ExpectSendAndReceive({ |
| // Control octets. |
| {0x7D_b}, |
| {0x7E_b}, |
| {0x7D_b, 0x7E_b}, |
| {0x7D_b, 0x5E_b}, |
| // XON / XOFF |
| {0x13_b}, |
| {0x11_b}, |
| }); |
| } |
| |
| TEST(Router, PendOnClosedIoChannelReturnsReady) { |
| static constexpr size_t kDecodeBufferSize = 256; |
| |
| SimpleAllocatorForTest alloc; |
| |
| ForwardingByteChannelPair byte_pair(*alloc); |
| std::array<std::byte, kDecodeBufferSize> decode_buffer; |
| Router router(byte_pair.first(), decode_buffer); |
| |
| ForwardingDatagramChannelPair datagram_pair(*alloc); |
| ReceiveDatagramsUntilClosed recv_task(datagram_pair.first()); |
| EXPECT_EQ(router.AddChannel(datagram_pair.second(), |
| /*arbitrary incoming address*/ 5017, |
| /*arbitrary outgoing address*/ 2019), |
| OkStatus()); |
| |
| PendFuncTask router_task([&router](Context& cx) { return router.Pend(cx); }); |
| |
| Dispatcher dispatcher; |
| dispatcher.Post(router_task); |
| dispatcher.Post(recv_task); |
| |
| EXPECT_EQ(dispatcher.RunUntilStalled(), Pending()); |
| |
| // Close the underlying byte channel. |
| Waker null_waker; |
| Context null_cx(dispatcher, null_waker); |
| EXPECT_EQ(byte_pair.second().PendClose(null_cx), Ready(OkStatus())); |
| |
| // Both the router and the receive task should complete. |
| EXPECT_EQ(dispatcher.RunUntilStalled(), Ready()); |
| } |
| |
| } // namespace |
| } // namespace pw::hdlc |