100.00% Lines (2/2) 100.00% Functions (1/1)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
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_AT_LEAST_HPP 10   #ifndef BOOST_CAPY_READ_AT_LEAST_HPP
11   #define BOOST_CAPY_READ_AT_LEAST_HPP 11   #define BOOST_CAPY_READ_AT_LEAST_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/consuming_buffers.hpp> 17   #include <boost/capy/buffers/consuming_buffers.hpp>
18   #include <boost/capy/concept/read_stream.hpp> 18   #include <boost/capy/concept/read_stream.hpp>
19   19  
20   #include <cstddef> 20   #include <cstddef>
21   #include <system_error> 21   #include <system_error>
22   22  
23   namespace boost { 23   namespace boost {
24   namespace capy { 24   namespace capy {
25   25  
26   /** Read at least a minimum number of bytes from a stream. 26   /** Read at least a minimum number of bytes from a stream.
27   27  
28   This is a straightforward extension of @ref read. While @ref read 28   This is a straightforward extension of @ref read. While @ref read
29   transfers exactly `buffer_size(buffers)` bytes, `read_at_least` 29   transfers exactly `buffer_size(buffers)` bytes, `read_at_least`
30   transfers at least `n` bytes: the loop stops as soon as `n` bytes 30   transfers at least `n` bytes: the loop stops as soon as `n` bytes
31   have been read, even if `buffers` is not yet full. Any bytes beyond 31   have been read, even if `buffers` is not yet full. Any bytes beyond
32   `n` that a single `stream.read_some` happens to deliver (up to the 32   `n` that a single `stream.read_some` happens to deliver (up to the
33   capacity of `buffers`) are kept, but no further awaiting is performed 33   capacity of `buffers`) are kept, but no further awaiting is performed
34   to fill the remainder. 34   to fill the remainder.
35   35  
36   This is useful when a caller has a required amount of data `n` that 36   This is useful when a caller has a required amount of data `n` that
37   must be met or exceeded, while the subsequent capacity of `buffers` 37   must be met or exceeded, while the subsequent capacity of `buffers`
38   is optional and should not block. 38   is optional and should not block.
39   39  
40   @par Await-effects 40   @par Await-effects
41   41  
42   If `n > buffer_size(buffers)` the request is impossible to satisfy 42   If `n > buffer_size(buffers)` the request is impossible to satisfy
43   and the operation fails immediately with 43   and the operation fails immediately with
44   `{std::errc::invalid_argument, 0}` without awaiting `stream.read_some`. 44   `{std::errc::invalid_argument, 0}` without awaiting `stream.read_some`.
45   45  
46   Otherwise reads data from `stream` via awaiting `stream.read_some` 46   Otherwise reads data from `stream` via awaiting `stream.read_some`
47   repeatedly until: 47   repeatedly until:
48   48  
49   @li either at least `n` bytes have been read, 49   @li either at least `n` bytes have been read,
50   @li or a contingency occurs on `stream.read_some`. 50   @li or a contingency occurs on `stream.read_some`.
51   51  
52   If `n == 0` then no awaiting `stream.read_some` is performed. This is 52   If `n == 0` then no awaiting `stream.read_some` is performed. This is
53   not a contingency. 53   not a contingency.
54   54  
55   @par Await-returns 55   @par Await-returns
56   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`. 56   An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
57   57  
58   Upon a contingency, the count represents the number of bytes read so 58   Upon a contingency, the count represents the number of bytes read so
59   far, inclusive of the last partial read. 59   far, inclusive of the last partial read.
60   60  
61   Contingencies: 61   Contingencies:
62   62  
63   @li The first contingency reported from awaiting @c stream.read_some 63   @li The first contingency reported from awaiting @c stream.read_some
64   while fewer than `n` bytes have been read. A contingency that 64   while fewer than `n` bytes have been read. A contingency that
65   accompanies the read which reaches `n` is not reported: a 65   accompanies the read which reaches `n` is not reported: a
66   satisfied request is a success. 66   satisfied request is a success.
67   67  
68   Notable conditions: 68   Notable conditions:
69   69  
70   @li @c std::errc::invalid_argument — `n` exceeds `buffer_size(buffers)`, 70   @li @c std::errc::invalid_argument — `n` exceeds `buffer_size(buffers)`,
71   @li @c cond::canceled — Operation was cancelled, 71   @li @c cond::canceled — Operation was cancelled,
72   @li @c cond::eof — Stream reached end before `n` bytes were read. 72   @li @c cond::eof — Stream reached end before `n` bytes were read.
73   73  
74   @par Await-postcondition 74   @par Await-postcondition
75   On success the returned count is greater than or equal to `n` and 75   On success the returned count is greater than or equal to `n` and
76   less than or equal to `buffer_size(buffers)`, and `ec` is success; 76   less than or equal to `buffer_size(buffers)`, and `ec` is success;
77   otherwise `ec` is set. 77   otherwise `ec` is set.
78   78  
79   @param stream The stream to read from. If the lifetime of `stream` ends 79   @param stream The stream to read from. If the lifetime of `stream` ends
80   before the coroutine finishes, the behavior is undefined. 80   before the coroutine finishes, the behavior is undefined.
81   81  
82   @param buffers The buffer sequence to read into. If the lifetime of the 82   @param buffers The buffer sequence to read into. If the lifetime of the
83   buffer sequence represented by `buffers` ends before the coroutine 83   buffer sequence represented by `buffers` ends before the coroutine
84   finishes, the behavior is undefined. 84   finishes, the behavior is undefined.
85   85  
86   @param n The minimum number of bytes to read. Must not exceed 86   @param n The minimum number of bytes to read. Must not exceed
87   `buffer_size(buffers)`. 87   `buffer_size(buffers)`.
88   88  
89   @par Remarks 89   @par Remarks
90   Supports _IoAwaitable cancellation_. 90   Supports _IoAwaitable cancellation_.
91   91  
92   @par Example 92   @par Example
93   93  
94   @code 94   @code
95   capy::task<> fill_buffer(capy::ReadStream auto& stream) 95   capy::task<> fill_buffer(capy::ReadStream auto& stream)
96   { 96   {
97   std::vector<char> storage(4096); // generous capacity 97   std::vector<char> storage(4096); // generous capacity
98   // Require 16 header bytes; opportunistically take more. 98   // Require 16 header bytes; opportunistically take more.
99   auto [ec, n] = co_await capy::read_at_least( 99   auto [ec, n] = co_await capy::read_at_least(
100   stream, capy::make_buffer(storage), 16); 100   stream, capy::make_buffer(storage), 16);
101   if(ec) 101   if(ec)
102   throw std::system_error(ec); 102   throw std::system_error(ec);
103   103  
104   // at least 16 bytes are available; n may be larger 104   // at least 16 bytes are available; n may be larger
105   } 105   }
106   @endcode 106   @endcode
107   107  
108   @see read, ReadStream, MutableBufferSequence 108   @see read, ReadStream, MutableBufferSequence
109   */ 109   */
110   template <typename S, typename MB> 110   template <typename S, typename MB>
111   requires ReadStream<S> && MutableBufferSequence<MB> 111   requires ReadStream<S> && MutableBufferSequence<MB>
112   auto 112   auto
HITCBC 113   34 read_at_least(S& stream, MB buffers, std::size_t n) -> 113   34 read_at_least(S& stream, MB buffers, std::size_t n) ->
114   io_task<std::size_t> 114   io_task<std::size_t>
115   { 115   {
116   consuming_buffers consuming(buffers); 116   consuming_buffers consuming(buffers);
117   std::size_t const total_size = buffer_size(buffers); 117   std::size_t const total_size = buffer_size(buffers);
118   118  
119   if(n > total_size) 119   if(n > total_size)
120   co_return {make_error_code(std::errc::invalid_argument), 0}; 120   co_return {make_error_code(std::errc::invalid_argument), 0};
121   121  
122   std::size_t total_read = 0; 122   std::size_t total_read = 0;
123   123  
124   while(total_read < n) 124   while(total_read < n)
125   { 125   {
126   auto [ec, m] = co_await stream.read_some(consuming.data()); 126   auto [ec, m] = co_await stream.read_some(consuming.data());
127   consuming.consume(m); 127   consuming.consume(m);
128   total_read += m; 128   total_read += m;
129   // A contingency that still satisfied the request is a success: 129   // A contingency that still satisfied the request is a success:
130   // report it only when fewer than n bytes were read. 130   // report it only when fewer than n bytes were read.
131   if(ec && total_read < n) 131   if(ec && total_read < n)
132   co_return {ec, total_read}; 132   co_return {ec, total_read};
133   } 133   }
134   134  
135   co_return {{}, total_read}; 135   co_return {{}, total_read};
HITCBC 136   68 } 136   68 }
137   137  
138   } // namespace capy 138   } // namespace capy
139   } // namespace boost 139   } // namespace boost
140   140  
141   #endif 141   #endif