Line data Source code
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/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_BUFFERS_BUFFER_PARAM_HPP
11 : #define BOOST_CAPY_BUFFERS_BUFFER_PARAM_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/buffers.hpp>
15 : #include <boost/assert.hpp>
16 :
17 : #include <array>
18 : #include <span>
19 : #include <type_traits>
20 :
21 : namespace boost {
22 : namespace capy {
23 :
24 : /** A type-erased const buffer sequence for function call boundaries.
25 :
26 : This class enables functions to accept any const buffer sequence
27 : type across a non-template interface, while the caller retains
28 : ownership of the underlying buffer data. The class provides
29 : incremental access to the buffer sequence through a sliding
30 : window, allowing efficient processing of arbitrarily large
31 : sequences without copying all buffer descriptors upfront.
32 :
33 : @par Purpose
34 :
35 : When building I/O abstractions, a common need arises: accepting
36 : buffer sequences of any type through a non-template function
37 : signature. This class solves the type-erasure problem by storing
38 : function pointers that operate on the concrete buffer sequence
39 : type, while presenting a uniform interface to the callee.
40 :
41 : @par Lifetime Model
42 :
43 : @warning This class has DANGEROUS lifetime semantics. The safety
44 : of this class depends entirely on the lifetime of the object
45 : returned by @ref make_const_buffer_param. When that object is
46 : destroyed, this class becomes invalid and any use is undefined
47 : behavior.
48 :
49 : The intended usage pattern exploits C++ temporary lifetime
50 : extension: when passing the result of @ref make_const_buffer_param
51 : directly to a function taking `const_buffer_param` by value,
52 : slicing occurs and the temporary implementation object lives
53 : until the function returns. This is safe:
54 :
55 : @code
56 : void write_some(const_buffer_param buffers);
57 :
58 : // SAFE: temporary lives through the function call
59 : write_some(make_const_buffer_param(my_buffers));
60 : @endcode
61 :
62 : @par Correct Usage
63 :
64 : The implementation receiving `const_buffer_param` MUST:
65 :
66 : @li Use `data()` and `consume()` immediately within the call
67 : @li Never store the `const_buffer_param` object
68 : @li Never return the `const_buffer_param` from the function
69 : @li Complete all buffer processing before returning
70 :
71 : @par Example: Correct Usage
72 :
73 : @code
74 : // Function accepts type-erased buffer parameter
75 : std::size_t write_all(socket& sock, const_buffer_param buffers)
76 : {
77 : std::size_t total = 0;
78 : while(true)
79 : {
80 : auto bufs = buffers.data();
81 : if(bufs.empty())
82 : break;
83 : auto n = sock.write_some(bufs);
84 : total += n;
85 : buffers.consume(n);
86 : }
87 : return total;
88 : }
89 :
90 : // Caller creates temporary that lives through the call
91 : std::vector<const_buffer> chunks = get_chunks();
92 : auto n = write_all(sock, make_const_buffer_param(chunks));
93 : @endcode
94 :
95 : @par UNSAFE USAGE: Storing const_buffer_param
96 :
97 : @warning Never store `const_buffer_param` for later use.
98 :
99 : @code
100 : class broken_writer
101 : {
102 : const_buffer_param saved_; // UNSAFE: member storage
103 :
104 : void start_write(const_buffer_param buffers)
105 : {
106 : saved_ = buffers; // UNSAFE: storing for later
107 : schedule_write();
108 : }
109 :
110 : void do_write()
111 : {
112 : // UNSAFE: The temporary from make_const_buffer_param
113 : // was destroyed when start_write returned!
114 : auto bufs = saved_.data(); // UNDEFINED BEHAVIOR
115 : }
116 : };
117 : @endcode
118 :
119 : @par UNSAFE USAGE: Returning const_buffer_param
120 :
121 : @warning Never return `const_buffer_param` from a function.
122 :
123 : @code
124 : // UNSAFE: Returning causes the temporary to be destroyed
125 : const_buffer_param get_buffers()
126 : {
127 : std::vector<const_buffer> bufs = prepare_buffers();
128 : return make_const_buffer_param(bufs); // UNSAFE!
129 : // The temporary AND the vector are destroyed here
130 : }
131 :
132 : void caller()
133 : {
134 : auto bp = get_buffers(); // Receives invalid object
135 : bp.data(); // UNDEFINED BEHAVIOR
136 : }
137 : @endcode
138 :
139 : @par UNSAFE USAGE: Storing in a Container
140 :
141 : @warning Never store `const_buffer_param` in containers or
142 : data structures.
143 :
144 : @code
145 : std::vector<const_buffer_param> pending; // UNSAFE
146 :
147 : void queue_write(const_buffer_param buffers)
148 : {
149 : pending.push_back(buffers); // UNSAFE: storing
150 : }
151 :
152 : void process_pending()
153 : {
154 : for(auto& bp : pending)
155 : bp.data(); // UNDEFINED BEHAVIOR
156 : }
157 : @endcode
158 :
159 : @par UNSAFE USAGE: Assigning to a Variable
160 :
161 : @warning Assigning to a named variable extends the wrong
162 : lifetime.
163 :
164 : @code
165 : void broken()
166 : {
167 : auto bp = make_const_buffer_param(my_buffers);
168 : // bp is valid here...
169 :
170 : some_function(); // ...but if this modifies my_buffers...
171 :
172 : bp.data(); // ...this may see inconsistent state
173 : }
174 :
175 : void also_broken()
176 : {
177 : const_buffer_param bp = make_const_buffer_param(
178 : std::vector<const_buffer>{{data, size}});
179 : // UNSAFE: The temporary vector is destroyed immediately!
180 : bp.data(); // UNDEFINED BEHAVIOR
181 : }
182 : @endcode
183 :
184 : @par Passing Convention
185 :
186 : Pass by value. The class contains only three pointers (24 bytes
187 : on 64-bit systems), making copies trivial. Pass-by-value also
188 : makes the slicing behavior explicit at call sites.
189 :
190 : @see make_const_buffer_param, mutable_buffer_param
191 : */
192 : class const_buffer_param
193 : {
194 : protected:
195 : void* ctx_;
196 : std::span<const_buffer>(*data_)(void*);
197 : bool (*consume_)(void*, std::size_t);
198 :
199 : public:
200 : /** Return the current window of buffer descriptors.
201 :
202 : Returns a span of buffer descriptors representing the
203 : currently available portion of the buffer sequence. The
204 : span may contain fewer buffers than the total sequence;
205 : call @ref consume to advance through the sequence and
206 : then call `data()` again to get the next window.
207 :
208 : @return A span of const buffer descriptors. Empty span
209 : indicates no more data is available.
210 : */
211 : std::span<const_buffer>
212 19 : data() const noexcept
213 : {
214 19 : return data_(ctx_);
215 : }
216 :
217 : /** Consume bytes from the buffer sequence.
218 :
219 : Advances the current position by `n` bytes, consuming
220 : data from the front of the sequence. After consuming,
221 : call @ref data to get the updated window of remaining
222 : buffers.
223 :
224 : @param n Number of bytes to consume.
225 :
226 : @return `true` if more data remains in the sequence,
227 : `false` if the sequence is exhausted.
228 : */
229 : bool
230 12 : consume(std::size_t n)
231 : {
232 12 : return consume_(ctx_, n);
233 : }
234 : };
235 :
236 : /** A type-erased mutable buffer sequence for function call boundaries.
237 :
238 : This class is identical to @ref const_buffer_param except it
239 : operates on mutable buffer sequences, allowing the callee to
240 : write into the buffers.
241 :
242 : @warning This class has the same dangerous lifetime semantics
243 : as @ref const_buffer_param. Read that documentation carefully
244 : before using this class.
245 :
246 : @see make_buffer_param, const_buffer_param
247 : */
248 : class mutable_buffer_param
249 : {
250 : protected:
251 : void* ctx_;
252 : std::span<mutable_buffer>(*data_)(void*);
253 : bool (*consume_)(void*, std::size_t);
254 :
255 : public:
256 : /** Return the current window of buffer descriptors.
257 :
258 : @return A span of mutable buffer descriptors. Empty span
259 : indicates no more data is available.
260 :
261 : @see const_buffer_param::data
262 : */
263 : std::span<mutable_buffer>
264 4 : data() const noexcept
265 : {
266 4 : return data_(ctx_);
267 : }
268 :
269 : /** Consume bytes from the buffer sequence.
270 :
271 : @param n Number of bytes to consume.
272 :
273 : @return `true` if more data remains in the sequence,
274 : `false` if the sequence is exhausted.
275 :
276 : @see const_buffer_param::consume
277 : */
278 : bool
279 3 : consume(std::size_t n)
280 : {
281 3 : return consume_(ctx_, n);
282 : }
283 : };
284 :
285 : /** Create a const buffer parameter from a buffer sequence.
286 :
287 : Constructs a type-erased buffer parameter that provides
288 : incremental access to the given buffer sequence. The returned
289 : object inherits from @ref const_buffer_param and can be passed
290 : to functions accepting that type by value (causing slicing).
291 :
292 : @warning The returned object contains pointers to internal
293 : state. The object MUST remain alive while any sliced
294 : @ref const_buffer_param copy is in use. The intended pattern
295 : is to pass the result directly to a function:
296 :
297 : @code
298 : void process(const_buffer_param bp);
299 :
300 : // CORRECT: temporary lives through the call
301 : process(make_const_buffer_param(buffers));
302 :
303 : // DANGEROUS: storing extends the wrong lifetime
304 : auto bp = make_const_buffer_param(buffers);
305 : process(bp); // Works, but fragile
306 : @endcode
307 :
308 : @par Thread Safety
309 : Not thread-safe. The returned object and any sliced copies
310 : must not be used concurrently.
311 :
312 : @param bs The buffer sequence to wrap. Must remain valid
313 : for the lifetime of the returned object.
314 :
315 : @return An implementation object that inherits from
316 : @ref const_buffer_param.
317 : */
318 : template<ConstBufferSequence BS>
319 : [[nodiscard]] auto
320 8 : make_const_buffer_param(BS const& bs)
321 : {
322 : class impl : public const_buffer_param
323 : {
324 : std::array<const_buffer, 16> arr_;
325 : decltype(begin(std::declval<BS const&>())) it_;
326 : decltype(end(std::declval<BS const&>())) end_;
327 : std::size_t size_ = 0;
328 : std::size_t pos_ = 0;
329 : std::size_t offset_ = 0;
330 :
331 : void
332 18 : refill_window()
333 : {
334 18 : pos_ = 0;
335 18 : size_ = 0;
336 18 : offset_ = 0;
337 81 : for(; it_ != end_ && size_ < 16; ++it_)
338 : {
339 63 : const_buffer buf(*it_);
340 63 : if(buf.size() == 0)
341 4 : continue;
342 59 : arr_[size_++] = buf;
343 : }
344 18 : }
345 :
346 : static
347 : std::span<const_buffer>
348 19 : data_impl(void* ctx)
349 : {
350 19 : auto& self = *static_cast<impl*>(ctx);
351 19 : if(self.pos_ >= self.size_)
352 7 : self.refill_window();
353 19 : if(self.size_ == 0)
354 7 : return {};
355 12 : if(self.offset_ > 0)
356 : {
357 2 : auto& buf = self.arr_[self.pos_];
358 6 : buf = const_buffer(
359 2 : static_cast<char const*>(buf.data()) + self.offset_,
360 2 : buf.size() - self.offset_);
361 2 : self.offset_ = 0;
362 : }
363 24 : return { self.arr_.data() + self.pos_, self.size_ - self.pos_ };
364 : }
365 :
366 : static
367 : bool
368 12 : consume_impl(void* ctx, std::size_t n)
369 : {
370 12 : auto& self = *static_cast<impl*>(ctx);
371 73 : while(n > 0 && self.pos_ < self.size_)
372 : {
373 61 : auto const avail = self.arr_[self.pos_].size() - self.offset_;
374 61 : if(n < avail)
375 : {
376 3 : self.offset_ += n;
377 3 : n = 0;
378 : }
379 : else
380 : {
381 58 : n -= avail;
382 58 : self.offset_ = 0;
383 58 : ++self.pos_;
384 : }
385 : }
386 12 : if(self.pos_ >= self.size_ && self.it_ != self.end_)
387 3 : self.refill_window();
388 12 : return self.pos_ < self.size_;
389 : }
390 :
391 : static
392 : std::span<const_buffer>
393 0 : data_after_destroy(void*)
394 : {
395 0 : BOOST_ASSERT(!"const_buffer_param used after impl destroyed");
396 : return {};
397 : }
398 :
399 : static
400 : bool
401 0 : consume_after_destroy(void*, std::size_t)
402 : {
403 0 : BOOST_ASSERT(!"const_buffer_param used after impl destroyed");
404 : return false;
405 : }
406 :
407 : public:
408 : impl(impl const&) = delete;
409 : impl(impl&&) = delete;
410 : impl& operator=(impl const&) = delete;
411 : impl& operator=(impl&&) = delete;
412 :
413 : explicit
414 8 : impl(BS const& bs)
415 8 : : it_(begin(bs))
416 16 : , end_(end(bs))
417 : {
418 8 : ctx_ = this;
419 8 : data_ = &data_impl;
420 8 : consume_ = &consume_impl;
421 8 : refill_window();
422 8 : }
423 :
424 8 : ~impl()
425 : {
426 8 : ctx_ = nullptr;
427 8 : data_ = &data_after_destroy;
428 8 : consume_ = &consume_after_destroy;
429 8 : }
430 : };
431 :
432 8 : return impl(bs);
433 : }
434 :
435 : /** @copydoc make_const_buffer_param(BS const&)
436 : @note Deleted overload to prevent passing temporary buffer sequences.
437 : */
438 : template<class BS>
439 : requires ConstBufferSequence<std::remove_cvref_t<BS>> &&
440 : (!std::is_lvalue_reference_v<BS>)
441 : auto
442 : make_const_buffer_param(BS&&) = delete;
443 :
444 : /** Create a mutable buffer parameter from a buffer sequence.
445 :
446 : Constructs a type-erased buffer parameter that provides
447 : incremental access to the given mutable buffer sequence.
448 :
449 : @warning The returned object has the same dangerous lifetime
450 : semantics as @ref make_const_buffer_param. Read that
451 : documentation carefully.
452 :
453 : @par Thread Safety
454 : Not thread-safe.
455 :
456 : @param bs The mutable buffer sequence to wrap. Must remain
457 : valid for the lifetime of the returned object.
458 :
459 : @return An implementation object that inherits from
460 : @ref mutable_buffer_param.
461 :
462 : @see make_const_buffer_param, mutable_buffer_param
463 : */
464 : template<MutableBufferSequence BS>
465 : [[nodiscard]] auto
466 2 : make_buffer_param(BS const& bs)
467 : {
468 : class impl : public mutable_buffer_param
469 : {
470 : std::array<mutable_buffer, 16> arr_;
471 : decltype(begin(std::declval<BS const&>())) it_;
472 : decltype(end(std::declval<BS const&>())) end_;
473 : std::size_t size_ = 0;
474 : std::size_t pos_ = 0;
475 : std::size_t offset_ = 0;
476 :
477 : void
478 3 : refill_window()
479 : {
480 3 : pos_ = 0;
481 3 : size_ = 0;
482 3 : offset_ = 0;
483 7 : for(; it_ != end_ && size_ < 16; ++it_)
484 : {
485 4 : mutable_buffer buf(*it_);
486 4 : if(buf.size() == 0)
487 0 : continue;
488 4 : arr_[size_++] = buf;
489 : }
490 3 : }
491 :
492 : static
493 : std::span<mutable_buffer>
494 4 : data_impl(void* ctx)
495 : {
496 4 : auto& self = *static_cast<impl*>(ctx);
497 4 : if(self.pos_ >= self.size_)
498 1 : self.refill_window();
499 4 : if(self.size_ == 0)
500 1 : return {};
501 3 : if(self.offset_ > 0)
502 : {
503 1 : auto& buf = self.arr_[self.pos_];
504 3 : buf = mutable_buffer(
505 1 : static_cast<char*>(buf.data()) + self.offset_,
506 1 : buf.size() - self.offset_);
507 1 : self.offset_ = 0;
508 : }
509 6 : return { self.arr_.data() + self.pos_, self.size_ - self.pos_ };
510 : }
511 :
512 : static
513 : bool
514 3 : consume_impl(void* ctx, std::size_t n)
515 : {
516 3 : auto& self = *static_cast<impl*>(ctx);
517 8 : while(n > 0 && self.pos_ < self.size_)
518 : {
519 5 : auto const avail = self.arr_[self.pos_].size() - self.offset_;
520 5 : if(n < avail)
521 : {
522 2 : self.offset_ += n;
523 2 : n = 0;
524 : }
525 : else
526 : {
527 3 : n -= avail;
528 3 : self.offset_ = 0;
529 3 : ++self.pos_;
530 : }
531 : }
532 3 : if(self.pos_ >= self.size_ && self.it_ != self.end_)
533 0 : self.refill_window();
534 3 : return self.pos_ < self.size_;
535 : }
536 :
537 : static
538 : std::span<mutable_buffer>
539 0 : data_after_destroy(void*)
540 : {
541 0 : BOOST_ASSERT(!"mutable_buffer_param used after impl destroyed");
542 : return {};
543 : }
544 :
545 : static
546 : bool
547 0 : consume_after_destroy(void*, std::size_t)
548 : {
549 0 : BOOST_ASSERT(!"mutable_buffer_param used after impl destroyed");
550 : return false;
551 : }
552 :
553 : public:
554 : impl(impl const&) = delete;
555 : impl(impl&&) = delete;
556 : impl& operator=(impl const&) = delete;
557 : impl& operator=(impl&&) = delete;
558 :
559 : explicit
560 2 : impl(BS const& bs)
561 2 : : it_(begin(bs))
562 4 : , end_(end(bs))
563 : {
564 2 : ctx_ = this;
565 2 : data_ = &data_impl;
566 2 : consume_ = &consume_impl;
567 2 : refill_window();
568 2 : }
569 :
570 2 : ~impl()
571 : {
572 2 : ctx_ = nullptr;
573 2 : data_ = &data_after_destroy;
574 2 : consume_ = &consume_after_destroy;
575 2 : }
576 : };
577 :
578 2 : return impl(bs);
579 : }
580 :
581 : /** @copydoc make_buffer_param(BS const&)
582 : @note Deleted overload to prevent passing temporary buffer sequences.
583 : */
584 : template<class BS>
585 : requires MutableBufferSequence<std::remove_cvref_t<BS>> &&
586 : (!std::is_lvalue_reference_v<BS>)
587 : auto
588 : make_buffer_param(BS&&) = delete;
589 :
590 : } // namespace capy
591 : } // namespace boost
592 :
593 : #endif
|