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   /** Read data from a stream until the buffer sequence is full. 28   /** Read data from a stream until the buffer sequence is full.
29   29  
30   @par Await-effects 30   @par Await-effects
31   31  
32   Reads data from `stream` via awaiting `stream.read_some` repeatedly 32   Reads data from `stream` via awaiting `stream.read_some` repeatedly
33   until: 33   until:
34   34  
35   @li either the entire buffer sequence @c buffers is filled, 35   @li either the entire buffer sequence @c buffers is filled,
36   @li or a contingency occurs on `stream.read_some`. 36   @li or a contingency occurs on `stream.read_some`.
37   37  
38   If `buffer_size(buffers) == 0` then no awaiting `stream.read_some` 38   If `buffer_size(buffers) == 0` then no awaiting `stream.read_some`
39   is performed. This is not a contingency. 39   is performed. This is not a contingency.
40   40  
41   @par Await-returns 41   @par Await-returns
42   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`. 42   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
43   43  
44   Upon a contingency, `n` represents the number of bytes read so far, 44   Upon a contingency, `n` represents the number of bytes read so far,
45   inclusive of the last partial read. 45   inclusive of the last partial read.
46   46  
47   Contingencies: 47   Contingencies:
48   48  
49   @li The first contingency reported from awaiting @c stream.read_some 49   @li The first contingency reported from awaiting @c stream.read_some
50   while `buffers` is not yet filled. A contingency that accompanies 50   while `buffers` is not yet filled. A contingency that accompanies
51   the read which fills `buffers` is not reported: a completed 51   the read which fills `buffers` is not reported: a completed
52   transfer is a success. 52   transfer is a success.
53   53  
54   Notable conditions: 54   Notable conditions:
55   55  
56   @li @c cond::canceled — Operation was cancelled, 56   @li @c cond::canceled — Operation was cancelled,
57   @li @c cond::eof — Stream reached end before @c buffers was filled. 57   @li @c cond::eof — Stream reached end before @c buffers was filled.
58   58  
59   @par Await-postcondition 59   @par Await-postcondition
60   If `n == buffer_size(buffers)` the transfer completed and `ec` is 60   If `n == buffer_size(buffers)` the transfer completed and `ec` is
61   success; otherwise `ec` is set. 61   success; otherwise `ec` is set.
62   62  
63   @param stream The stream to read from. If the lifetime of `stream` ends 63   @param stream The stream to read from. If the lifetime of `stream` ends
64   before the coroutine finishes, the behavior is undefined. 64   before the coroutine finishes, the behavior is undefined.
65   65  
66   @param buffers The buffer sequence to fill. If the lifetime of the buffer 66   @param buffers The buffer sequence to fill. If the lifetime of the buffer
67   sequence represented by `buffers` ends before the coroutine finishes, the behavior is undefined. 67   sequence represented by `buffers` ends before the coroutine finishes, the behavior is undefined.
68   68  
69   69  
70   @par Remarks 70   @par Remarks
71   Supports _IoAwaitable cancellation_. 71   Supports _IoAwaitable cancellation_.
72   72  
73   73  
74   @par Example 74   @par Example
75   75  
76   @code 76   @code
77   capy::task<> process_message(capy::ReadStream auto& stream) 77   capy::task<> process_message(capy::ReadStream auto& stream)
78   { 78   {
79   std::vector<char> header(16); // known header size for some protocol 79   std::vector<char> header(16); // known header size for some protocol
80   auto [ec, n] = co_await capy::read(stream, capy::mutable_buffer(header)); 80   auto [ec, n] = co_await capy::read(stream, capy::mutable_buffer(header));
81   if (ec == capy::cond::eof) 81   if (ec == capy::cond::eof)
82   co_return; // Connection closed 82   co_return; // Connection closed
83   if (ec) 83   if (ec)
84   throw std::system_error(ec); 84   throw std::system_error(ec);
85   85  
86   // at this point `header` contains exactly 16 bytes 86   // at this point `header` contains exactly 16 bytes
87   } 87   }
88   @endcode 88   @endcode
89   89  
90   @see ReadStream, MutableBufferSequence 90   @see ReadStream, MutableBufferSequence
91   */ 91   */
92   template <typename S, typename MB> 92   template <typename S, typename MB>
93   requires ReadStream<S> && MutableBufferSequence<MB> 93   requires ReadStream<S> && MutableBufferSequence<MB>
94   auto 94   auto
HITCBC 95   98 read(S& stream, MB buffers) -> 95   98 read(S& stream, MB buffers) ->
96   io_task<std::size_t> 96   io_task<std::size_t>
97   { 97   {
98   auto consuming = buffer_slice(buffers); 98   auto consuming = buffer_slice(buffers);
99   std::size_t const total_size = buffer_size(buffers); 99   std::size_t const total_size = buffer_size(buffers);
100   std::size_t total_read = 0; 100   std::size_t total_read = 0;
101   101  
102   while(total_read < total_size) 102   while(total_read < total_size)
103   { 103   {
104   auto [ec, n] = co_await stream.read_some(consuming.data()); 104   auto [ec, n] = co_await stream.read_some(consuming.data());
105   consuming.remove_prefix(n); 105   consuming.remove_prefix(n);
106   total_read += n; 106   total_read += n;
107   // A contingency that still completed the transfer is a success: 107   // A contingency that still completed the transfer is a success:
108   // report it only when the buffer was not filled. 108   // report it only when the buffer was not filled.
109   if(ec && total_read < total_size) 109   if(ec && total_read < total_size)
110   co_return {ec, total_read}; 110   co_return {ec, total_read};
111   } 111   }
112   112  
113   co_return {{}, total_read}; 113   co_return {{}, total_read};
HITCBC 114   196 } 114   196 }
115   115  
116   /** Read all data from a stream into a dynamic buffer. 116   /** Read all data from a stream into a dynamic buffer.
117   117  
118   @par Await-effects 118   @par Await-effects
119   119  
120   Reads data from `stream` via awaiting `stream.read_some` repeatedly 120   Reads data from `stream` via awaiting `stream.read_some` repeatedly
121   and appending it to `dynbuf` using prepare/commit semantics 121   and appending it to `dynbuf` using prepare/commit semantics
122   until: 122   until:
123   123  
124   @li either @c dynbuf.size() == @c dynbuf.max_size() , 124   @li either @c dynbuf.size() == @c dynbuf.max_size() ,
125   @li or a contingency on @c stream.read_some occurs. 125   @li or a contingency on @c stream.read_some occurs.
126   126  
127   The last, potenitally partial, read is also appended. 127   The last, potenitally partial, read is also appended.
128   128  
129 - The value passed in the first call to `dynbuf.prepare` is the smallest of 129 + The value passed in the first call to `dynbuf.prepare` is `initial_amount`.
130 - `initial_amount` and `dynbuf.max_size() - dynbuf.size()`. Value passed 130 + The value is grown to 1.5 times the preceding value only after a read that
131 - to each subsequent call is 1.5 the value passed in the preceding call. 131 + completely filled the prepared buffer; otherwise it is left unchanged for
  132 + the next call.
132   133  
133   134  
134   @par Await-returns 135   @par Await-returns
135   136  
136   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`. 137   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
137   138  
138   `n` represents the total number of bytes read, 139   `n` represents the total number of bytes read,
139   inclusive of the last partial read. 140   inclusive of the last partial read.
140   141  
141   Contingencies: 142   Contingencies:
142   143  
143   @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some . 144   @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some .
144   145  
145   146  
146   @par Await-throws 147   @par Await-throws
147   148  
148   Whatever operations on @c dunbuf throw. 149   Whatever operations on @c dunbuf throw.
149   150  
150   (Note: types modeling @c DynamicBufferParam provided by Capy throw 151   (Note: types modeling @c DynamicBufferParam provided by Capy throw
151   @c std::bad_alloc from member function 152   @c std::bad_alloc from member function
152   @c prepare .) 153   @c prepare .)
153   154  
154   155  
155   @param stream The stream to read from. If the lifetime of `stream` ends 156   @param stream The stream to read from. If the lifetime of `stream` ends
156   before the coroutine finishes, the behavior is undefined. 157   before the coroutine finishes, the behavior is undefined.
157   158  
158   @param dynbuf The dynamic buffer to append data to. If the lifetime of the buffer 159   @param dynbuf The dynamic buffer to append data to. If the lifetime of the buffer
159   sequence represented by `dynbuf` ends before the coroutine finishes, the behavior is undefined. 160   sequence represented by `dynbuf` ends before the coroutine finishes, the behavior is undefined.
160   161  
161   @param initial_amount Hint for the value to be passed in the initial call to `dynbuf.prepare()` 162   @param initial_amount Hint for the value to be passed in the initial call to `dynbuf.prepare()`
162   (default 2048). 163   (default 2048).
163   164  
164   165  
165   @par Remarks 166   @par Remarks
166   Supports _IoAwaitable cancellation_. 167   Supports _IoAwaitable cancellation_.
167   168  
168   @par Example 169   @par Example
169   170  
170   @code 171   @code
171   capy::task<std::string> read_body(capy::ReadStream auto& stream) 172   capy::task<std::string> read_body(capy::ReadStream auto& stream)
172   { 173   {
173   std::string body; 174   std::string body;
174   auto [ec, n] = co_await capy::read(stream, capy::dynamic_buffer(body)); 175   auto [ec, n] = co_await capy::read(stream, capy::dynamic_buffer(body));
175   if (ec) 176   if (ec)
176   throw std::system_error(ec); 177   throw std::system_error(ec);
177   return body; 178   return body;
178   } 179   }
179   @endcode 180   @endcode
180   181  
181   @see read_some, ReadStream, DynamicBufferParam 182   @see read_some, ReadStream, DynamicBufferParam
182   */ 183   */
183   template <typename S, typename DB> 184   template <typename S, typename DB>
184   requires ReadStream<S> && DynamicBufferParam<DB> 185   requires ReadStream<S> && DynamicBufferParam<DB>
185   auto 186   auto
HITCBC 186   80 read( 187   80 read(
187   S& stream, 188   S& stream,
188   DB&& dynbuf, 189   DB&& dynbuf,
189   std::size_t initial_amount = 2048) -> 190   std::size_t initial_amount = 2048) ->
190   io_task<std::size_t> 191   io_task<std::size_t>
191   { 192   {
192   std::size_t amount = initial_amount; 193   std::size_t amount = initial_amount;
193   std::size_t total_read = 0; 194   std::size_t total_read = 0;
194   for(;;) 195   for(;;)
195   { 196   {
196   auto mb = dynbuf.prepare(amount); 197   auto mb = dynbuf.prepare(amount);
197   auto const mb_size = buffer_size(mb); 198   auto const mb_size = buffer_size(mb);
198   auto [ec, n] = co_await stream.read_some(mb); 199   auto [ec, n] = co_await stream.read_some(mb);
199   dynbuf.commit(n); 200   dynbuf.commit(n);
200   total_read += n; 201   total_read += n;
201   if(ec == cond::eof) 202   if(ec == cond::eof)
202   co_return {{}, total_read}; 203   co_return {{}, total_read};
203   if(ec) 204   if(ec)
204   co_return {ec, total_read}; 205   co_return {ec, total_read};
205   if(n == mb_size) 206   if(n == mb_size)
206   amount = amount / 2 + amount; 207   amount = amount / 2 + amount;
207   } 208   }
HITCBC 208   160 } 209   160 }
209   210  
210   /** Read all data from a source into a dynamic buffer. 211   /** Read all data from a source into a dynamic buffer.
211   212  
212   @par Await-effects 213   @par Await-effects
213   214  
214   Reads data from `stream` by calling `source.read` repeatedly 215   Reads data from `stream` by calling `source.read` repeatedly
215   and appending it to `dynbuf` using prepare/commit semantics 216   and appending it to `dynbuf` using prepare/commit semantics
216   until: 217   until:
217   218  
218   @li either @c dynbuf.size() == @c dynbuf.max_size() , 219   @li either @c dynbuf.size() == @c dynbuf.max_size() ,
219   @li or a contingency on @c stream.read occurs. 220   @li or a contingency on @c stream.read occurs.
220   221  
221   The last, potenitally partial, read is also appended. 222   The last, potenitally partial, read is also appended.
222   223  
223 - The value passed in the first call to `dynbuf.prepare` is the smallest of 224 + The value passed in the first call to `dynbuf.prepare` is `initial_amount`.
224 - `initial_amount` and `dynbuf.max_size() - dynbuf.size()`. Value passed 225 + The value is grown to 1.5 times the preceding value only after a read that
225 - to each subsequent call is 1.5 the value passed in the preceding call. 226 + completely filled the prepared buffer; otherwise it is left unchanged for
  227 + the next call.
226   228  
227   229  
228   @par Await-returns 230   @par Await-returns
229   231  
230   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`. 232   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
231   233  
232   `n` represents the total number of bytes read, 234   `n` represents the total number of bytes read,
233   inclusive of the last partial read. 235   inclusive of the last partial read.
234   236  
235   237  
236   Contingencies: 238   Contingencies:
237   239  
238   @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some . 240   @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some .
239   241  
240   242  
241   @par Await-throws 243   @par Await-throws
242   244  
243   Whatever operations on @c dunbuf throw. 245   Whatever operations on @c dunbuf throw.
244   246  
245   (Note: types modeling @c DynamicBufferParam provided by Capy throw 247   (Note: types modeling @c DynamicBufferParam provided by Capy throw
246   @c std::bad_alloc from member function 248   @c std::bad_alloc from member function
247   @c prepare .) 249   @c prepare .)
248   250  
249   251  
250   @param source The source to read from. If the lifetime of `source` ends 252   @param source The source to read from. If the lifetime of `source` ends
251   before the coroutine finishes, the behavior is undefined. 253   before the coroutine finishes, the behavior is undefined.
252   254  
253   @param dynbuf The dynamic buffer to append data to. If the lifetime of the 255   @param dynbuf The dynamic buffer to append data to. If the lifetime of the
254   buffer sequence represented by `dynbuf` ends before the coroutine finishes, 256   buffer sequence represented by `dynbuf` ends before the coroutine finishes,
255   the behavior is undefined. 257   the behavior is undefined.
256   258  
257   @param initial_amount Hint for the value to be passed in the initial call to `dynbuf.prepare()` 259   @param initial_amount Hint for the value to be passed in the initial call to `dynbuf.prepare()`
258   (default 2048). 260   (default 2048).
259   261  
260   @par Remarks 262   @par Remarks
261   Supports _IoAwaitable cancellation_. 263   Supports _IoAwaitable cancellation_.
262   264  
263   @par Example 265   @par Example
264   266  
265   @code 267   @code
266   capy::task<std::string> read_body(capy::ReadSource auto& source) 268   capy::task<std::string> read_body(capy::ReadSource auto& source)
267   { 269   {
268   std::string body; 270   std::string body;
269   auto [ec, n] = co_await capy::read(source, capy::dynamic_buffer(body)); 271   auto [ec, n] = co_await capy::read(source, capy::dynamic_buffer(body));
270   if (ec) 272   if (ec)
271   throw std::system_error(ec); 273   throw std::system_error(ec);
272   return body; 274   return body;
273   } 275   }
274   @endcode 276   @endcode
275   277  
276   @see ReadSource, DynamicBufferParam 278   @see ReadSource, DynamicBufferParam
277   */ 279   */
278   template <typename S, typename DB> 280   template <typename S, typename DB>
279   requires ReadSource<S> && DynamicBufferParam<DB> 281   requires ReadSource<S> && DynamicBufferParam<DB>
280   auto 282   auto
HITCBC 281   54 read( 283   54 read(
282   S& source, 284   S& source,
283   DB&& dynbuf, 285   DB&& dynbuf,
284   std::size_t initial_amount = 2048) -> 286   std::size_t initial_amount = 2048) ->
285   io_task<std::size_t> 287   io_task<std::size_t>
286   { 288   {
287   std::size_t amount = initial_amount; 289   std::size_t amount = initial_amount;
288   std::size_t total_read = 0; 290   std::size_t total_read = 0;
289   for(;;) 291   for(;;)
290   { 292   {
291   auto mb = dynbuf.prepare(amount); 293   auto mb = dynbuf.prepare(amount);
292   auto const mb_size = buffer_size(mb); 294   auto const mb_size = buffer_size(mb);
293   auto [ec, n] = co_await source.read(mb); 295   auto [ec, n] = co_await source.read(mb);
294   dynbuf.commit(n); 296   dynbuf.commit(n);
295   total_read += n; 297   total_read += n;
296   if(ec == cond::eof) 298   if(ec == cond::eof)
297   co_return {{}, total_read}; 299   co_return {{}, total_read};
298   if(ec) 300   if(ec)
299   co_return {ec, total_read}; 301   co_return {ec, total_read};
300   if(n == mb_size) 302   if(n == mb_size)
301   amount = amount / 2 + amount; // 1.5x growth 303   amount = amount / 2 + amount; // 1.5x growth
302   } 304   }
HITCBC 303   108 } 305   108 }
304   306  
305   } // namespace capy 307   } // namespace capy
306   } // namespace boost 308   } // namespace boost
307   309  
308   #endif 310   #endif