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 are kept, up 32   `n` that a single `stream.read_some` happens to deliver are kept, up
33   to the capacity of `buffers`. No further awaiting is performed to 33   to the capacity of `buffers`. No further awaiting is performed to
34   fill the remainder. 34   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. The subsequent capacity of `buffers` is then 37   must be met or exceeded. The subsequent capacity of `buffers` is then
38   optional, and filling it should not block. 38   optional, and filling it 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   @return A task yielding `io_result<std::size_t>` whose second element 89   @return A task yielding `io_result<std::size_t>` whose second element
90   is the number of bytes read. 90   is the number of bytes read.
91   91  
92   @par Remarks 92   @par Remarks
93   Supports _IoAwaitable cancellation_. 93   Supports _IoAwaitable cancellation_.
94   94  
95   @par Example 95   @par Example
96   96  
97 - @code 97 + @par !example example
98 - capy::task<> fill_buffer(capy::ReadStream auto& stream)  
99 - {  
100 - std::vector<char> storage(4096); // generous capacity  
101 - // Require 16 header bytes; opportunistically take more.  
102 - auto [ec, n] = co_await capy::read_at_least(  
103 - stream, capy::make_buffer(storage), 16);  
104 - if(ec)  
105 - throw std::system_error(ec);  
106 - // at least 16 bytes are available; n may be larger  
107 - }  
108 - @endcode  
109   98  
110   99  
111   @see read, ReadStream, MutableBufferSequence 100   @see read, ReadStream, MutableBufferSequence
112   */ 101   */
113   template <typename S, typename MB> 102   template <typename S, typename MB>
114   requires ReadStream<S> && MutableBufferSequence<MB> 103   requires ReadStream<S> && MutableBufferSequence<MB>
115   auto 104   auto
HITCBC 116   34 read_at_least(S& stream, MB buffers, std::size_t n) -> 105   34 read_at_least(S& stream, MB buffers, std::size_t n) ->
117   io_task<std::size_t> 106   io_task<std::size_t>
118   { 107   {
119   consuming_buffers consuming(buffers); 108   consuming_buffers consuming(buffers);
120   std::size_t const total_size = buffer_size(buffers); 109   std::size_t const total_size = buffer_size(buffers);
121   110  
122   if(n > total_size) 111   if(n > total_size)
123   co_return {make_error_code(std::errc::invalid_argument), 0}; 112   co_return {make_error_code(std::errc::invalid_argument), 0};
124   113  
125   std::size_t total_read = 0; 114   std::size_t total_read = 0;
126   115  
127   while(total_read < n) 116   while(total_read < n)
128   { 117   {
129   auto [ec, m] = co_await stream.read_some(consuming.data()); 118   auto [ec, m] = co_await stream.read_some(consuming.data());
130   consuming.consume(m); 119   consuming.consume(m);
131   total_read += m; 120   total_read += m;
132   // A contingency that still satisfied the request is a success: 121   // A contingency that still satisfied the request is a success:
133   // report it only when fewer than n bytes were read. 122   // report it only when fewer than n bytes were read.
134   if(ec && total_read < n) 123   if(ec && total_read < n)
135   co_return {ec, total_read}; 124   co_return {ec, total_read};
136   } 125   }
137   126  
138   co_return {std::error_code(), total_read}; 127   co_return {std::error_code(), total_read};
HITCBC 139   68 } 128   68 }
140   129  
141   } // namespace capy 130   } // namespace capy
142   } // namespace boost 131   } // namespace boost
143   132  
144   #endif 133   #endif