100.00% Lines (46/46) 100.00% Functions (9/9)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
9   // 9   //
10   10  
11   #ifndef BOOST_CAPY_IO_ANY_STREAM_HPP 11   #ifndef BOOST_CAPY_IO_ANY_STREAM_HPP
12   #define BOOST_CAPY_IO_ANY_STREAM_HPP 12   #define BOOST_CAPY_IO_ANY_STREAM_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/concept/read_stream.hpp> 15   #include <boost/capy/concept/read_stream.hpp>
16   #include <boost/capy/concept/write_stream.hpp> 16   #include <boost/capy/concept/write_stream.hpp>
17   #include <boost/capy/io/any_read_stream.hpp> 17   #include <boost/capy/io/any_read_stream.hpp>
18   #include <boost/capy/io/any_write_stream.hpp> 18   #include <boost/capy/io/any_write_stream.hpp>
19   19  
20   #include <concepts> 20   #include <concepts>
21   21  
22   namespace boost { 22   namespace boost {
23   namespace capy { 23   namespace capy {
24   24  
25   /** Dispatches `read_some` and `write_some` through independent type-erased vtables. 25   /** Dispatches `read_some` and `write_some` through independent type-erased vtables.
26   26  
27   This class provides type erasure for any type satisfying both 27   This class provides type erasure for any type satisfying both
28   the @ref ReadStream and @ref WriteStream concepts, enabling 28   the @ref ReadStream and @ref WriteStream concepts, enabling
29   runtime polymorphism for bidirectional I/O operations. 29   runtime polymorphism for bidirectional I/O operations.
30   30  
31   Inherits from both @ref any_read_stream and @ref any_write_stream, 31   Inherits from both @ref any_read_stream and @ref any_write_stream,
32   providing `read_some` and `write_some` operations. Each base 32   providing `read_some` and `write_some` operations. Each base
33   maintains its own cached awaitable storage, allowing concurrent 33   maintains its own cached awaitable storage, allowing concurrent
34   read and write operations. 34   read and write operations.
35   35  
36   The wrapper supports two construction modes: 36   The wrapper supports two construction modes:
37   - **Owning**: Pass by value to transfer ownership. The wrapper 37   - **Owning**: Pass by value to transfer ownership. The wrapper
38   allocates storage and owns the stream. 38   allocates storage and owns the stream.
39   - **Reference**: Pass a pointer to wrap without ownership. The 39   - **Reference**: Pass a pointer to wrap without ownership. The
40   pointed-to stream must outlive this wrapper. 40   pointed-to stream must outlive this wrapper.
41   41  
42   @par Implicit Conversion 42   @par Implicit Conversion
43   This class implicitly converts to `any_read_stream&` or 43   This class implicitly converts to `any_read_stream&` or
44   `any_write_stream&`, allowing it to be passed to functions 44   `any_write_stream&`, allowing it to be passed to functions
45   that accept only one capability. However, do not move through 45   that accept only one capability. However, do not move through
46   a base reference as this would leave the other base in an 46   a base reference as this would leave the other base in an
47   invalid state. 47   invalid state.
48   48  
49   @par Thread Safety 49   @par Thread Safety
50   Not thread-safe. Concurrent operations of the same type 50   Not thread-safe. Concurrent operations of the same type
51   (two reads or two writes) are undefined behavior. One read 51   (two reads or two writes) are undefined behavior. One read
52   and one write may be in flight simultaneously. 52   and one write may be in flight simultaneously.
53   53  
54   @par Example 54   @par Example
55   @code 55   @code
56   void reader(any_read_stream&); 56   void reader(any_read_stream&);
57   void writer(any_write_stream&); 57   void writer(any_write_stream&);
58   58  
59   // Owning - takes ownership of the stream 59   // Owning - takes ownership of the stream
60   any_stream owning_stream(socket{ioc}); 60   any_stream owning_stream(socket{ioc});
61   61  
62   // Reference - wraps without ownership 62   // Reference - wraps without ownership
63   socket sock(ioc); 63   socket sock(ioc);
64   any_stream ref_stream(&sock); 64   any_stream ref_stream(&sock);
65   65  
66   // Use read_some from the any_read_stream base 66   // Use read_some from the any_read_stream base
67   char rdata[1024]; 67   char rdata[1024];
68   mutable_buffer rbuf(rdata, sizeof(rdata)); 68   mutable_buffer rbuf(rdata, sizeof(rdata));
69   auto [ec1, n1] = co_await owning_stream.read_some(std::span(&rbuf, 1)); 69   auto [ec1, n1] = co_await owning_stream.read_some(std::span(&rbuf, 1));
70   70  
71   // Use write_some from the any_write_stream base 71   // Use write_some from the any_write_stream base
72   char wdata[] = "hello"; 72   char wdata[] = "hello";
73   const_buffer wbuf(wdata, sizeof(wdata)); 73   const_buffer wbuf(wdata, sizeof(wdata));
74   auto [ec2, n2] = co_await owning_stream.write_some(std::span(&wbuf, 1)); 74   auto [ec2, n2] = co_await owning_stream.write_some(std::span(&wbuf, 1));
75   75  
76   // Pass to functions expecting one capability 76   // Pass to functions expecting one capability
77   reader(owning_stream); // Implicit upcast 77   reader(owning_stream); // Implicit upcast
78   writer(owning_stream); // Implicit upcast 78   writer(owning_stream); // Implicit upcast
79   @endcode 79   @endcode
80   80  
81   @see any_read_stream, any_write_stream, ReadStream, WriteStream 81   @see any_read_stream, any_write_stream, ReadStream, WriteStream
82   */ 82   */
83   class any_stream 83   class any_stream
84   : public any_read_stream 84   : public any_read_stream
85   , public any_write_stream 85   , public any_write_stream
86   { 86   {
87   void* storage_ = nullptr; 87   void* storage_ = nullptr;
88   void* stream_ptr_ = nullptr; 88   void* stream_ptr_ = nullptr;
89   void (*destroy_)(void*) noexcept = nullptr; 89   void (*destroy_)(void*) noexcept = nullptr;
90   90  
91   public: 91   public:
92   /** Destructor. 92   /** Destructor.
93   93  
94   Destroys the owned stream (if any). Base class destructors 94   Destroys the owned stream (if any). Base class destructors
95   handle their cached awaitable storage. 95   handle their cached awaitable storage.
96   */ 96   */
HITCBC 97   39 ~any_stream() 97   39 ~any_stream()
98   { 98   {
HITCBC 99   39 if(storage_) 99   39 if(storage_)
100   { 100   {
HITCBC 101   3 destroy_(stream_ptr_); 101   3 destroy_(stream_ptr_);
HITCBC 102   3 ::operator delete(storage_); 102   3 ::operator delete(storage_);
103   } 103   }
HITCBC 104   39 } 104   39 }
105   105  
106   /** Construct a default instance. 106   /** Construct a default instance.
107   107  
108   Constructs an empty wrapper. @ref has_value and `operator bool` 108   Constructs an empty wrapper. @ref has_value and `operator bool`
109   report the empty state; calling `read_some` or `write_some` 109   report the empty state; calling `read_some` or `write_some`
110   before the wrapper holds a stream is undefined behavior. 110   before the wrapper holds a stream is undefined behavior.
111   */ 111   */
112   any_stream() = default; 112   any_stream() = default;
113   113  
114   /** Non-copyable. 114   /** Non-copyable.
115   115  
116   The awaitable caches are per-instance and cannot be shared. 116   The awaitable caches are per-instance and cannot be shared.
117   117  
118   @param other The wrapper that would be copied. 118   @param other The wrapper that would be copied.
119   */ 119   */
120   any_stream(any_stream const& other) = delete; 120   any_stream(any_stream const& other) = delete;
121   121  
122   /** Copy assignment is disabled. 122   /** Copy assignment is disabled.
123   123  
124   The awaitable caches are per-instance and cannot be shared. 124   The awaitable caches are per-instance and cannot be shared.
125   125  
126   @param other The wrapper that would be assigned from. 126   @param other The wrapper that would be assigned from.
127   127  
128   @return A reference to `*this`. 128   @return A reference to `*this`.
129   */ 129   */
130   any_stream& operator=(any_stream const& other) = delete; 130   any_stream& operator=(any_stream const& other) = delete;
131   131  
132   /** Construct by moving. 132   /** Construct by moving.
133   133  
134   Transfers ownership from both bases and the owned stream (if any). 134   Transfers ownership from both bases and the owned stream (if any).
135   135  
136   @param other The wrapper to move from. 136   @param other The wrapper to move from.
137   */ 137   */
HITCBC 138   1 any_stream(any_stream&& other) noexcept 138   1 any_stream(any_stream&& other) noexcept
HITCBC 139   1 : any_read_stream(std::move(static_cast<any_read_stream&>(other))) 139   1 : any_read_stream(std::move(static_cast<any_read_stream&>(other)))
HITCBC 140   1 , any_write_stream(std::move(static_cast<any_write_stream&>(other))) 140   1 , any_write_stream(std::move(static_cast<any_write_stream&>(other)))
HITCBC 141   1 , storage_(std::exchange(other.storage_, nullptr)) 141   1 , storage_(std::exchange(other.storage_, nullptr))
HITCBC 142   1 , stream_ptr_(std::exchange(other.stream_ptr_, nullptr)) 142   1 , stream_ptr_(std::exchange(other.stream_ptr_, nullptr))
HITCBC 143   2 , destroy_(std::exchange(other.destroy_, nullptr)) 143   2 , destroy_(std::exchange(other.destroy_, nullptr))
144   { 144   {
HITCBC 145   1 } 145   1 }
146   146  
147   /** Assign by moving. 147   /** Assign by moving.
148   148  
149   Destroys any owned stream and releases existing resources, 149   Destroys any owned stream and releases existing resources,
150   then transfers ownership from `other`. 150   then transfers ownership from `other`.
151   151  
152   @param other The wrapper to move from. 152   @param other The wrapper to move from.
153   @return Reference to this wrapper. 153   @return Reference to this wrapper.
154   */ 154   */
155   any_stream& 155   any_stream&
HITCBC 156   2 operator=(any_stream&& other) noexcept 156   2 operator=(any_stream&& other) noexcept
157   { 157   {
HITCBC 158   2 if(this != &other) 158   2 if(this != &other)
159   { 159   {
HITCBC 160   2 if(storage_) 160   2 if(storage_)
161   { 161   {
HITCBC 162   1 destroy_(stream_ptr_); 162   1 destroy_(stream_ptr_);
HITCBC 163   1 ::operator delete(storage_); 163   1 ::operator delete(storage_);
164   } 164   }
165   static_cast<any_read_stream&>(*this) = 165   static_cast<any_read_stream&>(*this) =
HITCBC 166   2 std::move(static_cast<any_read_stream&>(other)); 166   2 std::move(static_cast<any_read_stream&>(other));
167   static_cast<any_write_stream&>(*this) = 167   static_cast<any_write_stream&>(*this) =
HITCBC 168   2 std::move(static_cast<any_write_stream&>(other)); 168   2 std::move(static_cast<any_write_stream&>(other));
HITCBC 169   2 storage_ = std::exchange(other.storage_, nullptr); 169   2 storage_ = std::exchange(other.storage_, nullptr);
HITCBC 170   2 stream_ptr_ = std::exchange(other.stream_ptr_, nullptr); 170   2 stream_ptr_ = std::exchange(other.stream_ptr_, nullptr);
HITCBC 171   2 destroy_ = std::exchange(other.destroy_, nullptr); 171   2 destroy_ = std::exchange(other.destroy_, nullptr);
172   } 172   }
HITCBC 173   2 return *this; 173   2 return *this;
174   } 174   }
175   175  
176   /** Construct by taking ownership of a bidirectional stream. 176   /** Construct by taking ownership of a bidirectional stream.
177   177  
178   Allocates storage and moves the stream into this wrapper. 178   Allocates storage and moves the stream into this wrapper.
179   The wrapper owns the stream and destroys it. 179   The wrapper owns the stream and destroys it.
180   180  
181   @param s The stream to take ownership of. Must satisfy both 181   @param s The stream to take ownership of. Must satisfy both
182   ReadStream and WriteStream concepts. 182   ReadStream and WriteStream concepts.
183   */ 183   */
184   template<class S> 184   template<class S>
185   requires ReadStream<S> && WriteStream<S> && 185   requires ReadStream<S> && WriteStream<S> &&
186   (!std::same_as<std::decay_t<S>, any_stream>) 186   (!std::same_as<std::decay_t<S>, any_stream>)
HITCBC 187   4 any_stream(S s) 187   4 any_stream(S s)
HITCBC 188   4 { 188   4 {
189   struct guard { 189   struct guard {
190   any_stream* self; 190   any_stream* self;
191   void* ptr = nullptr; 191   void* ptr = nullptr;
192   bool committed = false; 192   bool committed = false;
HITCBC 193   4 ~guard() { 193   4 ~guard() {
HITCBC 194   4 if(!committed && ptr) { 194   4 if(!committed && ptr) {
195   static_cast<S*>(ptr)->~S(); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws 195   static_cast<S*>(ptr)->~S(); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
196   ::operator delete(self->storage_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws 196   ::operator delete(self->storage_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
197   self->storage_ = nullptr; // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws 197   self->storage_ = nullptr; // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
198   } 198   }
HITCBC 199   4 } 199   4 }
HITCBC 200   4 } g{this}; 200   4 } g{this};
201   201  
HITCBC 202   4 storage_ = ::operator new(sizeof(S)); 202   4 storage_ = ::operator new(sizeof(S));
HITCBC 203   4 S* ptr = ::new(storage_) S(std::move(s)); 203   4 S* ptr = ::new(storage_) S(std::move(s));
HITCBC 204   4 g.ptr = ptr; 204   4 g.ptr = ptr;
HITCBC 205   4 stream_ptr_ = ptr; 205   4 stream_ptr_ = ptr;
HITCBC 206   8 destroy_ = +[](void* p) noexcept { static_cast<S*>(p)->~S(); }; 206   8 destroy_ = +[](void* p) noexcept { static_cast<S*>(p)->~S(); };
207   207  
208   // Initialize bases with pointer (reference semantics) 208   // Initialize bases with pointer (reference semantics)
HITCBC 209   4 static_cast<any_read_stream&>(*this) = any_read_stream(ptr); 209   4 static_cast<any_read_stream&>(*this) = any_read_stream(ptr);
HITCBC 210   4 static_cast<any_write_stream&>(*this) = any_write_stream(ptr); 210   4 static_cast<any_write_stream&>(*this) = any_write_stream(ptr);
211   211  
HITCBC 212   4 g.committed = true; 212   4 g.committed = true;
HITCBC 213   4 } 213   4 }
214   214  
215   /** Construct by wrapping a bidirectional stream without ownership. 215   /** Construct by wrapping a bidirectional stream without ownership.
216   216  
217   Wraps the given stream by pointer. The stream must remain 217   Wraps the given stream by pointer. The stream must remain
218   valid for the lifetime of this wrapper. 218   valid for the lifetime of this wrapper.
219   219  
220   @param s Pointer to the stream to wrap. Must satisfy both 220   @param s Pointer to the stream to wrap. Must satisfy both
221   ReadStream and WriteStream concepts. 221   ReadStream and WriteStream concepts.
222   */ 222   */
223   template<class S> 223   template<class S>
224   requires ReadStream<S> && WriteStream<S> 224   requires ReadStream<S> && WriteStream<S>
HITCBC 225   32 any_stream(S* s) 225   32 any_stream(S* s)
226   : any_read_stream(s) 226   : any_read_stream(s)
HITCBC 227   32 , any_write_stream(s) 227   32 , any_write_stream(s)
228   { 228   {
229   // storage_ remains nullptr - no ownership 229   // storage_ remains nullptr - no ownership
HITCBC 230   32 } 230   32 }
231   231  
232   /** Check if the wrapper contains a valid stream. 232   /** Check if the wrapper contains a valid stream.
233   233  
234   Both bases must be valid for the wrapper to be valid. 234   Both bases must be valid for the wrapper to be valid.
235   235  
236   @return `true` if wrapping a stream, `false` if default-constructed 236   @return `true` if wrapping a stream, `false` if default-constructed
237   or moved-from. 237   or moved-from.
238   */ 238   */
239   bool 239   bool
HITCBC 240   12 has_value() const noexcept 240   12 has_value() const noexcept
241   { 241   {
HITCBC 242   19 return any_read_stream::has_value() && 242   19 return any_read_stream::has_value() &&
HITCBC 243   19 any_write_stream::has_value(); 243   19 any_write_stream::has_value();
244   } 244   }
245   245  
246   /** Check if the wrapper contains a valid stream. 246   /** Check if the wrapper contains a valid stream.
247   247  
248   Both bases must be valid for the wrapper to be valid. 248   Both bases must be valid for the wrapper to be valid.
249   249  
250   @return `true` if wrapping a stream, `false` if default-constructed 250   @return `true` if wrapping a stream, `false` if default-constructed
251   or moved-from. 251   or moved-from.
252   */ 252   */
253   explicit 253   explicit
HITCBC 254   2 operator bool() const noexcept 254   2 operator bool() const noexcept
255   { 255   {
HITCBC 256   2 return has_value(); 256   2 return has_value();
257   } 257   }
258   }; 258   };
259   259  
260   } // namespace capy 260   } // namespace capy
261   } // namespace boost 261   } // namespace boost
262   262  
263   #endif 263   #endif