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