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 the smallest of
130   `initial_amount` and `dynbuf.max_size() - dynbuf.size()`. Value passed 130   `initial_amount` and `dynbuf.max_size() - dynbuf.size()`. Value passed
131   to each subsequent call is 1.5 the value passed in the preceding call. 131   to each subsequent call is 1.5 the value passed in the preceding call.
132   132  
133   133  
134   @par Await-returns 134   @par Await-returns
135   135  
136   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`. 136   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
137   137  
138   `n` represents the total number of bytes read, 138   `n` represents the total number of bytes read,
139   inclusive of the last partial read. 139   inclusive of the last partial read.
140   140  
141   Contingencies: 141   Contingencies:
142   142  
143   @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some . 143   @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some .
144   144  
145   145  
146   @par Await-throws 146   @par Await-throws
147   147  
148   Whatever operations on @c dunbuf throw. 148   Whatever operations on @c dunbuf throw.
149   149  
150   (Note: types modeling @c DynamicBufferParam provided by Capy throw 150   (Note: types modeling @c DynamicBufferParam provided by Capy throw
151   @c std::bad_alloc from member function 151   @c std::bad_alloc from member function
152   @c prepare .) 152   @c prepare .)
153   153  
154   154  
155   @param stream The stream to read from. If the lifetime of `stream` ends 155   @param stream The stream to read from. If the lifetime of `stream` ends
156   before the coroutine finishes, the behavior is undefined. 156   before the coroutine finishes, the behavior is undefined.
157   157  
158   @param dynbuf The dynamic buffer to append data to. If the lifetime of the buffer 158   @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. 159   sequence represented by `dynbuf` ends before the coroutine finishes, the behavior is undefined.
160   160  
161   @param initial_amount Hint for the value to be passed in the initial call to `dynbuf.prepare()` 161   @param initial_amount Hint for the value to be passed in the initial call to `dynbuf.prepare()`
162   (default 2048). 162   (default 2048).
163   163  
164   164  
165   @par Remarks 165   @par Remarks
166   Supports _IoAwaitable cancellation_. 166   Supports _IoAwaitable cancellation_.
167   167  
168   @par Example 168   @par Example
169   169  
170   @code 170   @code
171   capy::task<std::string> read_body(capy::ReadStream auto& stream) 171   capy::task<std::string> read_body(capy::ReadStream auto& stream)
172   { 172   {
173   std::string body; 173   std::string body;
174   auto [ec, n] = co_await capy::read(stream, capy::dynamic_buffer(body)); 174   auto [ec, n] = co_await capy::read(stream, capy::dynamic_buffer(body));
175   if (ec) 175   if (ec)
176   throw std::system_error(ec); 176   throw std::system_error(ec);
177   return body; 177   return body;
178   } 178   }
179   @endcode 179   @endcode
180   180  
181   @see read_some, ReadStream, DynamicBufferParam 181   @see read_some, ReadStream, DynamicBufferParam
182   */ 182   */
183   template <typename S, typename DB> 183   template <typename S, typename DB>
184   requires ReadStream<S> && DynamicBufferParam<DB> 184   requires ReadStream<S> && DynamicBufferParam<DB>
185   auto 185   auto
HITCBC 186   80 read( 186   80 read(
187   S& stream, 187   S& stream,
188   DB&& dynbuf, 188   DB&& dynbuf,
189   std::size_t initial_amount = 2048) -> 189   std::size_t initial_amount = 2048) ->
190   io_task<std::size_t> 190   io_task<std::size_t>
191   { 191   {
192   std::size_t amount = initial_amount; 192   std::size_t amount = initial_amount;
193   std::size_t total_read = 0; 193   std::size_t total_read = 0;
194   for(;;) 194   for(;;)
195   { 195   {
196   auto mb = dynbuf.prepare(amount); 196   auto mb = dynbuf.prepare(amount);
197   auto const mb_size = buffer_size(mb); 197   auto const mb_size = buffer_size(mb);
198   auto [ec, n] = co_await stream.read_some(mb); 198   auto [ec, n] = co_await stream.read_some(mb);
199   dynbuf.commit(n); 199   dynbuf.commit(n);
200   total_read += n; 200   total_read += n;
201   if(ec == cond::eof) 201   if(ec == cond::eof)
202   co_return {{}, total_read}; 202   co_return {{}, total_read};
203   if(ec) 203   if(ec)
204   co_return {ec, total_read}; 204   co_return {ec, total_read};
205   if(n == mb_size) 205   if(n == mb_size)
206   amount = amount / 2 + amount; 206   amount = amount / 2 + amount;
207   } 207   }
HITCBC 208   160 } 208   160 }
209   209  
210   /** Read all data from a source into a dynamic buffer. 210   /** Read all data from a source into a dynamic buffer.
211   211  
212   @par Await-effects 212   @par Await-effects
213   213  
214   Reads data from `stream` by calling `source.read` repeatedly 214   Reads data from `stream` by calling `source.read` repeatedly
215   and appending it to `dynbuf` using prepare/commit semantics 215   and appending it to `dynbuf` using prepare/commit semantics
216   until: 216   until:
217   217  
218   @li either @c dynbuf.size() == @c dynbuf.max_size() , 218   @li either @c dynbuf.size() == @c dynbuf.max_size() ,
219   @li or a contingency on @c stream.read occurs. 219   @li or a contingency on @c stream.read occurs.
220   220  
221   The last, potenitally partial, read is also appended. 221   The last, potenitally partial, read is also appended.
222   222  
223   The value passed in the first call to `dynbuf.prepare` is the smallest of 223   The value passed in the first call to `dynbuf.prepare` is the smallest of
224   `initial_amount` and `dynbuf.max_size() - dynbuf.size()`. Value passed 224   `initial_amount` and `dynbuf.max_size() - dynbuf.size()`. Value passed
225   to each subsequent call is 1.5 the value passed in the preceding call. 225   to each subsequent call is 1.5 the value passed in the preceding call.
226   226  
227   227  
228   @par Await-returns 228   @par Await-returns
229   229  
230   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`. 230   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
231   231  
232   `n` represents the total number of bytes read, 232   `n` represents the total number of bytes read,
233   inclusive of the last partial read. 233   inclusive of the last partial read.
234   234  
235   235  
236   Contingencies: 236   Contingencies:
237   237  
238   @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some . 238   @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some .
239   239  
240   240  
241   @par Await-throws 241   @par Await-throws
242   242  
243   Whatever operations on @c dunbuf throw. 243   Whatever operations on @c dunbuf throw.
244   244  
245   (Note: types modeling @c DynamicBufferParam provided by Capy throw 245   (Note: types modeling @c DynamicBufferParam provided by Capy throw
246   @c std::bad_alloc from member function 246   @c std::bad_alloc from member function
247   @c prepare .) 247   @c prepare .)
248   248  
249   249  
250   @param source The source to read from. If the lifetime of `source` ends 250   @param source The source to read from. If the lifetime of `source` ends
251   before the coroutine finishes, the behavior is undefined. 251   before the coroutine finishes, the behavior is undefined.
252   252  
253   @param dynbuf The dynamic buffer to append data to. If the lifetime of the 253   @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, 254   buffer sequence represented by `dynbuf` ends before the coroutine finishes,
255   the behavior is undefined. 255   the behavior is undefined.
256   256  
257   @param initial_amount Hint for the value to be passed in the initial call to `dynbuf.prepare()` 257   @param initial_amount Hint for the value to be passed in the initial call to `dynbuf.prepare()`
258   (default 2048). 258   (default 2048).
259   259  
260   @par Remarks 260   @par Remarks
261   Supports _IoAwaitable cancellation_. 261   Supports _IoAwaitable cancellation_.
262   262  
263   @par Example 263   @par Example
264   264  
265   @code 265   @code
266   capy::task<std::string> read_body(capy::ReadSource auto& source) 266   capy::task<std::string> read_body(capy::ReadSource auto& source)
267   { 267   {
268   std::string body; 268   std::string body;
269   auto [ec, n] = co_await capy::read(source, capy::dynamic_buffer(body)); 269   auto [ec, n] = co_await capy::read(source, capy::dynamic_buffer(body));
270   if (ec) 270   if (ec)
271   throw std::system_error(ec); 271   throw std::system_error(ec);
272   return body; 272   return body;
273   } 273   }
274   @endcode 274   @endcode
275   275  
276   @see ReadSource, DynamicBufferParam 276   @see ReadSource, DynamicBufferParam
277   */ 277   */
278   template <typename S, typename DB> 278   template <typename S, typename DB>
279   requires ReadSource<S> && DynamicBufferParam<DB> 279   requires ReadSource<S> && DynamicBufferParam<DB>
280   auto 280   auto
HITCBC 281   54 read( 281   54 read(
282   S& source, 282   S& source,
283   DB&& dynbuf, 283   DB&& dynbuf,
284   std::size_t initial_amount = 2048) -> 284   std::size_t initial_amount = 2048) ->
285   io_task<std::size_t> 285   io_task<std::size_t>
286   { 286   {
287   std::size_t amount = initial_amount; 287   std::size_t amount = initial_amount;
288   std::size_t total_read = 0; 288   std::size_t total_read = 0;
289   for(;;) 289   for(;;)
290   { 290   {
291   auto mb = dynbuf.prepare(amount); 291   auto mb = dynbuf.prepare(amount);
292   auto const mb_size = buffer_size(mb); 292   auto const mb_size = buffer_size(mb);
293   auto [ec, n] = co_await source.read(mb); 293   auto [ec, n] = co_await source.read(mb);
294   dynbuf.commit(n); 294   dynbuf.commit(n);
295   total_read += n; 295   total_read += n;
296   if(ec == cond::eof) 296   if(ec == cond::eof)
297   co_return {{}, total_read}; 297   co_return {{}, total_read};
298   if(ec) 298   if(ec)
299   co_return {ec, total_read}; 299   co_return {ec, total_read};
300   if(n == mb_size) 300   if(n == mb_size)
301   amount = amount / 2 + amount; // 1.5x growth 301   amount = amount / 2 + amount; // 1.5x growth
302   } 302   }
HITCBC 303   108 } 303   108 }
304   304  
305   } // namespace capy 305   } // namespace capy
306   } // namespace boost 306   } // namespace boost
307   307  
308   #endif 308   #endif