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_DETAIL_BUFFER_PARAM_HPP
 
11 +
#define BOOST_COROSIO_DETAIL_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 +
    `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(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(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 `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 `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 +
        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 buffer_param
 
149 +

 
150 +
    @warning Never store `buffer_param` for later use.
 
151 +

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

 
157 +
        void async_write_impl(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(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 `buffer_param` to a different coroutine
 
230 +
    or spawn a new coroutine that captures it.
 
231 +

 
232 +
    @code
 
233 +
    void broken_impl(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 +
    `buffer_param` without immediately unrolling it increases
 
251 +
    the risk of misuse.
 
252 +

 
253 +
    @code
 
254 +
    // Risky: multiple hops before unrolling
 
255 +
    void layer1(buffer_param p) {
 
256 +
        layer2(p);  // Still haven't unrolled...
 
257 +
    }
 
258 +
    void layer2(buffer_param p) {
 
259 +
        layer3(p);  // Still haven't unrolled...
 
260 +
    }
 
261 +
    void layer3(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(buffer_param buffers);
 
294 +

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

 
299 +
    @see capy::ConstBufferSequence, capy::MutableBufferSequence
 
300 +
*/
 
301 +
class 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 +
    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