xref: /aosp_15_r20/external/pigweed/pw_channel/public/pw_channel/channel.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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 #pragma once
15 
16 //         __      ___   ___ _  _ ___ _  _  ___
17 //         \ \    / /_\ | _ \ \| |_ _| \| |/ __|
18 //          \ \/\/ / _ \|   / .` || || .` | (_ |
19 //           \_/\_/_/ \_\_|_\_|\_|___|_|\_|\___|
20 //  _____  _____ ___ ___ ___ __  __ ___ _  _ _____ _   _
21 // | __\ \/ / _ \ __| _ \_ _|  \/  | __| \| |_   _/_\ | |
22 // | _| >  <|  _/ _||   /| || |\/| | _|| .` | | |/ _ \| |__
23 // |___/_/\_\_| |___|_|_\___|_|  |_|___|_|\_| |_/_/ \_\____|
24 //
25 // This module is in an early, experimental state. The APIs are in flux and may
26 // change without notice. Please do not rely on it in production code, but feel
27 // free to explore and share feedback with the Pigweed team!
28 
29 #include <cstddef>
30 #include <cstdint>
31 #include <limits>
32 #include <utility>
33 
34 #include "pw_async2/dispatcher.h"
35 #include "pw_async2/poll.h"
36 #include "pw_bytes/span.h"
37 #include "pw_channel/properties.h"
38 #include "pw_multibuf/multibuf.h"
39 #include "pw_result/result.h"
40 #include "pw_status/status.h"
41 
42 namespace pw::channel {
43 
44 /// @addtogroup pw_channel
45 /// @{
46 
47 /// The basic `Channel` type. Unlike `AnyChannel`, the `Channel`'s properties
48 /// are expressed in template parameters and thus reflected in the type.
49 ///
50 /// Properties must be specified in order (`kDatagram`, `kReliable`,
51 /// `kReadable`, `kWritable`, `kSeekable`) and without duplicates.
52 ///
53 /// To implement a `Channel`, inherit from `ChannelImpl` with the specified
54 /// properties.
55 template <DataType kDataType, Property... kProperties>
56 class Channel {
57  public:
58   /// Returns the data type of this channel.
data_type()59   [[nodiscard]] static constexpr DataType data_type() { return kDataType; }
60 
61   /// Returns whether the channel type is reliable.
reliable()62   [[nodiscard]] static constexpr bool reliable() {
63     return ((kProperties == Property::kReliable) || ...);
64   }
65   /// Returns whether the channel type is seekable.
seekable()66   [[nodiscard]] static constexpr bool seekable() {
67     return ((kProperties == Property::kSeekable) || ...);
68   }
69   /// Returns whether the channel type is readable.
readable()70   [[nodiscard]] static constexpr bool readable() {
71     return ((kProperties == Property::kReadable) || ...);
72   }
73   /// Returns whether the channel type is writable.
writable()74   [[nodiscard]] static constexpr bool writable() {
75     return ((kProperties == Property::kWritable) || ...);
76   }
77 
78   /// True if the channel type supports and is open for reading. Always false
79   /// for write-only channels.
80   [[nodiscard]] constexpr bool is_read_open() const;
81 
82   /// True if the channel type supports and is open for writing. Always false
83   /// for read-only channels.
84   [[nodiscard]] constexpr bool is_write_open() const;
85 
86   /// True if the channel is open for reading or writing.
is_read_or_write_open()87   [[nodiscard]] constexpr bool is_read_or_write_open() const {
88     return is_read_open() || is_write_open();
89   }
90 
91   /// @copydoc AnyChannel::PendRead
92   async2::Poll<Result<multibuf::MultiBuf>> PendRead(async2::Context& cx);
93   /// @copydoc AnyChannel::PendReadyToWrite
94   async2::Poll<Status> PendReadyToWrite(pw::async2::Context& cx);
95   /// @copydoc AnyChannel::PendAllocateBuffer
96   async2::Poll<std::optional<multibuf::MultiBuf>> PendAllocateWriteBuffer(
97       async2::Context& cx, size_t min_bytes);
98   /// @copydoc AnyChannel::StageWrite
99   Status StageWrite(multibuf::MultiBuf&& data);
100   /// @copydoc AnyChannel::PendWrite
101   async2::Poll<Status> PendWrite(async2::Context& cx);
102   /// @copydoc AnyChannel::PendClose
103   async2::Poll<pw::Status> PendClose(async2::Context& cx);
104 
105   // Conversions
106 
107   /// Channels may be implicitly converted to other compatible channel types.
108   template <typename Sibling,
109             typename = internal::EnableIfConversionIsValid<Channel, Sibling>>
110   constexpr operator Sibling&() {
111     return as<Sibling>();
112   }
113   template <typename Sibling,
114             typename = internal::EnableIfConversionIsValid<Channel, Sibling>>
115   constexpr operator const Sibling&() const {
116     return as<Sibling>();
117   }
118 
119   /// Returns a reference to this channel as another compatible channel type.
120   template <typename Sibling>
as()121   [[nodiscard]] Sibling& as() {
122     internal::CheckThatConversionIsValid<Channel, Sibling>();
123     return static_cast<Sibling&>(static_cast<AnyChannel&>(*this));
124   }
125   template <typename Sibling>
as()126   [[nodiscard]] const Sibling& as() const {
127     internal::CheckThatConversionIsValid<Channel, Sibling>();
128     return static_cast<const Sibling&>(static_cast<const AnyChannel&>(*this));
129   }
130 
131   /// Returns a reference to this channel as another channel with the specified
132   /// properties, which must be compatible.
133   template <Property... kOtherChannelProperties>
as()134   [[nodiscard]] auto& as() {
135     return as<Channel<data_type(), kOtherChannelProperties...>>();
136   }
137   template <Property... kOtherChannelProperties>
as()138   [[nodiscard]] const auto& as() const {
139     return as<Channel<data_type(), kOtherChannelProperties...>>();
140   }
141 
142   [[nodiscard]] Channel<DataType::kByte, kProperties...>&
IgnoreDatagramBoundaries()143   IgnoreDatagramBoundaries() {
144     static_assert(kDataType == DataType::kDatagram,
145                   "IgnoreDatagramBoundaries() may only be called to use a "
146                   "datagram channel to a byte channel");
147     return static_cast<Channel<DataType::kByte, kProperties...>&>(
148         static_cast<AnyChannel&>(*this));
149   }
150 
151   [[nodiscard]] const Channel<DataType::kByte, kProperties...>&
IgnoreDatagramBoundaries()152   IgnoreDatagramBoundaries() const {
153     static_assert(kDataType == DataType::kDatagram,
154                   "IgnoreDatagramBoundaries() may only be called to use a "
155                   "datagram channel to a byte channel");
156     return static_cast<const Channel<DataType::kByte, kProperties...>&>(
157         static_cast<const AnyChannel&>(*this));
158   }
159 
160  private:
161   static_assert(internal::PropertiesAreValid<kProperties...>());
162 
163   friend class AnyChannel;
164 
165   explicit constexpr Channel() = default;
166 };
167 
168 /// A generic data channel that may support reading or writing bytes or
169 /// datagrams.
170 ///
171 /// Note that this channel should be used from only one ``pw::async::Task``
172 /// at a time, as the ``Pend`` methods are only required to remember the
173 /// latest ``pw::async2::Context`` that was provided. Notably, this means
174 /// that it is not possible to read from the channel in one task while
175 /// writing to it from another task: a single task must own and operate
176 /// the channel. In the future, a wrapper will be offered which will
177 /// allow the channel to be split into a read half and a write half which
178 /// can be used from independent tasks.
179 ///
180 /// To implement a `Channel`, inherit from `ChannelImpl` with the specified
181 /// properties.
182 class AnyChannel
183     : private Channel<DataType::kByte, kReadable>,
184       private Channel<DataType::kByte, kWritable>,
185       private Channel<DataType::kByte, kReadable, kWritable>,
186       private Channel<DataType::kByte, kReliable, kReadable>,
187       private Channel<DataType::kByte, kReliable, kWritable>,
188       private Channel<DataType::kByte, kReliable, kReadable, kWritable>,
189       private Channel<DataType::kDatagram, kReadable>,
190       private Channel<DataType::kDatagram, kWritable>,
191       private Channel<DataType::kDatagram, kReadable, kWritable>,
192       private Channel<DataType::kDatagram, kReliable, kReadable>,
193       private Channel<DataType::kDatagram, kReliable, kWritable>,
194       private Channel<DataType::kDatagram, kReliable, kReadable, kWritable> {
195  public:
196   virtual ~AnyChannel() = default;
197 
198   // Returned by Position() if getting the position is not supported.
199   // TODO: b/323622630 - `Seek` and `Position` are not yet implemented.
200   // static constexpr size_t kUnknownPosition =
201   //     std::numeric_limits<size_t>::max();
202 
203   // Channel properties
204 
205   /// Returns the data type of the channel implementation.
data_type()206   [[nodiscard]] constexpr DataType data_type() const { return data_type_; }
207 
208   /// Returns whether the channel implementation is reliable.
reliable()209   [[nodiscard]] constexpr bool reliable() const {
210     return (properties_ & Property::kReliable) != 0;
211   }
212 
213   /// Returns whether the channel implementation is seekable.
seekable()214   [[nodiscard]] constexpr bool seekable() const {
215     return (properties_ & Property::kSeekable) != 0;
216   }
217 
218   /// Returns whether the channel implementation is readable.
readable()219   [[nodiscard]] constexpr bool readable() const {
220     return (properties_ & Property::kReadable) != 0;
221   }
222 
223   /// Returns whether the channel implementation is writable.
writable()224   [[nodiscard]] constexpr bool writable() const {
225     return (properties_ & Property::kWritable) != 0;
226   }
227 
228   /// True if the channel is open for reading. Always false for write-only
229   /// channels.
is_read_open()230   [[nodiscard]] constexpr bool is_read_open() const { return read_open_; }
231 
232   /// True if the channel is open for writing. Always false for read-only
233   /// channels.
is_write_open()234   [[nodiscard]] constexpr bool is_write_open() const { return write_open_; }
235 
236   /// True if the channel is open for either reading or writing.
is_read_or_write_open()237   [[nodiscard]] constexpr bool is_read_or_write_open() const {
238     return read_open_ || write_open_;
239   }
240 
241   // Read API
242 
243   /// Returns a `pw::multibuf::MultiBuf` with read data, if available. If data
244   /// is not available, invokes `cx.waker()` when it becomes available.
245   ///
246   /// For datagram channels, each successful read yields one complete
247   /// datagram, which may contain zero or more bytes. For byte stream channels,
248   /// each successful read yields one or more bytes.
249   ///
250   /// Channels only support one read operation / waker at a time.
251   ///
252   /// @returns @rst
253   ///
254   /// .. pw-status-codes::
255   ///
256   ///    OK: Data was read into a MultiBuf.
257   ///
258   ///    UNIMPLEMENTED: The channel does not support reading.
259   ///
260   ///    FAILED_PRECONDITION: The channel is closed.
261   ///
262   ///    OUT_OF_RANGE: The end of the stream was reached. This may be though
263   ///    of as reaching the end of a file. Future reads may succeed after
264   ///    ``Seek`` ing backwards, but no more new data will be produced. The
265   ///    channel is still open; writes and seeks may succeed.
266   ///
267   /// @endrst
PendRead(async2::Context & cx)268   async2::Poll<Result<multibuf::MultiBuf>> PendRead(async2::Context& cx) {
269     if (!is_read_open()) {
270       return Status::FailedPrecondition();
271     }
272     async2::Poll<Result<multibuf::MultiBuf>> result = DoPendRead(cx);
273     if (result.IsReady() && result->status().IsFailedPrecondition()) {
274       set_read_closed();
275     }
276     return result;
277   }
278 
279   // Write API
280 
281   /// Checks whether a writeable channel is *currently* writeable.
282   ///
283   /// This should be called before attempting to ``Write``, and may be called
284   /// before allocating a write buffer if trying to reduce memory pressure.
285   ///
286   /// This method will return:
287   ///
288   /// * Ready(OK) - The channel is currently writeable, and a single caller
289   ///   may proceed to ``Write``.
290   /// * Ready(UNIMPLEMENTED) - The channel does not support writing.
291   /// * Ready(FAILED_PRECONDITION) - The channel is closed for writing.
292   /// * Pending - ``cx`` will be awoken when the channel becomes writeable
293   ///   again.
294   ///
295   /// Note: this method will always return ``Ready`` for non-writeable
296   /// channels.
PendReadyToWrite(pw::async2::Context & cx)297   async2::Poll<Status> PendReadyToWrite(pw::async2::Context& cx) {
298     if (!is_write_open()) {
299       return Status::FailedPrecondition();
300     }
301     async2::Poll<Status> result = DoPendReadyToWrite(cx);
302     if (result.IsReady() && result->IsFailedPrecondition()) {
303       set_write_closed();
304     }
305     return result;
306   }
307 
308   /// Attempts to allocate a write buffer of at least `min_bytes` bytes.
309   ///
310   /// On success, returns a `MultiBuf` of at least `min_bytes`.
311   /// This buffer should be filled with data and then passed back into
312   /// `StageWrite`. The user may shrink or fragment the `MultiBuf` during
313   /// its own usage of the buffer, but the `MultiBuf` should be restored
314   /// to its original shape before it is passed to `StageWrite`.
315   ///
316   /// Users should not wait on the result of `PendAllocateWriteBuffer` while
317   /// holding an existing `MultiBuf` from `PendAllocateWriteBuffer`, as this
318   /// can result in deadlocks.
319   ///
320   /// This method will return:
321   ///
322   /// * Ready(buffer) - A buffer of the requested size is provided.
323   /// * Ready(std::nullopt) - `min_bytes` is larger than the maximum buffer
324   ///   size this channel can allocate.
325   /// * Pending - No buffer of at least `min_bytes` is available. The task
326   ///   associated with the provided `pw::async2::Context` will be awoken
327   ///   when a sufficiently-sized buffer becomes available.
PendAllocateWriteBuffer(async2::Context & cx,size_t min_bytes)328   async2::Poll<std::optional<multibuf::MultiBuf>> PendAllocateWriteBuffer(
329       async2::Context& cx, size_t min_bytes) {
330     return DoPendAllocateWriteBuffer(cx, min_bytes);
331   }
332 
333   /// Writes using a previously allocated MultiBuf. Returns a token that
334   /// refers to this write. These tokens are monotonically increasing, and
335   /// PendWrite() returns the value of the latest token it has flushed.
336   ///
337   /// The ``MultiBuf`` argument to ``Write`` may consist of either:
338   ///   (1) A single ``MultiBuf`` allocated by ``PendAllocateWriteBuffer``
339   ///       that has not been combined with any other ``MultiBuf`` s
340   ///       or ``Chunk``s OR
341   ///   (2) A ``MultiBuf`` containing any combination of buffers from sources
342   ///       other than ``PendAllocateWriteBuffer``.
343   ///
344   /// This requirement allows for more efficient use of memory in case (1).
345   /// For example, a ring-buffer implementation of a ``Channel`` may
346   /// specialize ``PendAllocateWriteBuffer`` to return the next section of the
347   /// buffer available for writing.
348   ///
349   /// @returns @rst
350   /// May fail with the following error codes:
351   ///
352   /// .. pw-status-codes::
353   ///
354   ///    OK: Data was accepted by the channel.
355   ///
356   ///    UNIMPLEMENTED: The channel does not support writing.
357   ///
358   ///    UNAVAILABLE: The write failed due to a transient error (only applies
359   ///    to unreliable channels).
360   ///
361   ///    FAILED_PRECONDITION: The channel is closed.
362   ///
363   /// @endrst
StageWrite(multibuf::MultiBuf && data)364   Status StageWrite(multibuf::MultiBuf&& data) {
365     if (!is_write_open()) {
366       return Status::FailedPrecondition();
367     }
368     Status status = DoStageWrite(std::move(data));
369     if (status.IsFailedPrecondition()) {
370       set_write_closed();
371     }
372     return status;
373   }
374 
375   /// Completes pending writes.
376   ///
377   /// Returns a ``async2::Poll`` indicating whether or the write has completed.
378   ///
379   /// * Ready(OK) - All data has been successfully written.
380   /// * Ready(UNIMPLEMENTED) - The channel does not support writing.
381   /// * Ready(FAILED_PRECONDITION) - The channel is closed.
382   /// * Pending - Writing is not complete.
PendWrite(async2::Context & cx)383   async2::Poll<Status> PendWrite(async2::Context& cx) {
384     if (!is_write_open()) {
385       return Status::FailedPrecondition();
386     }
387     async2::Poll<Status> status = DoPendWrite(cx);
388     if (status.IsReady() && status->IsFailedPrecondition()) {
389       set_write_closed();
390     }
391     return status;
392   }
393 
394   /// Seek changes the position in the stream.
395   ///
396   /// TODO: b/323622630 - `Seek` and `Position` are not yet implemented.
397   ///
398   /// Any ``PendRead`` or ``Write`` calls following a call to ``Seek`` will be
399   /// relative to the new position. Already-written data still being flushed
400   /// will be output relative to the old position.
401   ///
402   /// @returns @rst
403   ///
404   /// .. pw-status-codes::
405   ///
406   ///    OK: The current position was successfully changed.
407   ///
408   ///    UNIMPLEMENTED: The channel does not support seeking.
409   ///
410   ///    FAILED_PRECONDITION: The channel is closed.
411   ///
412   ///    NOT_FOUND: The seek was to a valid position, but the channel is no
413   ///    longer capable of seeking to this position (partially seekable
414   ///    channels only).
415   ///
416   ///    OUT_OF_RANGE: The seek went beyond the end of the stream.
417   ///
418   /// @endrst
419   Status Seek(async2::Context& cx, ptrdiff_t position, Whence whence);
420 
421   /// Returns the current position in the stream, or `kUnknownPosition` if
422   /// unsupported.
423   ///
424   /// TODO: b/323622630 - `Seek` and `Position` are not yet implemented.
425   size_t Position() const;
426 
427   /// Closes the channel, flushing any data.
428   ///
429   /// @returns @rst
430   ///
431   /// .. pw-status-codes::
432   ///
433   ///    OK: The channel was closed and all data was sent successfully.
434   ///
435   ///    DATA_LOSS: The channel was closed, but not all previously written
436   ///    data was delivered.
437   ///
438   ///    FAILED_PRECONDITION: Channel was already closed, which can happen
439   ///    out-of-band due to errors.
440   ///
441   /// @endrst
PendClose(async2::Context & cx)442   async2::Poll<pw::Status> PendClose(async2::Context& cx) {
443     if (!is_read_or_write_open()) {
444       return Status::FailedPrecondition();
445     }
446     auto result = DoPendClose(cx);
447     if (result.IsReady()) {
448       set_read_closed();
449       set_write_closed();
450     }
451     return result;
452   }
453 
454  protected:
455   // Marks the channel as closed for reading, but does nothing else.
456   //
457   // PendClose() always marks the channel closed when DoPendClose() returns
458   // Ready(), regardless of the status.
set_read_closed()459   void set_read_closed() { read_open_ = false; }
460 
461   // Marks the channel as closed for writing, but does nothing else.
462   //
463   // PendClose() always marks the channel closed when DoPendClose() returns
464   // Ready(), regardless of the status.
set_write_closed()465   void set_write_closed() { write_open_ = false; }
466 
467  private:
468   template <DataType, Property...>
469   friend class Channel;  // Allow static_casts to AnyChannel
470 
471   template <DataType, Property...>
472   friend class internal::BaseChannelImpl;  // Allow inheritance
473 
474   // `AnyChannel` may only be constructed by deriving from `ChannelImpl`.
AnyChannel(DataType type,uint8_t properties)475   explicit constexpr AnyChannel(DataType type, uint8_t properties)
476       : data_type_(type),
477         properties_(properties),
478         read_open_(readable()),
479         write_open_(writable()) {}
480 
481   // Virtual interface
482 
483   // Read functions
484 
485   virtual async2::Poll<Result<multibuf::MultiBuf>> DoPendRead(
486       async2::Context& cx) = 0;
487 
488   // Write functions
489 
490   virtual async2::Poll<std::optional<multibuf::MultiBuf>>
491   DoPendAllocateWriteBuffer(async2::Context& cx, size_t min_bytes) = 0;
492 
493   virtual pw::async2::Poll<Status> DoPendReadyToWrite(async2::Context& cx) = 0;
494 
495   virtual Status DoStageWrite(multibuf::MultiBuf&& buffer) = 0;
496 
497   virtual pw::async2::Poll<Status> DoPendWrite(async2::Context& cx) = 0;
498 
499   // Seek functions
500   /// TODO: b/323622630 - `Seek` and `Position` are not yet implemented.
501 
502   // virtual Status DoSeek(ptrdiff_t position, Whence whence) = 0;
503 
504   // virtual size_t DoPosition() const = 0;
505 
506   // Common functions
507   virtual async2::Poll<Status> DoPendClose(async2::Context& cx) = 0;
508 
509   DataType data_type_;
510   uint8_t properties_;
511   bool read_open_;
512   bool write_open_;
513 };
514 
515 /// A `ByteChannel` exchanges data as a stream of bytes.
516 template <Property... kProperties>
517 using ByteChannel = Channel<DataType::kByte, kProperties...>;
518 
519 /// A `DatagramChannel` exchanges data as a series of datagrams.
520 template <Property... kProperties>
521 using DatagramChannel = Channel<DataType::kDatagram, kProperties...>;
522 
523 /// Unreliable byte-oriented `Channel` that supports reading.
524 using ByteReader = ByteChannel<kReadable>;
525 /// Unreliable byte-oriented `Channel` that supports writing.
526 using ByteWriter = ByteChannel<kWritable>;
527 /// Unreliable byte-oriented `Channel` that supports reading and writing.
528 using ByteReaderWriter = ByteChannel<kReadable, kWritable>;
529 
530 /// Reliable byte-oriented `Channel` that supports reading.
531 using ReliableByteReader = ByteChannel<kReliable, kReadable>;
532 /// Reliable byte-oriented `Channel` that supports writing.
533 using ReliableByteWriter = ByteChannel<kReliable, kWritable>;
534 /// Reliable byte-oriented `Channel` that supports reading and writing.
535 using ReliableByteReaderWriter = ByteChannel<kReliable, kReadable, kWritable>;
536 
537 /// Unreliable datagram-oriented `Channel` that supports reading.
538 using DatagramReader = DatagramChannel<kReadable>;
539 /// Unreliable datagram-oriented `Channel` that supports writing.
540 using DatagramWriter = DatagramChannel<kWritable>;
541 /// Unreliable datagram-oriented `Channel` that supports reading and writing.
542 using DatagramReaderWriter = DatagramChannel<kReadable, kWritable>;
543 
544 /// Reliable datagram-oriented `Channel` that supports reading.
545 using ReliableDatagramReader = DatagramChannel<kReliable, kReadable>;
546 /// Reliable datagram-oriented `Channel` that supports writing.
547 using ReliableDatagramWriter = DatagramChannel<kReliable, kWritable>;
548 /// Reliable datagram-oriented `Channel` that supports reading and writing.
549 using ReliableDatagramReaderWriter =
550     DatagramChannel<kReliable, kReadable, kWritable>;
551 
552 // Implementation extension classes.
553 
554 /// Extend `ChannelImpl` to implement a channel with the specified properties.
555 /// Unavailable methods on the channel will be stubbed out.
556 ///
557 /// Alternately, inherit from `pw::channel::Implement` with a channel
558 /// reader/writer alias as the template parameter.
559 ///
560 /// A `ChannelImpl` has a corresponding `Channel` type
561 /// (`ChannelImpl<>::Channel`). Call the `channel()` method to convert the
562 /// `ChannelImpl` to its corresponding `Channel`.
563 template <DataType kDataType, Property... kProperties>
564 class ChannelImpl {
565   static_assert(internal::PropertiesAreValid<kProperties...>());
566 };
567 
568 /// Implement a byte-oriented `Channel` with the specified properties.
569 template <Property... kProperties>
570 using ByteChannelImpl = ChannelImpl<DataType::kByte, kProperties...>;
571 
572 /// Implement a datagram-oriented `Channel` with the specified properties.
573 template <Property... kProperties>
574 using DatagramChannelImpl = ChannelImpl<DataType::kDatagram, kProperties...>;
575 
576 /// Implement the specified `Channel` type. This is intended for use with the
577 /// reader/writer aliases:
578 ///
579 /// @code{.cpp}
580 /// class MyChannel : public pw::channel::Implement<pw::channel::ByteReader> {};
581 /// @endcode
582 template <typename ChannelType>
583 class Implement;
584 
585 /// @}
586 
587 template <DataType kDataType, Property... kProperties>
588 class Implement<Channel<kDataType, kProperties...>>
589     : public ChannelImpl<kDataType, kProperties...> {
590  protected:
591   explicit constexpr Implement() = default;
592 };
593 
594 }  // namespace pw::channel
595 
596 // Include specializations for supported Channel types.
597 #include "pw_channel/internal/channel_specializations.h"
598