1 -
//
 
2 -
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
 
3 -
//
 
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)
 
6 -
//
 
7 -
// Official repository: https://github.com/cppalliance/corosio
 
8 -
//
 
9 -

 
10 -
#ifndef BOOST_COROSIO_IO_BUFFER_PARAM_HPP
 
11 -
#define BOOST_COROSIO_IO_BUFFER_PARAM_HPP
 
12 -

 
13 -
#include <boost/corosio/detail/config.hpp>
 
14 -
#include <boost/capy/buffers.hpp>
 
15 -

 
16 -
#include <cstddef>
 
17 -

 
18 -
namespace boost::corosio {
 
19 -

 
20 -
/** A type-erased buffer sequence for I/O system call boundaries.
 
21 -

 
22 -
    This class enables I/O objects to accept any buffer sequence type
 
23 -
    across a virtual function boundary, while preserving the caller's
 
24 -
    typed buffer sequence at the call site. The implementation can
 
25 -
    then unroll the type-erased sequence into platform-native
 
26 -
    structures (e.g., `iovec` on POSIX, `WSABUF` on Windows) for the
 
27 -
    actual system call.
 
28 -

 
29 -
    @par Purpose
 
30 -

 
31 -
    When building coroutine-based I/O abstractions, a common pattern
 
32 -
    emerges: a templated awaitable captures the caller's buffer
 
33 -
    sequence, and at `await_suspend` time, must pass it across a
 
34 -
    virtual interface to the I/O implementation. This class solves
 
35 -
    the type-erasure problem at that boundary without heap allocation.
 
36 -

 
37 -
    @par Restricted Use Case
 
38 -

 
39 -
    This is NOT a general-purpose composable abstraction. It exists
 
40 -
    solely for the final step in a coroutine I/O call chain where:
 
41 -

 
42 -
    @li A templated awaitable captures the caller's buffer sequence
 
43 -
    @li The awaitable's `await_suspend` passes buffers across a
 
44 -
        virtual interface to an I/O object implementation
 
45 -
    @li The implementation immediately unrolls the buffers into
 
46 -
        platform-native structures for the system call
 
47 -

 
48 -
    @par Lifetime Model
 
49 -

 
50 -
    The safety of this class depends entirely on coroutine parameter
 
51 -
    lifetime extension. When a coroutine is suspended, parameters
 
52 -
    passed to the awaitable remain valid until the coroutine resumes
 
53 -
    or is destroyed. This class exploits that guarantee by holding
 
54 -
    only a pointer to the caller's buffer sequence.
 
55 -

 
56 -
    The referenced buffer sequence is valid ONLY while the calling
 
57 -
    coroutine remains suspended at the exact suspension point where
 
58 -
    `io_buffer_param` was created. Once the coroutine resumes,
 
59 -
    returns, or is destroyed, all referenced data becomes invalid.
 
60 -

 
61 -
    @par Const Buffer Handling
 
62 -

 
63 -
    This class accepts both `ConstBufferSequence` and
 
64 -
    `MutableBufferSequence` types. However, `copy_to` always produces
 
65 -
    `mutable_buffer` descriptors, casting away constness for const
 
66 -
    buffer sequences. This design matches platform I/O structures
 
67 -
    (`iovec`, `WSABUF`) which use non-const pointers regardless of
 
68 -
    the operation direction.
 
69 -

 
70 -
    @warning The caller is responsible for ensuring the type system
 
71 -
    is not violated. When the original buffer sequence was const
 
72 -
    (e.g., for a write operation), the implementation MUST NOT write
 
73 -
    to the buffers obtained from `copy_to`. The const-cast exists
 
74 -
    solely to provide a uniform interface for platform I/O calls.
 
75 -

 
76 -
    @code
 
77 -
    // For write operations (const buffers):
 
78 -
    void submit_write(io_buffer_param p)
 
79 -
    {
 
80 -
        capy::mutable_buffer bufs[8];
 
81 -
        auto n = p.copy_to(bufs, 8);
 
82 -
        // bufs[] may reference const data - DO NOT WRITE
 
83 -
        writev(fd, reinterpret_cast<iovec*>(bufs), n);  // OK: read-only
 
84 -
    }
 
85 -

 
86 -
    // For read operations (mutable buffers):
 
87 -
    void submit_read(io_buffer_param p)
 
88 -
    {
 
89 -
        capy::mutable_buffer bufs[8];
 
90 -
        auto n = p.copy_to(bufs, 8);
 
91 -
        // bufs[] references mutable data - safe to write
 
92 -
        readv(fd, reinterpret_cast<iovec*>(bufs), n);  // OK: writing
 
93 -
    }
 
94 -
    @endcode
 
95 -

 
96 -
    @par Correct Usage
 
97 -

 
98 -
    The implementation receiving `io_buffer_param` MUST:
 
99 -

 
100 -
    @li Call `copy_to` immediately upon receiving the parameter
 
101 -
    @li Use the unrolled buffer descriptors for the I/O operation
 
102 -
    @li Never store the `io_buffer_param` object itself
 
103 -
    @li Never store pointers obtained from `copy_to` beyond the
 
104 -
        immediate I/O operation
 
105 -

 
106 -
    @par Example: Correct Usage
 
107 -

 
108 -
    @code
 
109 -
    // Templated awaitable at the call site
 
110 -
    template<class Buffers>
 
111 -
    struct write_awaitable
 
112 -
    {
 
113 -
        Buffers bufs;
 
114 -
        io_stream* stream;
 
115 -

 
116 -
        bool await_ready() { return false; }
 
117 -

 
118 -
        void await_suspend(std::coroutine_handle<> h)
 
119 -
        {
 
120 -
            // CORRECT: Pass to virtual interface while suspended.
 
121 -
            // The buffer sequence 'bufs' remains valid because
 
122 -
            // coroutine parameters live until resumption.
 
123 -
            stream->async_write_some_impl(bufs, h);
 
124 -
        }
 
125 -

 
126 -
        io_result await_resume() { return stream->get_result(); }
 
127 -
    };
 
128 -

 
129 -
    // Virtual implementation - unrolls immediately
 
130 -
    void stream_impl::async_write_some_impl(
 
131 -
        io_buffer_param p,
 
132 -
        std::coroutine_handle<> h)
 
133 -
    {
 
134 -
        // CORRECT: Unroll immediately into platform structure
 
135 -
        iovec vecs[16];
 
136 -
        std::size_t n = p.copy_to(
 
137 -
            reinterpret_cast<capy::mutable_buffer*>(vecs), 16);
 
138 -

 
139 -
        // CORRECT: Use unrolled buffers for system call now
 
140 -
        submit_to_io_uring(vecs, n, h);
 
141 -

 
142 -
        // After this function returns, 'p' must not be used again.
 
143 -
        // The iovec array is safe because it contains copies of
 
144 -
        // the pointer/size pairs, not references to 'p'.
 
145 -
    }
 
146 -
    @endcode
 
147 -

 
148 -
    @par UNSAFE USAGE: Storing io_buffer_param
 
149 -

 
150 -
    @warning Never store `io_buffer_param` for later use.
 
151 -

 
152 -
    @code
 
153 -
    class broken_stream
 
154 -
    {
 
155 -
        io_buffer_param saved_param_;  // UNSAFE: member storage
 
156 -

 
157 -
        void async_write_impl(io_buffer_param p, ...)
 
158 -
        {
 
159 -
            saved_param_ = p;  // UNSAFE: storing for later
 
160 -
            schedule_write_later();
 
161 -
        }
 
162 -

 
163 -
        void do_write_later()
 
164 -
        {
 
165 -
            // UNSAFE: The calling coroutine may have resumed
 
166 -
            // or been destroyed. saved_param_ now references
 
167 -
            // invalid memory!
 
168 -
            capy::mutable_buffer bufs[8];
 
169 -
            saved_param_.copy_to(bufs, 8);  // UNDEFINED BEHAVIOR
 
170 -
        }
 
171 -
    };
 
172 -
    @endcode
 
173 -

 
174 -
    @par UNSAFE USAGE: Storing Unrolled Pointers
 
175 -

 
176 -
    @warning The pointers obtained from `copy_to` point into the
 
177 -
    caller's buffer sequence. They become invalid when the caller
 
178 -
    resumes.
 
179 -

 
180 -
    @code
 
181 -
    class broken_stream
 
182 -
    {
 
183 -
        capy::mutable_buffer saved_bufs_[8];  // UNSAFE
 
184 -
        std::size_t saved_count_;
 
185 -

 
186 -
        void async_write_impl(io_buffer_param p, ...)
 
187 -
        {
 
188 -
            // This copies pointer/size pairs into saved_bufs_
 
189 -
            saved_count_ = p.copy_to(saved_bufs_, 8);
 
190 -

 
191 -
            // UNSAFE: scheduling for later while storing the
 
192 -
            // buffer descriptors. The pointers in saved_bufs_
 
193 -
            // will dangle when the caller resumes!
 
194 -
            schedule_for_later();
 
195 -
        }
 
196 -

 
197 -
        void later()
 
198 -
        {
 
199 -
            // UNSAFE: saved_bufs_ contains dangling pointers
 
200 -
            for(std::size_t i = 0; i < saved_count_; ++i)
 
201 -
                write(fd_, saved_bufs_[i].data(), ...);  // UB
 
202 -
        }
 
203 -
    };
 
204 -
    @endcode
 
205 -

 
206 -
    @par UNSAFE USAGE: Using Outside a Coroutine
 
207 -

 
208 -
    @warning This class relies on coroutine lifetime semantics.
 
209 -
    Using it with callbacks or non-coroutine async patterns is
 
210 -
    undefined behavior.
 
211 -

 
212 -
    @code
 
213 -
    // UNSAFE: No coroutine lifetime guarantee
 
214 -
    void bad_callback_pattern(std::vector<char>& data)
 
215 -
    {
 
216 -
        capy::mutable_buffer buf(data.data(), data.size());
 
217 -

 
218 -
        // UNSAFE: In a callback model, 'buf' may go out of scope
 
219 -
        // before the callback fires. There is no coroutine
 
220 -
        // suspension to extend the lifetime.
 
221 -
        stream.async_write(buf, [](error_code ec) {
 
222 -
            // 'buf' is already destroyed!
 
223 -
        });
 
224 -
    }
 
225 -
    @endcode
 
226 -

 
227 -
    @par UNSAFE USAGE: Passing to Another Coroutine
 
228 -

 
229 -
    @warning Do not pass `io_buffer_param` to a different coroutine
 
230 -
    or spawn a new coroutine that captures it.
 
231 -

 
232 -
    @code
 
233 -
    void broken_impl(io_buffer_param p, std::coroutine_handle<> h)
 
234 -
    {
 
235 -
        // UNSAFE: Spawning a new coroutine that captures 'p'.
 
236 -
        // The original coroutine may resume before this new
 
237 -
        // coroutine uses 'p'.
 
238 -
        co_spawn([p]() -> task<void> {
 
239 -
            capy::mutable_buffer bufs[8];
 
240 -
            p.copy_to(bufs, 8);  // UNSAFE: original caller may
 
241 -
                                 // have resumed already!
 
242 -
            co_return;
 
243 -
        });
 
244 -
    }
 
245 -
    @endcode
 
246 -

 
247 -
    @par UNSAFE USAGE: Multiple Virtual Hops
 
248 -

 
249 -
    @warning Minimize indirection. Each virtual call that passes
 
250 -
    `io_buffer_param` without immediately unrolling it increases
 
251 -
    the risk of misuse.
 
252 -

 
253 -
    @code
 
254 -
    // Risky: multiple hops before unrolling
 
255 -
    void layer1(io_buffer_param p) {
 
256 -
        layer2(p);  // Still haven't unrolled...
 
257 -
    }
 
258 -
    void layer2(io_buffer_param p) {
 
259 -
        layer3(p);  // Still haven't unrolled...
 
260 -
    }
 
261 -
    void layer3(io_buffer_param p) {
 
262 -
        // Finally unrolling, but the chain is fragile.
 
263 -
        // Any intermediate layer storing 'p' breaks everything.
 
264 -
    }
 
265 -
    @endcode
 
266 -

 
267 -
    @par UNSAFE USAGE: Fire-and-Forget Operations
 
268 -

 
269 -
    @warning Do not use with detached or fire-and-forget async
 
270 -
    operations where there is no guarantee the caller remains
 
271 -
    suspended.
 
272 -

 
273 -
    @code
 
274 -
    task<void> caller()
 
275 -
    {
 
276 -
        char buf[1024];
 
277 -
        // UNSAFE: If async_write is fire-and-forget (doesn't
 
278 -
        // actually suspend the caller), 'buf' may be destroyed
 
279 -
        // before the I/O completes.
 
280 -
        stream.async_write_detached(capy::mutable_buffer(buf, 1024));
 
281 -
        // Returns immediately - 'buf' goes out of scope!
 
282 -
    }
 
283 -
    @endcode
 
284 -

 
285 -
    @par Passing Convention
 
286 -

 
287 -
    Pass by value. The class contains only two pointers (16 bytes
 
288 -
    on 64-bit systems), making copies trivial and clearly
 
289 -
    communicating the lightweight, transient nature of this type.
 
290 -

 
291 -
    @code
 
292 -
    // Preferred: pass by value
 
293 -
    void process(io_buffer_param buffers);
 
294 -

 
295 -
    // Also acceptable: pass by const reference
 
296 -
    void process(io_buffer_param const& buffers);
 
297 -
    @endcode
 
298 -

 
299 -
    @see capy::ConstBufferSequence, capy::MutableBufferSequence
 
300 -
*/
 
301 -
class io_buffer_param
 
302 -
{
 
303 -
public:
 
304 -
    /** Construct from a const buffer sequence.
 
305 -

 
306 -
        @param bs The buffer sequence to adapt.
 
307 -
    */
 
308 -
    template<capy::ConstBufferSequence BS>
 
309 -
    io_buffer_param(BS const& bs) noexcept : bs_(&bs)
 
310 -
                                           , fn_(&copy_impl<BS>)
 
311 -
    {
 
312 -
    }
 
313 -

 
314 -
    /** Fill an array with buffers from the sequence.
 
315 -

 
316 -
        Copies buffer descriptors from the sequence into the
 
317 -
        destination array, skipping any zero-size buffers.
 
318 -
        This ensures the output contains only buffers with
 
319 -
        actual data, suitable for direct use with system calls.
 
320 -

 
321 -
        @param dest Pointer to array of mutable buffer descriptors.
 
322 -
        @param n Maximum number of buffers to copy.
 
323 -

 
324 -
        @return The number of non-zero buffers copied.
 
325 -
    */
 
326 -
    std::size_t
 
327 -
    copy_to(capy::mutable_buffer* dest, std::size_t n) const noexcept
 
328 -
    {
 
329 -
        return fn_(bs_, dest, n);
 
330 -
    }
 
331 -

 
332 -
private:
 
333 -
    template<capy::ConstBufferSequence BS>
 
334 -
    static std::size_t
 
335 -
    copy_impl(void const* p, capy::mutable_buffer* dest, std::size_t n)
 
336 -
    {
 
337 -
        auto const& bs    = *static_cast<BS const*>(p);
 
338 -
        auto it           = capy::begin(bs);
 
339 -
        auto const end_it = capy::end(bs);
 
340 -

 
341 -
        std::size_t i = 0;
 
342 -
        if constexpr (capy::MutableBufferSequence<BS>)
 
343 -
        {
 
344 -
            for (; it != end_it && i < n; ++it)
 
345 -
            {
 
346 -
                capy::mutable_buffer buf(*it);
 
347 -
                if (buf.size() == 0)
 
348 -
                    continue;
 
349 -
                dest[i++] = buf;
 
350 -
            }
 
351 -
        }
 
352 -
        else
 
353 -
        {
 
354 -
            for (; it != end_it && i < n; ++it)
 
355 -
            {
 
356 -
                capy::const_buffer buf(*it);
 
357 -
                if (buf.size() == 0)
 
358 -
                    continue;
 
359 -
                dest[i++] = capy::mutable_buffer(
 
360 -
                    const_cast<char*>(static_cast<char const*>(buf.data())),
 
361 -
                    buf.size());
 
362 -
            }
 
363 -
        }
 
364 -
        return i;
 
365 -
    }
 
366 -

 
367 -
    using fn_t =
 
368 -
        std::size_t (*)(void const*, capy::mutable_buffer*, std::size_t);
 
369 -

 
370 -
    void const* bs_;
 
371 -
    fn_t fn_;
 
372 -
};
 
373 -

 
374 -
} // namespace boost::corosio
 
375 -

 
376 -
#endif