• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _module-pw_stream:
2
3.. cpp:namespace-push:: pw::stream
4
5=========
6pw_stream
7=========
8
9``pw_stream`` provides a foundational interface for streaming data from one part
10of a system to another. In the simplest use cases, this is basically a memcpy
11behind a reusable interface that can be passed around the system. On the other
12hand, the flexibility of this interface means a ``pw_stream`` could terminate is
13something more complex, like a UART stream or flash memory.
14
15--------
16Overview
17--------
18At the most basic level, ``pw_stream``'s interfaces provide very simple handles
19to enabling streaming data from one location in a system to an endpoint.
20
21Example:
22
23.. code-block:: cpp
24
25  Status DumpSensorData(pw::stream::Writer& writer) {
26    static char temp[64];
27    ImuSample imu_sample;
28    imu.GetSample(&info);
29    size_t bytes_written = imu_sample.AsCsv(temp, sizeof(temp));
30    return writer.Write(temp, bytes_written);
31  }
32
33In this example, ``DumpSensorData()`` only cares that it has access to a
34:cpp:class:`Writer` that it can use to stream data to using ``Writer::Write()``.
35The :cpp:class:`Writer` itself can be backed by anything that can act as a data
36"sink."
37
38---------------------
39pw::stream Interfaces
40---------------------
41There are three basic capabilities of a stream:
42
43 * Reading -- Bytes can be read from the stream.
44 * Writing -- Bytes can be written to the stream.
45 * Seeking -- The position in the stream can be changed.
46
47``pw_stream`` provides a family of stream classes with different capabilities.
48The most basic class, :cpp:class:`Stream` guarantees no functionality, while the
49most capable class, :cpp:class:`SeekableReaderWriter` supports reading, writing,
50and seeking.
51
52Usage overview
53==============
54
55+-------------------------------------------+-----------------+------------------------------+
56| pw::stream Interfaces                     | Accept in APIs? | Extend to create new stream? |
57+===========================================+=================+==============================+
58| :cpp:class:`Stream`                       | ❌              | ❌                           |
59+-------------------------------------------+-----------------+------------------------------+
60| :cpp:class:`Reader`                       | ✅              | ❌                           |
61|                                           |                 |                              |
62| :cpp:class:`Writer`                       |                 |                              |
63|                                           |                 |                              |
64| :cpp:class:`ReaderWriter`                 |                 |                              |
65+-------------------------------------------+-----------------+------------------------------+
66| :cpp:class:`SeekableReader`               | ✅              | ✅                           |
67|                                           |                 |                              |
68| :cpp:class:`SeekableWriter`               |                 |                              |
69|                                           |                 |                              |
70| :cpp:class:`SeekableReaderWriter`         |                 |                              |
71+-------------------------------------------+-----------------+------------------------------+
72| :cpp:class:`RelativeSeekableReader`       | ✅ (rarely)     | ✅                           |
73|                                           |                 |                              |
74| :cpp:class:`RelativeSeekableWriter`       |                 |                              |
75|                                           |                 |                              |
76| :cpp:class:`RelativeSeekableReaderWriter` |                 |                              |
77+-------------------------------------------+-----------------+------------------------------+
78| :cpp:class:`NonSeekableReader`            | ❌              | ✅                           |
79|                                           |                 |                              |
80| :cpp:class:`NonSeekableWriter`            |                 |                              |
81|                                           |                 |                              |
82| :cpp:class:`NonSeekableReaderWriter`      |                 |                              |
83+-------------------------------------------+-----------------+------------------------------+
84
85Interface documentation
86=======================
87Summary documentation for the ``pw_stream`` interfaces is below. See the API
88comments in `pw_stream/public/pw_stream/stream.h
89<https://cs.pigweed.dev/pigweed/+/main:pw_stream/public/pw_stream/stream.h>`_
90for full details.
91
92.. cpp:class:: Stream
93
94  A generic stream that may support reading, writing, and seeking, but makes no
95  guarantees about whether any operations are supported. Stream serves as the
96  base for the Reader, Writer, and ReaderWriter interfaces.
97
98  Stream cannot be extended directly. Instead, work with one of the derived
99  classes that explicitly supports the required functionality. Stream should
100  almost never be used in APIs; accept a derived class with the required
101  capabilities instead.
102
103  All Stream methods are blocking. They return when the requested operation
104  completes.
105
106  **Public methods**
107
108  .. cpp:function:: bool readable() const
109
110    True if :cpp:func:`Read` is supported.
111
112  .. cpp:function:: bool writable() const
113
114    True if :cpp:func:`Write` is supported.
115
116  .. cpp:function:: bool seekable() const
117
118    True if :cpp:func:`Seek` is supported.
119
120  .. cpp:function:: Result<ByteSpan> Read(ByteSpan buffer)
121  .. cpp:function:: Result<ByteSpan> Read(void* buffer, size_t size_bytes)
122
123    Reads data from the stream into the provided buffer, if supported. As many
124    bytes as are available up to the buffer size are copied into the buffer.
125    Remaining bytes may by read in subsequent calls.
126
127    Returns:
128
129      * OK - Between 1 and dest.size_bytes() were successfully read. Returns
130        the span of read bytes.
131      * UNIMPLEMENTED - This stream does not support writing.
132      * FAILED_PRECONDITION - The Reader  is not in state to read data.
133      * RESOURCE_EXHAUSTED - Unable to read any bytes at this time. No bytes
134        read. Try again once bytes become available.
135      * OUT_OF_RANGE - Reader has been exhausted, similar to EOF. No bytes were
136        read, no more will be read.
137
138  .. cpp:function:: Status Write(ConstByteSpan data)
139
140    Writes the provided data to the stream, if supported.
141
142    Returns:
143
144      * OK - Data was successfully accepted by the stream.
145      * UNIMPLEMENTED - This stream does not support writing.
146      * FAILED_PRECONDITION - The writer is not in a state to accept data.
147      * RESOURCE_EXHAUSTED - The writer was unable to write all of requested data
148        at this time. No data was written.
149      * OUT_OF_RANGE - The Writer has been exhausted, similar to EOF. No data was
150        written; no more will be written.
151
152
153  .. cpp:function:: Status Seek(ptrdiff_t offset, Whence origin = kBeginning)
154
155    Changes the current read & write position in the stream, if supported.
156
157    Returns:
158
159      * OK - Successfully updated the position.
160      * UNIMPLEMENTED - Seeking is not supported for this stream.
161      * OUT_OF_RANGE - Attempted to seek beyond the bounds of the stream. The
162        position is unchanged.
163
164  .. cpp:function:: size_t Tell() const
165
166    Returns the current read & write position in the stream, if supported.
167    Returns ``Stream::kUnknownPosition`` (``size_t(-1)``) if unsupported.
168
169  .. cpp:function:: size_t ConservativeReadLimit() const
170
171    Likely minimum bytes available to read. Returns ``kUnlimited``
172    (``size_t(-1)``) if there is no limit or it is unknown.
173
174  .. cpp:function:: size_t ConservativeWriteLimit() const
175
176    Likely minimum bytes available to write. Returns ``kUnlimited``
177    (``size_t(-1)``) if there is no limit or it is unknown.
178
179  **Private virtual methods**
180
181  Stream's public methods are non-virtual. The public methods call private
182  virtual methods that are implemented by derived classes.
183
184  .. cpp:function:: private virtual StatusWithSize DoRead(ByteSpan destination)
185
186    Virtual :cpp:func:`Read` function implemented by derived classes.
187
188  .. cpp:function:: private virtual Status DoWrite(ConstByteSpan data)
189
190    Virtual :cpp:func:`Write` function implemented by derived classes.
191
192  .. cpp:function:: private virtual Status DoSeek(ptrdiff_t offset, Whence origin)
193
194    Virtual :cpp:func:`Seek` function implemented by derived classes.
195
196  .. cpp:function:: private virtual size_t DoTell() const
197
198    Virtual :cpp:func:`Tell` function optionally implemented by derived classes.
199    The default implementation always returns ``kUnknownPosition``.
200
201  .. cpp:function:: private virtual size_t ConservativeLimit(LimitType limit_type)
202
203    Virtual function optionally implemented by derived classes that is used for
204    :cpp:func:`ConservativeReadLimit` and :cpp:func:`ConservativeWriteLimit`.
205    The default implementation returns ``kUnlimited`` or ``0`` depending on
206    whether the stream is readable/writable.
207
208Reader interfaces
209-----------------
210.. cpp:class:: Reader : public Stream
211
212   A Stream that supports reading but not writing. The Write() method is hidden.
213
214   Use in APIs when:
215     * Must read from, but not write to, a stream.
216     * May or may not need seeking. Use a SeekableReader& if seeking is
217       required.
218
219   Inherit from when:
220     * Reader cannot be extended directly. Instead, extend SeekableReader,
221       NonSeekableReader, or (rarely) RelativeSeekableReader, as appropriate.
222
223   A Reader may or may not support seeking. Check seekable() or try calling
224   Seek() to determine if the stream is seekable.
225
226.. cpp:class:: SeekableReader : public RelativeSeekableReader
227
228   A Reader that fully supports seeking.
229
230   Use in APIs when:
231     * Absolute seeking is required. Use Reader& if seeking is not required or
232       seek failures can be handled gracefully.
233
234   Inherit from when:
235     * Implementing a reader that supports absolute seeking.
236
237.. cpp:class:: RelativeSeekableReader : public Reader
238
239   A Reader that at least partially supports seeking. Seeking within some range
240   of the current position works, but seeking beyond that or from other origins
241   may or may not be supported. The extent to which seeking is possible is NOT
242   exposed by this API.
243
244   Use in APIs when:
245     * Relative seeking is required. Usage in APIs should be rare; generally
246       Reader should be used instead.
247
248   Inherit from when:
249     * Implementing a Reader that can only support seeking near the current
250       position.
251
252   A buffered Reader that only supports seeking within its buffer is a good
253   example of a RelativeSeekableReader.
254
255.. cpp:class:: NonSeekableReader : public Reader
256
257   A Reader that does not support seeking. The Seek() method is hidden.
258
259   Use in APIs when:
260     * Do NOT use in APIs! If seeking is not required, use Reader& instead.
261
262   Inherit from when:
263     * Implementing a Reader that does not support seeking.
264
265Writer interfaces
266-----------------
267.. cpp:class:: Writer : public Stream
268
269   A Stream that supports writing but not reading. The Read() method is hidden.
270
271   Use in APIs when:
272     * Must write to, but not read from, a stream.
273     * May or may not need seeking. Use a SeekableWriter& if seeking is
274       required.
275
276   Inherit from when:
277     * Writer cannot be extended directly. Instead, extend SeekableWriter,
278       NonSeekableWriter, or (rarely) RelativeSeekableWriter, as appropriate.
279
280   A Writer may or may not support seeking. Check seekable() or try calling
281   Seek() to determine if the stream is seekable.
282
283.. cpp:class:: SeekableWriter : public RelativeSeekableWriter
284
285   A Writer that fully supports seeking.
286
287   Use in APIs when:
288     * Absolute seeking is required. Use Writer& if seeking is not required or
289       seek failures can be handled gracefully.
290
291   Inherit from when:
292     * Implementing a writer that supports absolute seeking.
293
294
295.. cpp:class:: RelativeSeekableWriter : public Writer
296
297   A Writer that at least partially supports seeking. Seeking within some range
298   of the current position works, but seeking beyond that or from other origins
299   may or may not be supported. The extent to which seeking is possible is NOT
300   exposed by this API.
301
302   Use in APIs when:
303     * Relative seeking is required. Usage in APIs should be rare; generally
304       Writer should be used instead.
305
306   Inherit from when:
307     * Implementing a Writer that can only support seeking near the current
308       position.
309
310   A buffered Writer that only supports seeking within its buffer is a good
311   example of a RelativeSeekableWriter.
312
313.. cpp:class:: NonSeekableWriter : public Writer
314
315   A Writer that does not support seeking. The Seek() method is hidden.
316
317   Use in APIs when:
318     * Do NOT use in APIs! If seeking is not required, use Writer& instead.
319
320   Inherit from when:
321     * Implementing a Writer that does not support seeking.
322
323ReaderWriter interfaces
324-----------------------
325.. cpp:class:: ReaderWriter : public Stream
326
327   A Stream that supports both reading and writing.
328
329   Use in APIs when:
330     * Must both read from and write to a stream.
331     * May or may not need seeking. Use a SeekableReaderWriter& if seeking is
332       required.
333
334   Inherit from when:
335     * Cannot extend ReaderWriter directly. Instead, extend
336       SeekableReaderWriter, NonSeekableReaderWriter, or (rarely)
337       RelativeSeekableReaderWriter, as appropriate.
338
339   A ReaderWriter may or may not support seeking. Check seekable() or try
340   calling Seek() to determine if the stream is seekable.
341
342.. cpp:class:: SeekableReaderWriter : public RelativeSeekableReaderWriter
343
344   A ReaderWriter that fully supports seeking.
345
346   Use in APIs when:
347     * Absolute seeking is required. Use ReaderWriter& if seeking is not
348       required or seek failures can be handled gracefully.
349
350   Inherit from when:
351     * Implementing a writer that supports absolute seeking.
352
353.. cpp:class:: RelativeSeekableReaderWriter : public ReaderWriter
354
355   A ReaderWriter that at least partially supports seeking. Seeking within some
356   range of the current position works, but seeking beyond that or from other
357   origins may or may not be supported. The extent to which seeking is possible
358   is NOT exposed by this API.
359
360   Use in APIs when:
361     * Relative seeking is required. Usage in APIs should be rare; generally
362       ReaderWriter should be used instead.
363
364   Inherit from when:
365     * Implementing a ReaderWriter that can only support seeking near the
366       current position.
367
368   A buffered ReaderWriter that only supports seeking within its buffer is a
369   good example of a RelativeSeekableReaderWriter.
370
371.. cpp:class:: NonSeekableReaderWriter : public ReaderWriter
372
373   A ReaderWriter that does not support seeking. The Seek() method is hidden.
374
375   Use in APIs when:
376     * Do NOT use in APIs! If seeking is not required, use ReaderWriter&
377       instead.
378
379   Inherit from when:
380     * Implementing a ReaderWriter that does not support seeking.
381
382---------------
383Implementations
384---------------
385``pw_stream`` includes a few stream implementations for general use.
386
387.. cpp:class:: MemoryWriter : public SeekableWriter
388
389  The ``MemoryWriter`` class implements the :cpp:class:`Writer` interface by
390  backing the data destination with an **externally-provided** memory buffer.
391  ``MemoryWriterBuffer`` extends ``MemoryWriter`` to internally provide a memory
392  buffer.
393
394  The ``MemoryWriter`` can be accessed like a standard C++ container. The
395  contents grow as data is written.
396
397.. cpp:class:: MemoryReader : public SeekableReader
398
399  The ``MemoryReader`` class implements the :cpp:class:`Reader` interface by
400  backing the data source with an **externally-provided** memory buffer.
401
402.. cpp:class:: NullStream : public SeekableReaderWriter
403
404  ``NullStream`` is a no-op stream implementation, similar to ``/dev/null``.
405  Writes are always dropped. Reads always return ``OUT_OF_RANGE``. Seeks have no
406  effect.
407
408.. cpp:class:: CountingNullStream : public SeekableReaderWriter
409
410  ``CountingNullStream`` is a no-op stream implementation, like
411  :cpp:class:`NullStream`, that counts the number of bytes written.
412
413  .. cpp:function:: size_t bytes_written() const
414
415    Returns the number of bytes provided to previous ``Write()`` calls.
416
417.. cpp:class:: StdFileWriter : public SeekableWriter
418
419  ``StdFileWriter`` wraps an ``std::ofstream`` with the :cpp:class:`Writer`
420  interface.
421
422.. cpp:class:: StdFileReader : public SeekableReader
423
424  ``StdFileReader`` wraps an ``std::ifstream`` with the :cpp:class:`Reader`
425  interface.
426
427.. cpp:class:: SocketStream : public NonSeekableReaderWriter
428
429  ``SocketStream`` wraps posix-style TCP sockets with the :cpp:class:`Reader`
430  and :cpp:class:`Writer` interfaces. It can be used to connect to a TCP server,
431  or to communicate with a client via the ``ServerSocket`` class.
432
433.. cpp:class:: ServerSocket
434
435  ``ServerSocket`` wraps a posix server socket, and produces a
436  :cpp:class:`SocketStream` for each accepted client connection.
437
438------------------
439Why use pw_stream?
440------------------
441
442Standard API
443============
444``pw_stream`` provides a standard way for classes to express that they have the
445ability to write data. Writing to one sink versus another sink is a matter of
446just passing a reference to the appropriate :cpp:class:`Writer`.
447
448As an example, imagine dumping sensor data. If written against a random HAL
449or one-off class, there's porting work required to write to a different sink
450(imagine writing over UART vs dumping to flash memory). Building a "dumping"
451implementation against the :cpp:class:`Writer` interface prevents a dependency
452on a bespoke API that would require porting work.
453
454Similarly, after building a :cpp:class:`Writer` implementation for a Sink that
455data could be dumped to, that same :cpp:class:`Writer` can be reused for other
456contexts that already write data to the :cpp:class:`pw::stream::Writer`
457interface.
458
459Before:
460
461.. code-block:: cpp
462
463  // Not reusable, depends on `Uart`.
464  void DumpSensorData(Uart& uart) {
465    static char temp[64];
466    ImuSample imu_sample;
467    imu.GetSample(&info);
468    size_t bytes_written = imu_sample.AsCsv(temp, sizeof(temp));
469    uart.Transmit(temp, bytes_written, /*timeout_ms=*/ 200);
470  }
471
472After:
473
474.. code-block:: cpp
475
476  // Reusable; no more Uart dependency!
477  Status DumpSensorData(Writer& writer) {
478    static char temp[64];
479    ImuSample imu_sample;
480    imu.GetSample(&info);
481    size_t bytes_written = imu_sample.AsCsv(temp, sizeof(temp));
482    return writer.Write(temp, bytes_written);
483  }
484
485Reduce intermediate buffers
486===========================
487Often functions that write larger blobs of data request a buffer is passed as
488the destination that data should be written to. This *requires* a buffer to be
489allocated, even if the data only exists in that buffer for a very short period
490of time before it's written somewhere else.
491
492In situations where data read from somewhere will immediately be written
493somewhere else, a :cpp:class:`Writer` interface can cut out the middleman
494buffer.
495
496Before:
497
498.. code-block:: cpp
499
500  // Requires an intermediate buffer to write the data as CSV.
501  void DumpSensorData(Uart& uart) {
502    char temp[64];
503    ImuSample imu_sample;
504    imu.GetSample(&info);
505    size_t bytes_written = imu_sample.AsCsv(temp, sizeof(temp));
506    uart.Transmit(temp, bytes_written, /*timeout_ms=*/ 200);
507  }
508
509After:
510
511.. code-block:: cpp
512
513  // Both DumpSensorData() and RawSample::AsCsv() use a Writer, eliminating the
514  // need for an intermediate buffer.
515  Status DumpSensorData(Writer& writer) {
516    RawSample imu_sample;
517    imu.GetSample(&info);
518    return imu_sample.AsCsv(writer);
519  }
520
521Prevent buffer overflow
522=======================
523When copying data from one buffer to another, there must be checks to ensure the
524copy does not overflow the destination buffer. As this sort of logic is
525duplicated throughout a codebase, there's more opportunities for bound-checking
526bugs to sneak in. ``Writers`` manage this logic internally rather than pushing
527the bounds checking to the code that is moving or writing the data.
528
529Similarly, since only the :cpp:class:`Writer` has access to any underlying
530buffers, it's harder for functions that share a :cpp:class:`Writer` to
531accidentally clobber data written by others using the same buffer.
532
533Before:
534
535.. code-block:: cpp
536
537  Status BuildPacket(Id dest, span<const std::byte> payload,
538                     span<std::byte> dest) {
539    Header header;
540    if (dest.size_bytes() + payload.size_bytes() < sizeof(Header)) {
541      return Status::ResourceExhausted();
542    }
543    header.dest = dest;
544    header.src = DeviceId();
545    header.payload_size = payload.size_bytes();
546
547    memcpy(dest.data(), &header, sizeof(header));
548    // Forgetting this line would clobber buffer contents. Also, using
549    // a temporary span instead could leave `dest` to be misused elsewhere in
550    // the function.
551    dest = dest.subspan(sizeof(header));
552    memcpy(dest.data(), payload.data(), payload.size_bytes());
553  }
554
555After:
556
557.. code-block:: cpp
558
559  Status BuildPacket(Id dest, span<const std::byte> payload, Writer& writer) {
560    Header header;
561    header.dest = dest;
562    header.src = DeviceId();
563    header.payload_size = payload.size_bytes();
564
565    writer.Write(header);
566    return writer.Write(payload);
567  }
568
569------------
570Design notes
571------------
572
573Sync & Flush
574============
575The :cpp:class:`pw::stream::Stream` API does not include ``Sync()`` or
576``Flush()`` functions. There no mechanism in the :cpp:class:`Stream` API to
577synchronize a :cpp:class:`Reader`'s potentially buffered input with its
578underlying data source. This must be handled by the implementation if required.
579Similarly, the :cpp:class:`Writer` implementation is responsible for flushing
580any buffered data to the sink.
581
582``Flush()`` and ``Sync()`` were excluded from :cpp:class:`Stream` for a few
583reasons:
584
585  * The semantics of when to call ``Flush()``/``Sync()`` on the stream are
586    unclear. The presence of these methods complicates using a
587    :cpp:class:`Reader` or :cpp:class:`Writer`.
588  * Adding one or two additional virtual calls increases the size of all
589    :cpp:class:`Stream` vtables.
590
591Class hierarchy
592===============
593All ``pw_stream`` classes inherit from a single, common base with all possible
594functionality: :cpp:class:`pw::stream::Stream`. This structure has
595some similarities with Python's `io module
596<https://docs.python.org/3/library/io.html>`_ and C#'s `Stream class
597<https://docs.microsoft.com/en-us/dotnet/api/system.io.stream>`_.
598
599An alternative approach is to have the reading, writing, and seeking portions of
600the interface provided by different entities. This is how Go's `io
601<https://pkg.go.dev/io package>`_ and C++'s `input/output library
602<https://en.cppreference.com/w/cpp/io>`_ are structured.
603
604We chose to use a single base class for a few reasons:
605
606* The inheritance hierarchy is simple and linear. Despite the linear
607  hierarchy, combining capabilities is natural with classes like
608  :cpp:class:`ReaderWriter`.
609
610  In C++, separate interfaces for each capability requires either a complex
611  virtual inheritance hierarchy or entirely separate hierarchies for each
612  capability. Separate hierarchies can become cumbersome when trying to
613  combine multiple capabilities. A :cpp:class:`SeekableReaderWriter` would
614  have to implement three different interfaces, which means three different
615  vtables and three vtable pointers in each instance.
616* Stream capabilities are clearly expressed in the type system, while
617  naturally supporting optional functionality. A :cpp:class:`Reader` may
618  or may not support :cpp:func:`Stream::Seek`. Applications that can handle
619  seek failures gracefully way use seek on any :cpp:class:`Reader`. If seeking
620  is strictly necessary, an API can accept a :cpp:class:`SeekableReader`
621  instead.
622
623  Expressing optional functionality in the type system is cumbersome when
624  there are distinct interfaces for each capability. ``Reader``, ``Writer``,
625  and ``Seeker`` interfaces would not be sufficient. To match the flexibility
626  of the current structure, there would have to be separate optional versions
627  of each interface, and classes for various combinations. :cpp:class:`Stream`
628  would be an "OptionalReaderOptionalWriterOptionalSeeker" in this model.
629* Code reuse is maximized. For example, a single
630  :cpp:func:`Stream::ConservativeLimit` implementation supports many stream
631  implementations.
632
633Virtual interfaces
634==================
635``pw_stream`` uses virtual functions. Virtual functions enable runtime
636polymorphism. The same code can be used with any stream implementation.
637
638Virtual functions have inherently has more overhead than a regular function
639call. However, this is true of any polymorphic API. Using a C-style ``struct``
640of function pointers makes different trade-offs but still has more overhead than
641a regular function call.
642
643For many use cases, the overhead of virtual calls insignificant. However, in
644some extremely performance-sensitive contexts, the flexibility of the virtual
645interface may not justify the performance cost.
646
647Asynchronous APIs
648=================
649At present, ``pw_stream`` is synchronous. All :cpp:class:`Stream` API calls are
650expected to block until the operation is complete. This might be undesirable
651for slow operations, like writing to NOR flash.
652
653Pigweed has not yet established a pattern for asynchronous C++ APIs. The
654:cpp:class:`Stream` class may be extended in the future to add asynchronous
655capabilities, or a separate ``AsyncStream`` could be created.
656
657------------
658Dependencies
659------------
660  * :ref:`module-pw_assert`
661  * :ref:`module-pw_preprocessor`
662  * :ref:`module-pw_status`
663  * :ref:`module-pw_span`
664
665.. cpp:namespace-pop::
666
667Zephyr
668======
669To enable ``pw_stream`` for Zephyr add ``CONFIG_PIGWEED_STREAM=y`` to the
670project's configuration.
671