1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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_result.hpp>
15  
#include <boost/capy/io_result.hpp>
16  
#include <boost/capy/task.hpp>
16  
#include <boost/capy/task.hpp>
17  
#include <boost/capy/buffers.hpp>
17  
#include <boost/capy/buffers.hpp>
18  
#include <boost/capy/buffers/consuming_buffers.hpp>
18  
#include <boost/capy/buffers/consuming_buffers.hpp>
19  
#include <boost/capy/concept/dynamic_buffer.hpp>
19  
#include <boost/capy/concept/dynamic_buffer.hpp>
20  
#include <boost/capy/concept/read_source.hpp>
20  
#include <boost/capy/concept/read_source.hpp>
21  
#include <boost/capy/concept/read_stream.hpp>
21  
#include <boost/capy/concept/read_stream.hpp>
22  
#include <system_error>
22  
#include <system_error>
23  

23  

24  
#include <cstddef>
24  
#include <cstddef>
25  

25  

26  
namespace boost {
26  
namespace boost {
27  
namespace capy {
27  
namespace capy {
28  

28  

29  
/** Asynchronously read until the buffer sequence is full.
29  
/** Asynchronously read until the buffer sequence is full.
30  

30  

31  
    Reads data from the stream by calling `read_some` repeatedly
31  
    Reads data from the stream by calling `read_some` repeatedly
32  
    until the entire buffer sequence is filled or an error occurs.
32  
    until the entire buffer sequence is filled or an error occurs.
33  

33  

34  
    @li The operation completes when:
34  
    @li The operation completes when:
35  
    @li The buffer sequence is completely filled
35  
    @li The buffer sequence is completely filled
36  
    @li An error occurs (including `cond::eof`)
36  
    @li An error occurs (including `cond::eof`)
37  
    @li The operation is cancelled
37  
    @li The operation is cancelled
38  

38  

39  
    @par Cancellation
39  
    @par Cancellation
40  
    Supports cancellation via `stop_token` propagated through the
40  
    Supports cancellation via `stop_token` propagated through the
41  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
41  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
42  

42  

43  
    @param stream The stream to read from. The caller retains ownership.
43  
    @param stream The stream to read from. The caller retains ownership.
44  
    @param buffers The buffer sequence to fill. The caller retains
44  
    @param buffers The buffer sequence to fill. The caller retains
45  
        ownership and must ensure validity until the operation completes.
45  
        ownership and must ensure validity until the operation completes.
46  

46  

47  
    @return An awaitable yielding `(error_code, std::size_t)`.
47  
    @return An awaitable yielding `(error_code, std::size_t)`.
48  
        On success, `n` equals `buffer_size(buffers)`. On error,
48  
        On success, `n` equals `buffer_size(buffers)`. On error,
49  
        `n` is the number of bytes read before the error. Compare
49  
        `n` is the number of bytes read before the error. Compare
50  
        error codes to conditions:
50  
        error codes to conditions:
51  
        @li `cond::eof` - Stream reached end before buffer was filled
51  
        @li `cond::eof` - Stream reached end before buffer was filled
52  
        @li `cond::canceled` - Operation was cancelled
52  
        @li `cond::canceled` - Operation was cancelled
53  

53  

54  
    @par Example
54  
    @par Example
55  

55  

56  
    @code
56  
    @code
57  
    task<> read_message( ReadStream auto& stream )
57  
    task<> read_message( ReadStream auto& stream )
58  
    {
58  
    {
59  
        char header[16];
59  
        char header[16];
60  
        auto [ec, n] = co_await read( stream, mutable_buffer( header ) );
60  
        auto [ec, n] = co_await read( stream, mutable_buffer( header ) );
61  
        if( ec == cond::eof )
61  
        if( ec == cond::eof )
62  
            co_return;  // Connection closed
62  
            co_return;  // Connection closed
63  
        if( ec.failed() )
63  
        if( ec.failed() )
64  
            detail::throw_system_error( ec );
64  
            detail::throw_system_error( ec );
65  
        // header contains exactly 16 bytes
65  
        // header contains exactly 16 bytes
66  
    }
66  
    }
67  
    @endcode
67  
    @endcode
68  

68  

69  
    @see read_some, ReadStream, MutableBufferSequence
69  
    @see read_some, ReadStream, MutableBufferSequence
70  
*/
70  
*/
71  
auto
71  
auto
72  
read(
72  
read(
73  
    ReadStream auto& stream,
73  
    ReadStream auto& stream,
74  
    MutableBufferSequence auto const& buffers) ->
74  
    MutableBufferSequence auto const& buffers) ->
75  
        task<io_result<std::size_t>>
75  
        task<io_result<std::size_t>>
76  
{
76  
{
77  
    consuming_buffers consuming(buffers);
77  
    consuming_buffers consuming(buffers);
78  
    std::size_t const total_size = buffer_size(buffers);
78  
    std::size_t const total_size = buffer_size(buffers);
79  
    std::size_t total_read = 0;
79  
    std::size_t total_read = 0;
80  

80  

81  
    while(total_read < total_size)
81  
    while(total_read < total_size)
82  
    {
82  
    {
83  
        auto [ec, n] = co_await stream.read_some(consuming);
83  
        auto [ec, n] = co_await stream.read_some(consuming);
84  
        if(ec)
84  
        if(ec)
85  
            co_return {ec, total_read};
85  
            co_return {ec, total_read};
86  
        consuming.consume(n);
86  
        consuming.consume(n);
87  
        total_read += n;
87  
        total_read += n;
88  
    }
88  
    }
89  

89  

90  
    co_return {{}, total_read};
90  
    co_return {{}, total_read};
91  
}
91  
}
92  

92  

93  
/** Asynchronously read all data from a stream into a dynamic buffer.
93  
/** Asynchronously read all data from a stream into a dynamic buffer.
94  

94  

95  
    Reads data by calling `read_some` repeatedly until EOF is reached
95  
    Reads data by calling `read_some` repeatedly until EOF is reached
96  
    or an error occurs. Data is appended using prepare/commit semantics.
96  
    or an error occurs. Data is appended using prepare/commit semantics.
97  
    The buffer grows with 1.5x factor when filled.
97  
    The buffer grows with 1.5x factor when filled.
98  

98  

99  
    @li The operation completes when:
99  
    @li The operation completes when:
100  
    @li End-of-stream is reached (`cond::eof`)
100  
    @li End-of-stream is reached (`cond::eof`)
101  
    @li An error occurs
101  
    @li An error occurs
102  
    @li The operation is cancelled
102  
    @li The operation is cancelled
103  

103  

104  
    @par Cancellation
104  
    @par Cancellation
105  
    Supports cancellation via `stop_token` propagated through the
105  
    Supports cancellation via `stop_token` propagated through the
106  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
106  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
107  

107  

108  
    @param stream The stream to read from. The caller retains ownership.
108  
    @param stream The stream to read from. The caller retains ownership.
109  
    @param buffers The dynamic buffer to append data to. Must remain
109  
    @param buffers The dynamic buffer to append data to. Must remain
110  
        valid until the operation completes.
110  
        valid until the operation completes.
111  
    @param initial_amount Initial bytes to prepare (default 2048).
111  
    @param initial_amount Initial bytes to prepare (default 2048).
112  

112  

113  
    @return An awaitable yielding `(error_code, std::size_t)`.
113  
    @return An awaitable yielding `(error_code, std::size_t)`.
114  
        On success (EOF), `ec` is clear and `n` is total bytes read.
114  
        On success (EOF), `ec` is clear and `n` is total bytes read.
115  
        On error, `n` is bytes read before the error. Compare error
115  
        On error, `n` is bytes read before the error. Compare error
116  
        codes to conditions:
116  
        codes to conditions:
117  
        @li `cond::canceled` - Operation was cancelled
117  
        @li `cond::canceled` - Operation was cancelled
118  

118  

119  
    @par Example
119  
    @par Example
120  

120  

121  
    @code
121  
    @code
122  
    task<std::string> read_body( ReadStream auto& stream )
122  
    task<std::string> read_body( ReadStream auto& stream )
123  
    {
123  
    {
124  
        std::string body;
124  
        std::string body;
125  
        auto [ec, n] = co_await read( stream, string_dynamic_buffer( &body ) );
125  
        auto [ec, n] = co_await read( stream, string_dynamic_buffer( &body ) );
126  
        if( ec.failed() )
126  
        if( ec.failed() )
127  
            detail::throw_system_error( ec );
127  
            detail::throw_system_error( ec );
128  
        return body;
128  
        return body;
129  
    }
129  
    }
130  
    @endcode
130  
    @endcode
131  

131  

132  
    @see read_some, ReadStream, DynamicBufferParam
132  
    @see read_some, ReadStream, DynamicBufferParam
133  
*/
133  
*/
134  
auto
134  
auto
135  
read(
135  
read(
136  
    ReadStream auto& stream,
136  
    ReadStream auto& stream,
137  
    DynamicBufferParam auto&& buffers,
137  
    DynamicBufferParam auto&& buffers,
138  
    std::size_t initial_amount = 2048) ->
138  
    std::size_t initial_amount = 2048) ->
139  
        task<io_result<std::size_t>>
139  
        task<io_result<std::size_t>>
140  
{
140  
{
141  
    std::size_t amount = initial_amount;
141  
    std::size_t amount = initial_amount;
142  
    std::size_t total_read = 0;
142  
    std::size_t total_read = 0;
143  
    for(;;)
143  
    for(;;)
144  
    {
144  
    {
145  
        auto mb = buffers.prepare(amount);
145  
        auto mb = buffers.prepare(amount);
146  
        auto const mb_size = buffer_size(mb);
146  
        auto const mb_size = buffer_size(mb);
147  
        auto [ec, n] = co_await stream.read_some(mb);
147  
        auto [ec, n] = co_await stream.read_some(mb);
148  
        buffers.commit(n);
148  
        buffers.commit(n);
149  
        total_read += n;
149  
        total_read += n;
150  
        if(ec == cond::eof)
150  
        if(ec == cond::eof)
151  
            co_return {{}, total_read};
151  
            co_return {{}, total_read};
152  
        if(ec)
152  
        if(ec)
153  
            co_return {ec, total_read};
153  
            co_return {ec, total_read};
154  
        if(n == mb_size)
154  
        if(n == mb_size)
155  
            amount = amount / 2 + amount;
155  
            amount = amount / 2 + amount;
156  
    }
156  
    }
157  
}
157  
}
158  

158  

159  
/** Asynchronously read all data from a source into a dynamic buffer.
159  
/** Asynchronously read all data from a source into a dynamic buffer.
160  

160  

161  
    Reads data by calling `source.read` repeatedly until EOF is reached
161  
    Reads data by calling `source.read` repeatedly until EOF is reached
162  
    or an error occurs. Data is appended using prepare/commit semantics.
162  
    or an error occurs. Data is appended using prepare/commit semantics.
163  
    The buffer grows with 1.5x factor when filled.
163  
    The buffer grows with 1.5x factor when filled.
164  

164  

165  
    @li The operation completes when:
165  
    @li The operation completes when:
166  
    @li End-of-stream is reached (`cond::eof`)
166  
    @li End-of-stream is reached (`cond::eof`)
167  
    @li An error occurs
167  
    @li An error occurs
168  
    @li The operation is cancelled
168  
    @li The operation is cancelled
169  

169  

170  
    @par Cancellation
170  
    @par Cancellation
171  
    Supports cancellation via `stop_token` propagated through the
171  
    Supports cancellation via `stop_token` propagated through the
172  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
172  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
173  

173  

174  
    @param source The source to read from. The caller retains ownership.
174  
    @param source The source to read from. The caller retains ownership.
175  
    @param buffers The dynamic buffer to append data to. Must remain
175  
    @param buffers The dynamic buffer to append data to. Must remain
176  
        valid until the operation completes.
176  
        valid until the operation completes.
177  
    @param initial_amount Initial bytes to prepare (default 2048).
177  
    @param initial_amount Initial bytes to prepare (default 2048).
178  

178  

179  
    @return An awaitable yielding `(error_code, std::size_t)`.
179  
    @return An awaitable yielding `(error_code, std::size_t)`.
180  
        On success (EOF), `ec` is clear and `n` is total bytes read.
180  
        On success (EOF), `ec` is clear and `n` is total bytes read.
181  
        On error, `n` is bytes read before the error. Compare error
181  
        On error, `n` is bytes read before the error. Compare error
182  
        codes to conditions:
182  
        codes to conditions:
183  
        @li `cond::canceled` - Operation was cancelled
183  
        @li `cond::canceled` - Operation was cancelled
184  

184  

185  
    @par Example
185  
    @par Example
186  

186  

187  
    @code
187  
    @code
188  
    task<std::string> read_body( ReadSource auto& source )
188  
    task<std::string> read_body( ReadSource auto& source )
189  
    {
189  
    {
190  
        std::string body;
190  
        std::string body;
191  
        auto [ec, n] = co_await read( source, string_dynamic_buffer( &body ) );
191  
        auto [ec, n] = co_await read( source, string_dynamic_buffer( &body ) );
192  
        if( ec.failed() )
192  
        if( ec.failed() )
193  
            detail::throw_system_error( ec );
193  
            detail::throw_system_error( ec );
194  
        return body;
194  
        return body;
195  
    }
195  
    }
196  
    @endcode
196  
    @endcode
197  

197  

198  
    @see ReadSource, DynamicBufferParam
198  
    @see ReadSource, DynamicBufferParam
199  
*/
199  
*/
200  
auto
200  
auto
201  
read(
201  
read(
202  
    ReadSource auto& source,
202  
    ReadSource auto& source,
203  
    DynamicBufferParam auto&& buffers,
203  
    DynamicBufferParam auto&& buffers,
204  
    std::size_t initial_amount = 2048) ->
204  
    std::size_t initial_amount = 2048) ->
205  
        task<io_result<std::size_t>>
205  
        task<io_result<std::size_t>>
206  
{
206  
{
207  
    std::size_t amount = initial_amount;
207  
    std::size_t amount = initial_amount;
208  
    std::size_t total_read = 0;
208  
    std::size_t total_read = 0;
209  
    for(;;)
209  
    for(;;)
210  
    {
210  
    {
211  
        auto mb = buffers.prepare(amount);
211  
        auto mb = buffers.prepare(amount);
212  
        auto const mb_size = buffer_size(mb);
212  
        auto const mb_size = buffer_size(mb);
213  
        auto [ec, n] = co_await source.read(mb);
213  
        auto [ec, n] = co_await source.read(mb);
214  
        buffers.commit(n);
214  
        buffers.commit(n);
215  
        total_read += n;
215  
        total_read += n;
216  
        if(ec == cond::eof)
216  
        if(ec == cond::eof)
217  
            co_return {{}, total_read};
217  
            co_return {{}, total_read};
218  
        if(ec)
218  
        if(ec)
219  
            co_return {ec, total_read};
219  
            co_return {ec, total_read};
220  
        if(n == mb_size)
220  
        if(n == mb_size)
221  
            amount = amount / 2 + amount; // 1.5x growth
221  
            amount = amount / 2 + amount; // 1.5x growth
222  
    }
222  
    }
223  
}
223  
}
224  

224  

225  
} // namespace capy
225  
} // namespace capy
226  
} // namespace boost
226  
} // namespace boost
227  

227  

228  
#endif
228  
#endif