1 // Copyright 2024 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 #include "pw_channel/channel.h"
15
16 #include <optional>
17
18 #include "gtest/gtest.h"
19 #include "pw_allocator/testing.h"
20 #include "pw_assert/check.h"
21 #include "pw_compilation_testing/negative_compilation.h"
22 #include "pw_multibuf/allocator.h"
23 #include "pw_multibuf/simple_allocator.h"
24 #include "pw_preprocessor/compiler.h"
25
26 namespace {
27
28 using ::pw::allocator::test::AllocatorForTest;
29 using ::pw::async2::Context;
30 using ::pw::async2::Pending;
31 using ::pw::async2::Poll;
32 using ::pw::async2::Ready;
33 using ::pw::async2::Waker;
34 using ::pw::channel::ByteChannel;
35 using ::pw::channel::DatagramWriter;
36 using ::pw::channel::kReadable;
37 using ::pw::channel::kReliable;
38 using ::pw::channel::kSeekable;
39 using ::pw::channel::kWritable;
40 using ::pw::multibuf::MultiBuf;
41 using ::pw::multibuf::MultiBufAllocationFuture;
42 using ::pw::multibuf::MultiBufAllocator;
43 using ::pw::multibuf::SimpleAllocator;
44
45 static_assert(sizeof(::pw::channel::AnyChannel) == 2 * sizeof(void*));
46
47 static_assert((kReliable < kReadable) && (kReadable < kWritable) &&
48 (kWritable < kSeekable));
49
50 class ReliableByteReaderWriterStub
51 : public pw::channel::ByteChannel<kReliable, kReadable, kWritable> {
52 private:
53 // Read functions
54
DoPendRead(pw::async2::Context &)55 pw::async2::Poll<pw::Result<pw::multibuf::MultiBuf>> DoPendRead(
56 pw::async2::Context&) override {
57 return pw::async2::Pending();
58 }
59
60 // Write functions
61
62 // Disable maybe-uninitialized: this check fails erroniously on Windows GCC.
63 PW_MODIFY_DIAGNOSTICS_PUSH();
64 PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
DoPendReadyToWrite(pw::async2::Context &)65 pw::async2::Poll<pw::Status> DoPendReadyToWrite(
66 pw::async2::Context&) override {
67 return pw::async2::Pending();
68 }
69 PW_MODIFY_DIAGNOSTICS_POP();
70
DoGetWriteAllocator()71 pw::multibuf::MultiBufAllocator& DoGetWriteAllocator() override {
72 // `DoPendReadyToWrite` never returns `Ready`, so this is not callable.
73 PW_CHECK(false);
74 }
75
DoWrite(pw::multibuf::MultiBuf &&)76 pw::Result<pw::channel::WriteToken> DoWrite(
77 pw::multibuf::MultiBuf&&) override {
78 return pw::Status::Unimplemented();
79 }
80
DoPendFlush(pw::async2::Context &)81 pw::async2::Poll<pw::Result<pw::channel::WriteToken>> DoPendFlush(
82 pw::async2::Context&) override {
83 return pw::async2::Ready(
84 pw::Result<pw::channel::WriteToken>(pw::Status::Unimplemented()));
85 }
86
87 // Common functions
DoPendClose(pw::async2::Context &)88 pw::async2::Poll<pw::Status> DoPendClose(pw::async2::Context&) override {
89 return pw::OkStatus();
90 }
91 };
92
93 class ReadOnlyStub : public pw::channel::ByteReader {
94 public:
95 constexpr ReadOnlyStub() = default;
96
97 private:
98 // Read functions
DoPendRead(pw::async2::Context &)99 pw::async2::Poll<pw::Result<pw::multibuf::MultiBuf>> DoPendRead(
100 pw::async2::Context&) override {
101 return pw::async2::Pending();
102 }
103
DoPendClose(pw::async2::Context &)104 pw::async2::Poll<pw::Status> DoPendClose(pw::async2::Context&) override {
105 return pw::OkStatus();
106 }
107 };
108
109 class WriteOnlyStub : public pw::channel::ByteWriter {
110 private:
111 // Write functions
112
DoPendReadyToWrite(pw::async2::Context &)113 pw::async2::Poll<pw::Status> DoPendReadyToWrite(
114 pw::async2::Context&) override {
115 return pw::async2::Pending();
116 }
117
DoGetWriteAllocator()118 pw::multibuf::MultiBufAllocator& DoGetWriteAllocator() override {
119 PW_CHECK(false);
120 }
121
DoWrite(pw::multibuf::MultiBuf &&)122 pw::Result<pw::channel::WriteToken> DoWrite(
123 pw::multibuf::MultiBuf&&) override {
124 return pw::Status::Unimplemented();
125 }
126
DoPendFlush(pw::async2::Context &)127 pw::async2::Poll<pw::Result<pw::channel::WriteToken>> DoPendFlush(
128 pw::async2::Context&) override {
129 return pw::async2::Ready(
130 pw::Result<pw::channel::WriteToken>(pw::Status::Unimplemented()));
131 }
132
133 // Common functions
DoPendClose(pw::async2::Context &)134 pw::async2::Poll<pw::Status> DoPendClose(pw::async2::Context&) override {
135 return pw::OkStatus();
136 }
137 };
138
TEST(Channel,MethodsShortCircuitAfterCloseReturnsReady)139 TEST(Channel, MethodsShortCircuitAfterCloseReturnsReady) {
140 pw::async2::Dispatcher dispatcher;
141
142 class : public pw::async2::Task {
143 public:
144 ReliableByteReaderWriterStub channel;
145
146 private:
147 pw::async2::Poll<> DoPend(pw::async2::Context& cx) override {
148 EXPECT_TRUE(channel.is_read_open());
149 EXPECT_TRUE(channel.is_write_open());
150 EXPECT_EQ(pw::async2::Ready(pw::OkStatus()), channel.PendClose(cx));
151 EXPECT_FALSE(channel.is_read_open());
152 EXPECT_FALSE(channel.is_write_open());
153
154 EXPECT_EQ(pw::Status::FailedPrecondition(),
155 channel.PendRead(cx)->status());
156 EXPECT_EQ(Ready(pw::Status::FailedPrecondition()),
157 channel.PendReadyToWrite(cx));
158 EXPECT_EQ(pw::Status::FailedPrecondition(),
159 channel.PendFlush(cx)->status());
160 EXPECT_EQ(pw::async2::Ready(pw::Status::FailedPrecondition()),
161 channel.PendClose(cx));
162
163 return pw::async2::Ready();
164 }
165 } test_task;
166 dispatcher.Post(test_task);
167
168 EXPECT_TRUE(dispatcher.RunUntilStalled().IsReady());
169 }
170
TEST(Channel,ReadOnlyChannelOnlyOpenForReads)171 TEST(Channel, ReadOnlyChannelOnlyOpenForReads) {
172 ReadOnlyStub read_only;
173
174 EXPECT_TRUE(read_only.readable());
175 EXPECT_TRUE(read_only.is_read_open());
176 EXPECT_FALSE(read_only.is_write_open());
177 }
178
TEST(Channel,WriteOnlyChannelOnlyOpenForWrites)179 TEST(Channel, WriteOnlyChannelOnlyOpenForWrites) {
180 WriteOnlyStub write_only;
181
182 EXPECT_FALSE(write_only.readable());
183 EXPECT_FALSE(write_only.is_read_open());
184 EXPECT_TRUE(write_only.is_write_open());
185 }
186
187 #if PW_NC_TEST(InvalidOrdering)
188 PW_NC_EXPECT("Properties must be specified in the following order");
Illegal(pw::channel::ByteChannel<kReadable,pw::channel::kReliable> & foo)189 bool Illegal(pw::channel::ByteChannel<kReadable, pw::channel::kReliable>& foo) {
190 return foo.is_read_open();
191 }
192 #elif PW_NC_TEST(NoProperties)
193 PW_NC_EXPECT("At least one of kReadable or kWritable must be provided");
Illegal(pw::channel::ByteChannel<> & foo)194 bool Illegal(pw::channel::ByteChannel<>& foo) { return foo.is_read_open(); }
195 #elif PW_NC_TEST(NoReadOrWrite)
196 PW_NC_EXPECT("At least one of kReadable or kWritable must be provided");
Illegal(pw::channel::ByteChannel<pw::channel::kReliable> & foo)197 bool Illegal(pw::channel::ByteChannel<pw::channel::kReliable>& foo) {
198 return foo.is_read_open();
199 }
200 #elif PW_NC_TEST(TooMany)
201 PW_NC_EXPECT("Too many properties given");
Illegal(pw::channel::ByteChannel<kReliable,kReliable,kReliable,kReadable,kWritable> & foo)202 bool Illegal(
203 pw::channel::
204 ByteChannel<kReliable, kReliable, kReliable, kReadable, kWritable>&
205 foo) {
206 return foo.is_read_open();
207 }
208 #elif PW_NC_TEST(Duplicates)
209 PW_NC_EXPECT("duplicates");
Illegal(pw::channel::ByteChannel<kReadable,kReadable> & foo)210 bool Illegal(pw::channel::ByteChannel<kReadable, kReadable>& foo) {
211 return foo.is_read_open();
212 }
213 #endif // PW_NC_TEST
214
215 class TestByteReader : public ByteChannel<kReliable, kReadable> {
216 public:
TestByteReader()217 TestByteReader() {}
218
PushData(MultiBuf data)219 void PushData(MultiBuf data) {
220 bool was_empty = data_.empty();
221 data_.PushSuffix(std::move(data));
222 if (was_empty) {
223 std::move(read_waker_).Wake();
224 }
225 }
226
227 private:
DoPendRead(Context & cx)228 Poll<pw::Result<MultiBuf>> DoPendRead(Context& cx) override {
229 if (data_.empty()) {
230 read_waker_ = cx.GetWaker(pw::async2::WaitReason::Unspecified());
231 return Pending();
232 }
233 return std::move(data_);
234 }
235
DoPendClose(Context &)236 Poll<pw::Status> DoPendClose(Context&) override {
237 return Ready(pw::OkStatus());
238 }
239
240 Waker read_waker_;
241 MultiBuf data_;
242 };
243
244 class TestDatagramWriter : public DatagramWriter {
245 public:
TestDatagramWriter(MultiBufAllocator & alloc)246 TestDatagramWriter(MultiBufAllocator& alloc) : alloc_(alloc) {}
247
last_datagram() const248 const pw::multibuf::MultiBuf& last_datagram() const { return last_dgram_; }
249
MakeReadyToWrite()250 void MakeReadyToWrite() {
251 PW_CHECK_INT_EQ(
252 state_,
253 kUnavailable,
254 "Can't make writable when write is pending or already writable");
255
256 state_ = kReadyToWrite;
257 std::move(waker_).Wake();
258 }
259
MakeReadyToFlush()260 void MakeReadyToFlush() {
261 PW_CHECK_INT_EQ(state_,
262 kWritePending,
263 "Can't make flushable unless a write is pending");
264
265 state_ = kReadyToFlush;
266 std::move(waker_).Wake();
267 }
268
269 private:
DoPendReadyToWrite(Context & cx)270 Poll<pw::Status> DoPendReadyToWrite(Context& cx) override {
271 if (state_ == kReadyToWrite) {
272 return Ready(pw::OkStatus());
273 }
274
275 waker_ = cx.GetWaker(pw::async2::WaitReason::Unspecified());
276 return Pending();
277 }
278
DoWrite(MultiBuf && buffer)279 pw::Result<pw::channel::WriteToken> DoWrite(MultiBuf&& buffer) override {
280 if (state_ != kReadyToWrite) {
281 return pw::Status::Unavailable();
282 }
283
284 state_ = kWritePending;
285 last_dgram_ = std::move(buffer);
286 return CreateWriteToken(++last_write_);
287 }
288
DoGetWriteAllocator()289 pw::multibuf::MultiBufAllocator& DoGetWriteAllocator() override {
290 return alloc_;
291 }
292
DoPendFlush(Context & cx)293 Poll<pw::Result<pw::channel::WriteToken>> DoPendFlush(Context& cx) override {
294 if (state_ != kReadyToFlush) {
295 waker_ = cx.GetWaker(pw::async2::WaitReason::Unspecified());
296 return Pending();
297 }
298 last_flush_ = last_write_;
299 return Ready(
300 pw::Result<pw::channel::WriteToken>(CreateWriteToken(last_flush_)));
301 }
302
DoPendClose(Context &)303 Poll<pw::Status> DoPendClose(Context&) override {
304 return Ready(pw::OkStatus());
305 }
306
307 enum {
308 kUnavailable,
309 kReadyToWrite,
310 kWritePending,
311 kReadyToFlush,
312 } state_ = kUnavailable;
313 Waker waker_;
314 uint32_t last_write_ = 0;
315 uint32_t last_flush_ = 0;
316 MultiBuf last_dgram_;
317 MultiBufAllocator& alloc_;
318 };
319
TEST(Channel,TestByteReader)320 TEST(Channel, TestByteReader) {
321 static constexpr char kReadData[] = "hello, world";
322 static constexpr size_t kReadDataSize = sizeof(kReadData);
323 static constexpr size_t kArbitraryMetaSize = 512;
324
325 pw::async2::Dispatcher dispatcher;
326 std::array<std::byte, kReadDataSize> data_area;
327 AllocatorForTest<kArbitraryMetaSize> meta_alloc;
328 SimpleAllocator simple_allocator(data_area, meta_alloc);
329 std::optional<MultiBuf> read_buf_opt =
330 simple_allocator.Allocate(kReadDataSize);
331 ASSERT_TRUE(read_buf_opt.has_value());
332 MultiBuf& read_buf = *read_buf_opt;
333
334 class : public pw::async2::Task {
335 public:
336 TestByteReader channel;
337 int test_executed = 0;
338
339 private:
340 Poll<> DoPend(Context& cx) override {
341 auto result = channel.PendRead(cx);
342 if (!result.IsReady()) {
343 return Pending();
344 }
345
346 auto actual_result = std::move(*result);
347 EXPECT_TRUE(actual_result.ok());
348
349 std::byte contents[kReadDataSize] = {};
350
351 EXPECT_EQ(actual_result->size(), sizeof(kReadData));
352 std::copy(actual_result->begin(), actual_result->end(), contents);
353 EXPECT_STREQ(reinterpret_cast<const char*>(contents), kReadData);
354
355 test_executed += 1;
356 return Ready();
357 }
358 } test_task;
359
360 dispatcher.Post(test_task);
361
362 EXPECT_FALSE(dispatcher.RunUntilStalled().IsReady());
363
364 auto kReadDataBytes = reinterpret_cast<const std::byte*>(kReadData);
365 std::copy(kReadDataBytes, kReadDataBytes + kReadDataSize, read_buf.begin());
366 test_task.channel.PushData(std::move(read_buf));
367 EXPECT_TRUE(dispatcher.RunUntilStalled().IsReady());
368
369 EXPECT_EQ(test_task.test_executed, 1);
370 }
371
TEST(Channel,TestDatagramWriter)372 TEST(Channel, TestDatagramWriter) {
373 pw::async2::Dispatcher dispatcher;
374 static constexpr size_t kArbitraryDataSize = 128;
375 static constexpr size_t kArbitraryMetaSize = 512;
376 std::array<std::byte, kArbitraryDataSize> data_area;
377 AllocatorForTest<kArbitraryMetaSize> meta_alloc;
378 SimpleAllocator simple_allocator(data_area, meta_alloc);
379 TestDatagramWriter write_channel(simple_allocator);
380
381 static constexpr char kWriteData[] = "Hello there";
382
383 class SendWriteDataAndFlush : public pw::async2::Task {
384 public:
385 explicit SendWriteDataAndFlush(DatagramWriter& channel, size_t)
386 : channel_(channel) {}
387 int test_executed = 0;
388
389 private:
390 Poll<> DoPend(Context& cx) override {
391 switch (state_) {
392 case kWaitUntilReady: {
393 if (channel_.PendReadyToWrite(cx).IsPending()) {
394 return Pending();
395 }
396 if (!allocation_future_.has_value()) {
397 allocation_future_ =
398 channel_.GetWriteAllocator().AllocateAsync(sizeof(kWriteData));
399 }
400 Poll<std::optional<MultiBuf>> buffer = allocation_future_->Pend(cx);
401 if (buffer.IsPending()) {
402 return Pending();
403 }
404 allocation_future_ = std::nullopt;
405 if (!buffer->has_value()) {
406 // Allocator should have enough space for `kWriteData`.
407 ADD_FAILURE();
408 return Ready();
409 }
410 pw::ConstByteSpan str(pw::as_bytes(pw::span(kWriteData)));
411 std::copy(str.begin(), str.end(), (**buffer).begin());
412 auto token = channel_.Write(std::move(**buffer));
413 PW_CHECK(token.ok());
414 write_token_ = *token;
415 state_ = kFlushPacket;
416 [[fallthrough]];
417 }
418 case kFlushPacket: {
419 auto result = channel_.PendFlush(cx);
420 if (result.IsPending() || **result < write_token_) {
421 return Pending();
422 }
423 test_executed += 1;
424 state_ = kWaitUntilReady;
425 return Ready();
426 }
427 default:
428 PW_CRASH("Illegal value");
429 }
430
431 // This test is INCOMPLETE.
432
433 test_executed += 1;
434 return Ready();
435 }
436
437 enum { kWaitUntilReady, kFlushPacket } state_ = kWaitUntilReady;
438 std::optional<MultiBufAllocationFuture> allocation_future_;
439 DatagramWriter& channel_;
440 pw::channel::WriteToken write_token_;
441 };
442
443 SendWriteDataAndFlush test_task(write_channel, 24601);
444 dispatcher.Post(test_task);
445
446 EXPECT_EQ(dispatcher.RunUntilStalled(), Pending());
447 EXPECT_EQ(dispatcher.RunUntilStalled(), Pending());
448
449 write_channel.MakeReadyToWrite();
450
451 EXPECT_EQ(dispatcher.RunUntilStalled(), Pending());
452 EXPECT_EQ(dispatcher.RunUntilStalled(), Pending());
453
454 write_channel.MakeReadyToFlush();
455
456 EXPECT_EQ(dispatcher.RunUntilStalled(), Ready());
457 EXPECT_EQ(test_task.test_executed, 1);
458
459 std::byte contents[64] = {};
460 const MultiBuf& dgram = write_channel.last_datagram();
461 std::copy(dgram.begin(), dgram.end(), contents);
462 EXPECT_STREQ(reinterpret_cast<const char*>(contents), kWriteData);
463 }
464
TakesAChannel(const pw::channel::AnyChannel &)465 void TakesAChannel(const pw::channel::AnyChannel&) {}
466
TakesAReadableByteChannel(const pw::channel::ByteChannel<kReadable> & channel)467 const pw::channel::ByteChannel<kReadable>& TakesAReadableByteChannel(
468 const pw::channel::ByteChannel<kReadable>& channel) {
469 return channel;
470 }
471
TakesAWritableByteChannel(const pw::channel::ByteChannel<kWritable> &)472 void TakesAWritableByteChannel(const pw::channel::ByteChannel<kWritable>&) {}
473
TEST(Channel,Conversions)474 TEST(Channel, Conversions) {
475 static constexpr size_t kArbitraryDataSize = 128;
476 static constexpr size_t kArbitraryMetaSize = 128;
477 std::array<std::byte, kArbitraryDataSize> data_area;
478 AllocatorForTest<kArbitraryMetaSize> meta_alloc;
479 SimpleAllocator simple_allocator(data_area, meta_alloc);
480
481 const TestByteReader byte_channel;
482 const TestDatagramWriter datagram_channel(simple_allocator);
483
484 TakesAReadableByteChannel(byte_channel);
485 TakesAReadableByteChannel(byte_channel.as<kReadable>());
486 TakesAReadableByteChannel(byte_channel.as<pw::channel::ByteReader>());
487 TakesAReadableByteChannel(
488 byte_channel.as<pw::channel::ByteChannel<kReliable, kReadable>>());
489 TakesAChannel(byte_channel);
490
491 TakesAWritableByteChannel(datagram_channel.IgnoreDatagramBoundaries());
492
493 [[maybe_unused]] const pw::channel::AnyChannel& plain = byte_channel;
494
495 #if PW_NC_TEST(CannotImplicitlyLoseWritability)
496 PW_NC_EXPECT("no matching function for call");
497 TakesAWritableByteChannel(byte_channel);
498 #elif PW_NC_TEST(CannotExplicitlyLoseWritability)
499 PW_NC_EXPECT("Cannot use a non-writable channel as a writable channel");
500 TakesAWritableByteChannel(byte_channel.as<kWritable>());
501 #endif // PW_NC_TEST
502 }
503
504 #if PW_NC_TEST(CannotImplicitlyUseDatagramChannelAsByteChannel)
505 PW_NC_EXPECT("no matching function for call");
DatagramChannelNcTest(pw::channel::DatagramChannel<kReliable,kReadable> & dgram)506 void DatagramChannelNcTest(
507 pw::channel::DatagramChannel<kReliable, kReadable>& dgram) {
508 TakesAReadableByteChannel(dgram);
509 }
510 #elif PW_NC_TEST(CannotExplicitlyUseDatagramChannelAsByteChannel)
511 PW_NC_EXPECT("Datagram and byte channels are not interchangeable");
DatagramChannelNcTest(pw::channel::DatagramChannel<kReliable,kReadable> & dgram)512 void DatagramChannelNcTest(
513 pw::channel::DatagramChannel<kReliable, kReadable>& dgram) {
514 TakesAReadableByteChannel(dgram.as<pw::channel::ByteChannel<kReadable>>());
515 }
516 #endif // PW_NC_TEST
517
518 class Foo {
519 public:
Foo(pw::channel::ByteChannel<kReadable> &)520 Foo(pw::channel::ByteChannel<kReadable>&) {}
521 Foo(const Foo&) = default;
522 };
523
524 // Define additional overloads to ensure the right overload is selected with the
525 // implicit conversion.
TakesAReadableByteChannel(const Foo &)526 [[maybe_unused]] void TakesAReadableByteChannel(const Foo&) {}
TakesAReadableByteChannel(int)527 [[maybe_unused]] void TakesAReadableByteChannel(int) {}
TakesAReadableByteChannel(const pw::channel::DatagramReaderWriter &)528 [[maybe_unused]] void TakesAReadableByteChannel(
529 const pw::channel::DatagramReaderWriter&) {}
530
TEST(Channel,SelectsCorrectOverloadWhenRelyingOnImplicitConversion)531 TEST(Channel, SelectsCorrectOverloadWhenRelyingOnImplicitConversion) {
532 TestByteReader byte_channel;
533
534 [[maybe_unused]] Foo selects_channel_ctor_not_copy_ctor(byte_channel);
535 EXPECT_EQ(&byte_channel.as<pw::channel::ByteChannel<kReadable>>(),
536 &TakesAReadableByteChannel(byte_channel));
537 }
538
539 } // namespace
540