100.00% Lines (6/6) 100.00% Functions (3/3)
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   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #ifndef BOOST_CAPY_READ_HPP 10   #ifndef BOOST_CAPY_READ_HPP
11   #define BOOST_CAPY_READ_HPP 11   #define BOOST_CAPY_READ_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/cond.hpp> 14   #include <boost/capy/cond.hpp>
15   #include <boost/capy/io_task.hpp> 15   #include <boost/capy/io_task.hpp>
16   #include <boost/capy/buffers.hpp> 16   #include <boost/capy/buffers.hpp>
17   #include <boost/capy/buffers/buffer_slice.hpp> 17   #include <boost/capy/buffers/buffer_slice.hpp>
18   #include <boost/capy/concept/dynamic_buffer.hpp> 18   #include <boost/capy/concept/dynamic_buffer.hpp>
19   #include <boost/capy/concept/read_source.hpp> 19   #include <boost/capy/concept/read_source.hpp>
20   #include <boost/capy/concept/read_stream.hpp> 20   #include <boost/capy/concept/read_stream.hpp>
21   #include <system_error> 21   #include <system_error>
22   22  
23   #include <cstddef> 23   #include <cstddef>
24   24  
25   namespace boost { 25   namespace boost {
26   namespace capy { 26   namespace capy {
27   27  
28 - /** Asynchronously read until the buffer sequence is full. 28 + /** Read data from a stream until the buffer sequence is full.
29   29  
30 - Reads data from the stream by calling `read_some` repeatedly 30 + @par Await-effects
31 - until the entire buffer sequence is filled or an error occurs.  
32   31  
33 - @li The operation completes when: 32 + Reads data from `stream` via awaiting `stream.read_some` repeatedly
34 - @li The buffer sequence is completely filled 33 + until:
35 - @li An error occurs (including `cond::eof`)  
36 - @li The operation is cancelled  
37   34  
38 - @par Cancellation 35 + @li either the entire buffer sequence @c buffers is filled,
39 - Supports cancellation via `stop_token` propagated through the 36 + @li or a contingency occurs.
40 - IoAwaitable protocol. When cancelled, returns with `cond::canceled`.  
41   37  
42 - @param stream The stream to read from. The caller retains ownership. 38 + If `buffer_size(buffers) == 0` then no awaiting `stream.read_some`
43 - @param buffers The buffer sequence to fill. The caller retains 39 + is performed. This is not a contingency.
44 - ownership and must ensure validity until the operation completes. 40 +
  41 + @par Await-returns
  42 + An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
  43 +
  44 + Upon a contingency, `n` represents the number of bytes read so far,
  45 + inclusive of the last partial read.
  46 +
  47 + Contingencies:
  48 +
  49 + @li The first contingency reported from awaiting @c stream.read_some .
  50 +
  51 + Notable conditions:
  52 +
  53 + @li @c cond::canceled — Operation was cancelled,
  54 + @li @c cond::eof — Stream reached end before `buffers` was filled.
  55 +
  56 + @par Await-postcondition
  57 + `ec || n == buffer_size(buffers)`.
  58 +
  59 + @param stream The stream to read from. If the lifetime of `stream` ends
  60 + before the coroutine finishes, the behavior is undefined.
  61 +
  62 + @param buffers The buffer sequence to fill. If the lifetime of the buffer
  63 + sequence represented by `buffers` ends before the coroutine finishes, the behavior is undefined.
  64 +
  65 +
  66 + @par Remarks
  67 + Supports _IoAwaitable cancellation_.
45 - @return An awaitable that await-returns `(error_code, std::size_t)`.  
46 - On success, `n` equals `buffer_size(buffers)`. On error,  
47 - `n` is the number of bytes read before the error. Compare  
48 - error codes to conditions:  
49 - @li `cond::eof` - Stream reached end before buffer was filled  
50 - @li `cond::canceled` - Operation was cancelled  
51   68  
52   69  
53   @par Example 70   @par Example
54   71  
55   @code 72   @code
56 - task<> read_message( ReadStream auto& stream ) 73 + capy::task<> process_message(capy::ReadStream auto& stream)
57   { 74   {
58 - char header[16]; 75 + std::vector<char> header(16); // known header size for some protocol
59 - auto [ec, n] = co_await read( stream, mutable_buffer( header ) ); 76 + auto [ec, n] = co_await capy::read(stream, capy::mutable_buffer(header));
60 - if( ec == cond::eof ) 77 + if (ec == capy::cond::eof)
61   co_return; // Connection closed 78   co_return; // Connection closed
62 - if( ec ) 79 + if (ec)
63 - detail::throw_system_error( ec ); 80 + throw std::system_error(ec);
64 - // header contains exactly 16 bytes 81 +
  82 + // at this point `header` contains exactly 16 bytes
65   } 83   }
66   @endcode 84   @endcode
67   85  
68 - @see read_some, ReadStream, MutableBufferSequence 86 + @see ReadStream, MutableBufferSequence
69   */ 87   */
  88 + template <typename S, typename MB>
  89 + requires ReadStream<S> && MutableBufferSequence<MB>
70   auto 90   auto
HITCBC 71 - 94 read( 91 + 94 read(S& stream, MB buffers) ->
72 - ReadStream auto& stream,  
73 - MutableBufferSequence auto buffers) ->  
74   io_task<std::size_t> 92   io_task<std::size_t>
75   { 93   {
76   auto consuming = buffer_slice(buffers); 94   auto consuming = buffer_slice(buffers);
77   std::size_t const total_size = buffer_size(buffers); 95   std::size_t const total_size = buffer_size(buffers);
78   std::size_t total_read = 0; 96   std::size_t total_read = 0;
79   97  
80   while(total_read < total_size) 98   while(total_read < total_size)
81   { 99   {
82   auto [ec, n] = co_await stream.read_some(consuming.data()); 100   auto [ec, n] = co_await stream.read_some(consuming.data());
83   consuming.remove_prefix(n); 101   consuming.remove_prefix(n);
84   total_read += n; 102   total_read += n;
85   if(ec) 103   if(ec)
86   co_return {ec, total_read}; 104   co_return {ec, total_read};
87   } 105   }
88   106  
89   co_return {{}, total_read}; 107   co_return {{}, total_read};
HITCBC 90   188 } 108   188 }
91   109  
92 - /** Asynchronously read all data from a stream into a dynamic buffer. 110 + /** Read all data from a stream into a dynamic buffer.
93   111  
94 - Reads data by calling `read_some` repeatedly until EOF is reached 112 + @par Await-effects
95 - or an error occurs. Data is appended using prepare/commit semantics. 113 +
  114 + Reads data from `stream` via awaiting `stream.read_some` repeatedly
  115 + and appending the results to `dynbuf`,
  116 + until a contingency occurs.
  117 +
  118 + Data is appended using prepare/commit semantics.
96   The buffer grows with 1.5x factor when filled. 119   The buffer grows with 1.5x factor when filled.
97   120  
98 - @li The operation completes when: 121 + @par Await-returns
99 - @li End-of-stream is reached (`cond::eof`)  
100 - @li An error occurs  
101 - @li The operation is cancelled  
102   122  
103 - @par Cancellation 123 + An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
104 - Supports cancellation via `stop_token` propagated through the 124 +
105 - IoAwaitable protocol. When cancelled, returns with `cond::canceled`. 125 + `n` represents the total number of bytes read,
  126 + inclusive of the last partial read.
  127 +
  128 + Contingencies:
  129 +
  130 + @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some .
  131 +
  132 + @par Await-throws
  133 + `std::bad_alloc` when append to `dynbuf` fails.
  134 +
  135 + @param stream The stream to read from. If the lifetime of `stream` ends
  136 + before the coroutine finishes, the behavior is undefined.
  137 +
  138 + @param dynbuf The dynamic buffer to append data to. If the lifetime of the buffer
  139 + sequence represented by `dynbuf` ends before the coroutine finishes, the behavior is undefined.
106 - @param stream The stream to read from. The caller retains ownership.  
107 - @param buffers The dynamic buffer to append data to. Must remain  
108 - valid until the operation completes.  
109   140  
110   @param initial_amount Initial bytes to prepare (default 2048). 141   @param initial_amount Initial bytes to prepare (default 2048).
111   142  
112 - @return An awaitable that await-returns `(error_code, std::size_t)`. 143 +
113 - On success (EOF), `ec` is clear and `n` is total bytes read. 144 + @par Remarks
114 - On error, `n` is bytes read before the error. Compare error 145 + Supports _IoAwaitable cancellation_.
115 - codes to conditions:  
116 - @li `cond::canceled` - Operation was cancelled  
117   146  
118   @par Example 147   @par Example
119   148  
120   @code 149   @code
121 - task<std::string> read_body( ReadStream auto& stream ) 150 + capy::task<std::string> read_body(capy::ReadStream auto& stream)
122   { 151   {
123   std::string body; 152   std::string body;
124 - auto [ec, n] = co_await read( stream, string_dynamic_buffer( &body ) ); 153 + auto [ec, n] = co_await capy::read(stream, capy::dynamic_buffer(body));
125 - if( ec ) 154 + if (ec)
126 - detail::throw_system_error( ec ); 155 + throw std::system_error(ec);
127   return body; 156   return body;
128   } 157   }
129   @endcode 158   @endcode
130   159  
131   @see read_some, ReadStream, DynamicBufferParam 160   @see read_some, ReadStream, DynamicBufferParam
132   */ 161   */
  162 + template <typename S, typename DB>
  163 + requires ReadStream<S> && DynamicBufferParam<DB>
133   auto 164   auto
HITCBC 134   80 read( 165   80 read(
135 - ReadStream auto& stream, 166 + S& stream,
136 - DynamicBufferParam auto&& buffers, 167 + DB&& dynbuf,
137   std::size_t initial_amount = 2048) -> 168   std::size_t initial_amount = 2048) ->
138   io_task<std::size_t> 169   io_task<std::size_t>
139   { 170   {
140   std::size_t amount = initial_amount; 171   std::size_t amount = initial_amount;
141   std::size_t total_read = 0; 172   std::size_t total_read = 0;
142   for(;;) 173   for(;;)
143   { 174   {
144 - auto mb = buffers.prepare(amount); 175 + auto mb = dynbuf.prepare(amount);
145   auto const mb_size = buffer_size(mb); 176   auto const mb_size = buffer_size(mb);
146   auto [ec, n] = co_await stream.read_some(mb); 177   auto [ec, n] = co_await stream.read_some(mb);
147 - buffers.commit(n); 178 + dynbuf.commit(n);
148   total_read += n; 179   total_read += n;
149   if(ec == cond::eof) 180   if(ec == cond::eof)
150   co_return {{}, total_read}; 181   co_return {{}, total_read};
151   if(ec) 182   if(ec)
152   co_return {ec, total_read}; 183   co_return {ec, total_read};
153   if(n == mb_size) 184   if(n == mb_size)
154   amount = amount / 2 + amount; 185   amount = amount / 2 + amount;
155   } 186   }
HITCBC 156   160 } 187   160 }
157   188  
158 - /** Asynchronously read all data from a source into a dynamic buffer. 189 + /** Read all data from a source into a dynamic buffer.
159   190  
160 - Reads data by calling `source.read` repeatedly until EOF is reached 191 + @par Await-effects
161 - or an error occurs. Data is appended using prepare/commit semantics. 192 +
  193 + Reads data from `stream` by calling `source.read` repeatedly
  194 + and appending it to `dynbuf` until a contingency occurs.
  195 + The last, potenitally partial, read is also appended.
  196 +
  197 + Data is appended using prepare/commit semantics.
162   The buffer grows with 1.5x factor when filled. 198   The buffer grows with 1.5x factor when filled.
163   199  
164 - @li The operation completes when: 200 + @par Await-returns
165 - @li End-of-stream is reached (`cond::eof`)  
166 - @li An error occurs  
167 - @li The operation is cancelled  
168   201  
169 - @par Cancellation 202 + An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
170 - Supports cancellation via `stop_token` propagated through the 203 +
171 - IoAwaitable protocol. When cancelled, returns with `cond::canceled`. 204 + `n` represents the total number of bytes read,
  205 + inclusive of the last partial read.
  206 +
  207 +
  208 + Contingencies:
  209 +
  210 + @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some .
  211 +
  212 + @par Await-throws
  213 +
  214 + `std::bad_alloc` when append to `dynbuf` fails.
  215 +
  216 + @param source The source to read from. If the lifetime of `source` ends
  217 + before the coroutine finishes, the behavior is undefined.
  218 +
  219 + @param dynbuf The dynamic buffer to append data to. If the lifetime of the
  220 + buffer sequence represented by `dynbuf` ends before the coroutine finishes,
  221 + the behavior is undefined.
172 - @param source The source to read from. The caller retains ownership.  
173 - @param buffers The dynamic buffer to append data to. Must remain  
174 - valid until the operation completes.  
175   222  
176   @param initial_amount Initial bytes to prepare (default 2048). 223   @param initial_amount Initial bytes to prepare (default 2048).
177   224  
178 - @return An awaitable that await-returns `(error_code, std::size_t)`. 225 + @par Remarks
179 - On success (EOF), `ec` is clear and `n` is total bytes read. 226 + Supports _IoAwaitable cancellation_.
180 - On error, `n` is bytes read before the error. Compare error  
181 - codes to conditions:  
182 - @li `cond::canceled` - Operation was cancelled  
183   227  
184   @par Example 228   @par Example
185   229  
186   @code 230   @code
187 - task<std::string> read_body( ReadSource auto& source ) 231 + capy::task<std::string> read_body(capy::ReadSource auto& source)
188   { 232   {
189   std::string body; 233   std::string body;
190 - auto [ec, n] = co_await read( source, string_dynamic_buffer( &body ) ); 234 + auto [ec, n] = co_await capy::read(source, capy::dynamic_buffer(body));
191 - if( ec ) 235 + if (ec)
192 - detail::throw_system_error( ec ); 236 + throw std::system_error(ec);
193   return body; 237   return body;
194   } 238   }
195   @endcode 239   @endcode
196   240  
197   @see ReadSource, DynamicBufferParam 241   @see ReadSource, DynamicBufferParam
198   */ 242   */
  243 + template <typename S, typename DB>
  244 + requires ReadSource<S> && DynamicBufferParam<DB>
199   auto 245   auto
HITCBC 200   54 read( 246   54 read(
201 - ReadSource auto& source, 247 + S& source,
202 - DynamicBufferParam auto&& buffers, 248 + DB&& dynbuf,
203   std::size_t initial_amount = 2048) -> 249   std::size_t initial_amount = 2048) ->
204   io_task<std::size_t> 250   io_task<std::size_t>
205   { 251   {
206   std::size_t amount = initial_amount; 252   std::size_t amount = initial_amount;
207   std::size_t total_read = 0; 253   std::size_t total_read = 0;
208   for(;;) 254   for(;;)
209   { 255   {
210 - auto mb = buffers.prepare(amount); 256 + auto mb = dynbuf.prepare(amount);
211   auto const mb_size = buffer_size(mb); 257   auto const mb_size = buffer_size(mb);
212   auto [ec, n] = co_await source.read(mb); 258   auto [ec, n] = co_await source.read(mb);
213 - buffers.commit(n); 259 + dynbuf.commit(n);
214   total_read += n; 260   total_read += n;
215   if(ec == cond::eof) 261   if(ec == cond::eof)
216   co_return {{}, total_read}; 262   co_return {{}, total_read};
217   if(ec) 263   if(ec)
218   co_return {ec, total_read}; 264   co_return {ec, total_read};
219   if(n == mb_size) 265   if(n == mb_size)
220   amount = amount / 2 + amount; // 1.5x growth 266   amount = amount / 2 + amount; // 1.5x growth
221   } 267   }
HITCBC 222   108 } 268   108 }
223   269  
224   } // namespace capy 270   } // namespace capy
225   } // namespace boost 271   } // namespace boost
226   272  
227   #endif 273   #endif