• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 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_transfer/transfer_thread.h"
16 
17 #include "gtest/gtest.h"
18 #include "pw_assert/check.h"
19 #include "pw_bytes/array.h"
20 #include "pw_rpc/raw/client_testing.h"
21 #include "pw_rpc/raw/test_method_context.h"
22 #include "pw_rpc/thread_testing.h"
23 #include "pw_thread/thread.h"
24 #include "pw_thread_stl/options.h"
25 #include "pw_transfer/handler.h"
26 #include "pw_transfer/transfer.h"
27 #include "pw_transfer/transfer.raw_rpc.pb.h"
28 #include "pw_transfer_private/chunk_testing.h"
29 
30 namespace pw::transfer::test {
31 namespace {
32 
33 using internal::Chunk;
34 
35 PW_MODIFY_DIAGNOSTICS_PUSH();
36 PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
37 
38 // TODO(frolv): Have a generic way to obtain a thread for testing on any system.
TransferThreadOptions()39 thread::Options& TransferThreadOptions() {
40   static thread::stl::Options options;
41   return options;
42 }
43 
44 class TransferThreadTest : public ::testing::Test {
45  public:
TransferThreadTest()46   TransferThreadTest()
47       : ctx_(transfer_thread_, 512),
48         max_parameters_(chunk_buffer_.size(),
49                         chunk_buffer_.size(),
50                         cfg::kDefaultExtendWindowDivisor),
51         transfer_thread_(chunk_buffer_, encode_buffer_),
52         system_thread_(TransferThreadOptions(), transfer_thread_) {}
53 
~TransferThreadTest()54   ~TransferThreadTest() {
55     transfer_thread_.Terminate();
56     system_thread_.join();
57   }
58 
59  protected:
60   PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read) ctx_;
61 
62   std::array<std::byte, 64> chunk_buffer_;
63   std::array<std::byte, 64> encode_buffer_;
64 
65   rpc::RawClientTestContext<> rpc_client_context_;
66   internal::TransferParameters max_parameters_;
67 
68   transfer::Thread<1, 1> transfer_thread_;
69 
70   thread::Thread system_thread_;
71 };
72 
73 class SimpleReadTransfer final : public ReadOnlyHandler {
74  public:
SimpleReadTransfer(uint32_t transfer_id,ConstByteSpan data)75   SimpleReadTransfer(uint32_t transfer_id, ConstByteSpan data)
76       : ReadOnlyHandler(transfer_id),
77         prepare_read_called(false),
78         finalize_read_called(false),
79         finalize_read_status(Status::Unknown()),
80         reader_(data) {}
81 
PrepareRead()82   Status PrepareRead() final {
83     PW_CHECK_OK(reader_.Seek(0));
84     set_reader(reader_);
85     prepare_read_called = true;
86     return OkStatus();
87   }
88 
FinalizeRead(Status status)89   void FinalizeRead(Status status) final {
90     finalize_read_called = true;
91     finalize_read_status = status;
92   }
93 
94   bool prepare_read_called;
95   bool finalize_read_called;
96   Status finalize_read_status;
97 
98  private:
99   stream::MemoryReader reader_;
100 };
101 
__anon4f3b7d7a0202(size_t i) 102 constexpr auto kData = bytes::Initialized<32>([](size_t i) { return i; });
103 
TEST_F(TransferThreadTest,AddTransferHandler)104 TEST_F(TransferThreadTest, AddTransferHandler) {
105   auto reader_writer = ctx_.reader_writer();
106   transfer_thread_.SetServerReadStream(reader_writer);
107 
108   SimpleReadTransfer handler(3, kData);
109   transfer_thread_.AddTransferHandler(handler);
110 
111   transfer_thread_.StartServerTransfer(internal::TransferType::kTransmit,
112                                        3,
113                                        3,
114                                        max_parameters_,
115                                        std::chrono::seconds(2),
116                                        0);
117 
118   transfer_thread_.WaitUntilEventIsProcessed();
119 
120   EXPECT_TRUE(handler.prepare_read_called);
121 }
122 
TEST_F(TransferThreadTest,RemoveTransferHandler)123 TEST_F(TransferThreadTest, RemoveTransferHandler) {
124   auto reader_writer = ctx_.reader_writer();
125   transfer_thread_.SetServerReadStream(reader_writer);
126 
127   SimpleReadTransfer handler(3, kData);
128   transfer_thread_.AddTransferHandler(handler);
129   transfer_thread_.RemoveTransferHandler(handler);
130 
131   transfer_thread_.StartServerTransfer(internal::TransferType::kTransmit,
132                                        3,
133                                        3,
134                                        max_parameters_,
135                                        std::chrono::seconds(2),
136                                        0);
137 
138   transfer_thread_.WaitUntilEventIsProcessed();
139 
140   EXPECT_FALSE(handler.prepare_read_called);
141 
142   ASSERT_EQ(ctx_.total_responses(), 1u);
143   auto chunk = DecodeChunk(ctx_.response());
144   EXPECT_EQ(chunk.transfer_id, 3u);
145   ASSERT_TRUE(chunk.status.has_value());
146   EXPECT_EQ(chunk.status.value(), Status::NotFound());
147 }
148 
TEST_F(TransferThreadTest,ProcessChunk_SendsWindow)149 TEST_F(TransferThreadTest, ProcessChunk_SendsWindow) {
150   auto reader_writer = ctx_.reader_writer();
151   transfer_thread_.SetServerReadStream(reader_writer);
152 
153   SimpleReadTransfer handler(3, kData);
154   transfer_thread_.AddTransferHandler(handler);
155 
156   transfer_thread_.StartServerTransfer(internal::TransferType::kTransmit,
157                                        3,
158                                        3,
159                                        max_parameters_,
160                                        std::chrono::seconds(2),
161                                        0);
162 
163   rpc::test::WaitForPackets(ctx_.output(), 2, [this] {
164     // Malformed transfer parameters chunk without a pending_bytes field.
165     transfer_thread_.ProcessServerChunk(
166         EncodeChunk({.transfer_id = 3,
167                      .window_end_offset = 16,
168                      .pending_bytes = 16,
169                      .max_chunk_size_bytes = 8,
170                      .offset = 0,
171                      .type = Chunk::Type::kParametersRetransmit}));
172   });
173 
174   ASSERT_EQ(ctx_.total_responses(), 2u);
175   auto chunk = DecodeChunk(ctx_.responses()[0]);
176   EXPECT_EQ(chunk.transfer_id, 3u);
177   EXPECT_EQ(chunk.offset, 0u);
178   EXPECT_EQ(chunk.data.size(), 8u);
179   EXPECT_EQ(std::memcmp(chunk.data.data(), kData.data(), chunk.data.size()), 0);
180 
181   chunk = DecodeChunk(ctx_.responses()[1]);
182   EXPECT_EQ(chunk.transfer_id, 3u);
183   EXPECT_EQ(chunk.offset, 8u);
184   EXPECT_EQ(chunk.data.size(), 8u);
185   EXPECT_EQ(std::memcmp(chunk.data.data(), kData.data() + 8, chunk.data.size()),
186             0);
187 }
188 
TEST_F(TransferThreadTest,ProcessChunk_Malformed)189 TEST_F(TransferThreadTest, ProcessChunk_Malformed) {
190   auto reader_writer = ctx_.reader_writer();
191   transfer_thread_.SetServerReadStream(reader_writer);
192 
193   SimpleReadTransfer handler(3, kData);
194   transfer_thread_.AddTransferHandler(handler);
195 
196   rpc::test::WaitForPackets(ctx_.output(), 1, [this] {
197     transfer_thread_.StartServerTransfer(internal::TransferType::kTransmit,
198                                          3,
199                                          3,
200                                          max_parameters_,
201                                          std::chrono::seconds(2),
202                                          0);
203 
204     // Malformed transfer parameters chunk without a pending_bytes field.
205     transfer_thread_.ProcessServerChunk(EncodeChunk({.transfer_id = 3}));
206   });
207 
208   ASSERT_EQ(ctx_.total_responses(), 1u);
209   auto chunk = DecodeChunk(ctx_.response());
210   EXPECT_EQ(chunk.transfer_id, 3u);
211   ASSERT_TRUE(chunk.status.has_value());
212   EXPECT_EQ(chunk.status.value(), Status::InvalidArgument());
213 }
214 
215 PW_MODIFY_DIAGNOSTICS_POP();
216 
217 }  // namespace
218 }  // namespace pw::transfer::test
219