Skip to main content

air_sys_syscall/io_uring/
provided.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! **Buffers fournis ring-mapped** (Temps 3b, `IORING_REGISTER_PBUF_RING`) :
6//! l'application enregistre un **groupe de buffers** ([`ProvidedBufferRing`]) ;
7//! elle soumet un `recv`/`read` avec `IOSQE_BUFFER_SELECT` **sans** buffer
8//! attaché ; le kernel **choisit un buffer du groupe** au moment où les données
9//! arrivent et rend son **id** dans le CQE (`CQE_F_BUFFER`, 16 bits hauts de
10//! `cqe->flags`). Les buffers ne sont consommés que par les connexions actives.
11//!
12//! Référence normative : `docs/specs/layer-0/io-uring-3b-provided.md`.
13//!
14//! **Mécanisme.** Un groupe possède deux mémoires : l'**anneau de descripteurs**
15//! (`io_uring_buf[count]`, partagé avec le kernel — le `tail` `u16` à l'offset 14
16//! du premier descripteur est avancé par **store-release**) et la **mémoire de
17//! données** (`count × buf_size`, possédée). L'anneau de descripteurs est soit
18//! alloué par la façade (mmap anonyme page-aligné), soit par le kernel
19//! (`kernel_mmap`) puis mmappé à `IORING_OFF_PBUF_RING | (group_id << SHIFT)`.
20//!
21//! **Cycle checkout → traitement → return** rendu sûr par RAII
22//! ([`ProvidedBuffer`]) : la libération du guard **réapprovisionne** le groupe
23//! (réécrit le descripteur, avance le `tail`). En mode **incrémental**
24//! (`IOU_PBUF_RING_INC`), un `CQE_F_BUF_MORE` signale que **le même buffer**
25//! recevra d'autres complétions → le guard **ne réapprovisionne pas** (réappro
26//! seulement à la complétion finale, sans `BUF_MORE`).
27//!
28//! **Legacy évacués** : `PROVIDE_BUFFERS` (31) / `REMOVE_BUFFERS` (32) — cf.
29//! `docs/UNSUPPORTED.md`.
30
31use super::{Completion, IoUring, SubmissionToken, raw, syscall};
32use crate::mem::{Mapping, MmapRegion, mmap_file};
33use air_sys_types::Errno;
34use air_sys_types::fd::{AsRawFd, BorrowedFd};
35use air_sys_types::io_uring::CompletionFlags;
36use air_sys_types::mem::{MapFlags, ProtectionFlags};
37use air_sys_types::net::MessageFlags;
38use alloc::vec::Vec;
39use core::num::{NonZeroU16, NonZeroU32};
40use core::sync::atomic::{AtomicU16, Ordering};
41
42// ───────────────────────────────────────────────────────────────────────────
43// Options & status
44// ───────────────────────────────────────────────────────────────────────────
45
46/// Options de création d'un [`ProvidedBufferRing`].
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
48pub struct ProvidedBufferRingOptions {
49    /// Mémoire de l'anneau de **descripteurs** allouée par le **kernel**
50    /// (`IOU_PBUF_RING_MMAP`) puis mmappée par la façade, au lieu d'être fournie
51    /// par l'application.
52    pub kernel_mmap: bool,
53    /// Consommation **incrémentale** (`IOU_PBUF_RING_INC`) : un buffer peut servir
54    /// plusieurs complétions, consommé au fur et à mesure (cf. module §5).
55    pub incremental: bool,
56}
57
58/// État d'un groupe rendu par [`ProvidedBufferRing::status`] (`PBUF_STATUS`).
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub struct ProvidedBufferRingStatus {
61    /// Tête courante du groupe (index de consommation côté kernel).
62    pub head: u32,
63}
64
65// ───────────────────────────────────────────────────────────────────────────
66// ProvidedBufferRing
67// ───────────────────────────────────────────────────────────────────────────
68
69/// Origine de la mémoire de l'**anneau de descripteurs** (page-alignée).
70#[derive(Debug)]
71enum RingMemory {
72    /// Anneau alloué par la façade (mmap anonyme).
73    App(MmapRegion),
74    /// Anneau alloué par le kernel (`IOU_PBUF_RING_MMAP`) puis mmappé.
75    Kernel(Mapping),
76}
77
78impl RingMemory {
79    /// Base de l'anneau (`struct io_uring_buf_ring`). `*mut` : la façade écrit
80    /// les descripteurs et le `tail` ; le kernel lit les descripteurs/`tail` et
81    /// écrit le `head` (champs **disjoints**, pas d'écriture aliasée).
82    fn base(&self) -> *mut u8 {
83        match self {
84            Self::App(region) => region.as_ptr().cast_mut(),
85            Self::Kernel(mapping) => mapping.as_ptr().cast_mut(),
86        }
87    }
88}
89
90/// Groupe de **buffers fournis** *ring-mapped* (`IORING_REGISTER_PBUF_RING`, 22).
91///
92/// Possède l'anneau de descripteurs **et** la mémoire de données tant que le
93/// groupe est enregistré ; [`ProvidedBufferRing::unregister`] les restitue
94/// (désenregistre puis libère). Les buffers consommés sont **rendus** au groupe
95/// par la libération du guard [`ProvidedBuffer`].
96#[derive(Debug)]
97pub struct ProvidedBufferRing {
98    /// Identifiant de groupe (`bgid`).
99    bgid: u16,
100    /// Nombre de buffers (puissance de 2).
101    count: u16,
102    /// `count - 1` (masque d'indexation de l'anneau).
103    mask: u16,
104    /// Taille d'un buffer en octets.
105    buf_size: u32,
106    /// Mémoire de l'anneau de descripteurs (partagée avec le kernel).
107    ring_mem: RingMemory,
108    /// Mémoire de données possédée (`count × buf_size` octets). Le kernel y écrit
109    /// les données reçues ; reste vivante tant que le groupe est enregistré.
110    data: Vec<u8>,
111    /// `tail` local (prochain slot où écrire un descripteur ; wrappe).
112    provide_tail: u16,
113    /// Nombre de buffers actuellement disponibles côté groupe (non checkout).
114    available: u16,
115    /// Buffer `bid` actuellement **sorti** (checkout non encore rendu). Indexé par
116    /// `bid` ; rend l'accounting d'`available` **idempotent** vis-à-vis des
117    /// complétions multiples d'un même buffer (mode incrémental, `BUF_MORE`).
118    out_flags: Vec<bool>,
119}
120
121impl ProvidedBufferRing {
122    /// `IORING_REGISTER_PBUF_RING` (22) : enregistre un groupe `group_id` de
123    /// `count` buffers de `buf_size` octets (`count` **puissance de 2**), puis
124    /// approvisionne les `count` buffers.
125    ///
126    /// # Errors
127    ///
128    /// [`Errno::EINVAL`] si `count` n'est pas une puissance de 2 ou si
129    /// `count × buf_size` déborde ; erreurs de `mmap`/`io_uring_register`
130    /// ([`Errno::ENOMEM`], [`Errno::EEXIST`] si `group_id` déjà pris).
131    pub fn register(
132        ring: &mut IoUring,
133        group_id: u16,
134        count: NonZeroU16,
135        buf_size: NonZeroU32,
136        opts: ProvidedBufferRingOptions,
137    ) -> Result<Self, Errno> {
138        let count = count.get();
139        if !count.is_power_of_two() {
140            return Err(Errno::EINVAL);
141        }
142        let buf_size = buf_size.get();
143        let count_usize = usize::from(count);
144        let total = count_usize
145            .checked_mul(usize::try_from(buf_size).expect("u32 ⊆ usize sur cible LP64"))
146            .ok_or(Errno::EINVAL)?;
147        let ring_bytes = count_usize
148            .checked_mul(raw::IO_URING_BUF_SIZE)
149            .ok_or(Errno::EINVAL)?;
150        let data = vec![0u8; total];
151
152        let mut flags = if opts.incremental {
153            raw::IOU_PBUF_RING_INC
154        } else {
155            0
156        };
157        // Mode app : anneau alloué ici (mmap anonyme page-aligné).
158        let app_region = if opts.kernel_mmap {
159            flags |= raw::IOU_PBUF_RING_MMAP;
160            None
161        } else {
162            let region = MmapRegion::new_anonymous(
163                ring_bytes,
164                ProtectionFlags::READ | ProtectionFlags::WRITE,
165                MapFlags::PRIVATE,
166            )?;
167            // Anneau de contrôle **pinné par le kernel** (il y écrit hors de tout
168            // syscall du process) : le retirer de l'héritage au `fork` évite la
169            // fenêtre COW/GUP classique des régions pinnées (ADR-132).
170            super::deny_fork_inheritance(region.as_ptr(), region.len())?;
171            Some(region)
172        };
173        let ring_addr = app_region.as_ref().map_or(0, |r| r.as_ptr() as u64);
174
175        let reg = raw::IoUringBufReg {
176            ring_addr,
177            ring_entries: u32::from(count),
178            bgid: group_id,
179            flags,
180            resv: [0; 3],
181        };
182        // SAFETY: PBUF_RING attend une `io_uring_buf_reg` (nr_args == 1) ; `reg`
183        // vit pendant l'appel ; `ring_addr` pointe l'anneau page-aligné (mode app)
184        // ou 0 (mode kernel).
185        let ret = unsafe {
186            syscall::register(
187                ring.fd_raw(),
188                raw::IORING_REGISTER_PBUF_RING,
189                core::ptr::from_ref(&reg) as u64,
190                1,
191            )
192        };
193        if ret < 0 {
194            return Err(raw::errno_from_negative_syscall_ret(ret));
195        }
196
197        // Mode kernel : mmap l'anneau alloué par le kernel.
198        let ring_mem = match app_region {
199            Some(region) => RingMemory::App(region),
200            None => {
201                let offset =
202                    raw::IORING_OFF_PBUF_RING | (u64::from(group_id) << raw::IORING_OFF_PBUF_SHIFT);
203                // SAFETY: `ring.fd_raw()` est un ring fd valide possédé, emprunté
204                // le temps de l'appel mmap.
205                let fd = unsafe { BorrowedFd::borrow_raw(ring.fd_raw()) };
206                let mapping = mmap_file(
207                    fd,
208                    ring_bytes,
209                    offset,
210                    ProtectionFlags::READ | ProtectionFlags::WRITE,
211                    MapFlags::SHARED,
212                )?;
213                // `MAP_SHARED` sur le ring fd : exactement la faille des anneaux
214                // SQ/CQ/SQE (ADR-132) — un enfant forké écrirait dans les *mêmes*
215                // pages que le parent (adresses de buffers, `tail`).
216                super::deny_fork_inheritance(mapping.as_ptr(), mapping.len())?;
217                RingMemory::Kernel(mapping)
218            }
219        };
220
221        let mut this = Self {
222            bgid: group_id,
223            count,
224            mask: count.wrapping_sub(1),
225            buf_size,
226            ring_mem,
227            data,
228            provide_tail: 0,
229            available: 0,
230            out_flags: vec![false; usize::from(count)],
231        };
232        this.provide_initial();
233        Ok(this)
234    }
235
236    /// Approvisionne les `count` buffers à l'enregistrement (descripteurs
237    /// `0..count`, `tail = count`).
238    fn provide_initial(&mut self) {
239        for bid in 0..self.count {
240            // Slot == bid à l'initialisation (tail part de 0).
241            self.write_descriptor(bid, bid);
242        }
243        self.provide_tail = self.count;
244        self.available = self.count;
245        self.publish_tail();
246    }
247
248    /// Écrit un descripteur `io_uring_buf` (`addr`/`len`/`bid`) dans `slot`, sans
249    /// toucher l'offset 14 (`resv`/`tail`).
250    fn write_descriptor(&self, slot: u16, bid: u16) {
251        let base = self.ring_mem.base();
252        let slot_off = usize::from(slot & self.mask)
253            .checked_mul(raw::IO_URING_BUF_SIZE)
254            .expect("slot borné par le masque");
255        let data_off = usize::from(bid)
256            .checked_mul(usize::try_from(self.buf_size).expect("u32 ⊆ usize"))
257            .expect("bid × buf_size ≤ total (borné par construction)");
258        let addr = self.data.as_ptr().wrapping_add(data_off) as u64;
259        // SAFETY: `base` pointe l'anneau page-aligné vivant ; `slot_off` est dans
260        // `count × 16` octets (slot masqué) ; les écritures `u64`/`u32`/`u16` sont
261        // alignées (base page-alignée, offsets multiples de 8/4/2). On n'écrit pas
262        // l'offset 14 (`tail`). `addr` désigne le buffer `bid` dans `data` (vivant).
263        unsafe {
264            let buf = base.add(slot_off);
265            buf.cast::<u64>().write(addr);
266            buf.add(8).cast::<u32>().write(self.buf_size);
267            buf.add(12).cast::<u16>().write(bid);
268        }
269    }
270
271    /// Publie le `tail` local par un **store-release** (rend les descripteurs
272    /// écrits visibles au kernel avant l'avancée de la queue).
273    fn publish_tail(&self) {
274        let base = self.ring_mem.base();
275        // SAFETY: `base + 14` est le mot `tail` (`u16`, 2-aligné) de l'anneau
276        // vivant ; `AtomicU16` a le même layout qu'un `u16`. Store-release apparié
277        // au load-acquire du kernel (protocole d'anneau de buffers).
278        unsafe {
279            let tail = base
280                .add(raw::IO_URING_BUF_RING_TAIL_OFFSET)
281                .cast::<AtomicU16>();
282            (*tail).store(self.provide_tail, Ordering::Release);
283        }
284    }
285
286    /// Comptabilise un **checkout** du buffer `bid` (le kernel l'a sorti pour une
287    /// complétion). Idempotent : une complétion supplémentaire sur un buffer
288    /// **déjà** sorti (incrémental, `BUF_MORE`) ne re-décrémente pas `available`.
289    fn checkout(&mut self, bid: u16) {
290        let newly_out = self
291            .out_flags
292            .get_mut(usize::from(bid))
293            .is_some_and(|flag| {
294                if *flag {
295                    false
296                } else {
297                    *flag = true;
298                    true
299                }
300            });
301        if newly_out {
302            self.available = self.available.saturating_sub(1);
303        }
304    }
305
306    /// **Rend** le buffer `bid` au groupe : réécrit son descripteur au `tail`,
307    /// avance et publie le `tail`, et — s'il était sorti — incrémente `available`.
308    fn return_buffer(&mut self, bid: u16) {
309        self.write_descriptor(self.provide_tail, bid);
310        self.provide_tail = self.provide_tail.wrapping_add(1);
311        self.publish_tail();
312        let was_out = self
313            .out_flags
314            .get_mut(usize::from(bid))
315            .is_some_and(|flag| {
316                let prev = *flag;
317                *flag = false;
318                prev
319            });
320        if was_out {
321            self.available = self.available.saturating_add(1).min(self.count);
322        }
323    }
324
325    /// Tranche d'octets du buffer `bid`, longueur `len` (bornée par construction :
326    /// `len ≤ buf_size` et `bid < count`).
327    fn buffer_bytes(&self, bid: u16, len: usize) -> &[u8] {
328        let bufsz = usize::try_from(self.buf_size).expect("u32 ⊆ usize");
329        let start = usize::from(bid)
330            .checked_mul(bufsz)
331            .expect("bid × buf_size borné");
332        let end = start.checked_add(len).expect("start + len borné");
333        self.data
334            .get(start..end)
335            .expect("buffer borné (len ≤ buf_size, bid < count)")
336    }
337
338    /// Variante mutable de [`Self::buffer_bytes`].
339    fn buffer_bytes_mut(&mut self, bid: u16, len: usize) -> &mut [u8] {
340        let bufsz = usize::try_from(self.buf_size).expect("u32 ⊆ usize");
341        let start = usize::from(bid)
342            .checked_mul(bufsz)
343            .expect("bid × buf_size borné");
344        let end = start.checked_add(len).expect("start + len borné");
345        self.data
346            .get_mut(start..end)
347            .expect("buffer borné (len ≤ buf_size, bid < count)")
348    }
349
350    /// `IORING_UNREGISTER_PBUF_RING` (23) : désenregistre le groupe et **libère**
351    /// l'anneau et la mémoire de données (à la consommation de `self`).
352    ///
353    /// **Sûreté par construction** : un [`ProvidedBuffer`] en cours de traitement
354    /// emprunte le groupe `&mut` — impossible de désenregistrer tant qu'il vit.
355    /// Le code suivant **ne compile pas** :
356    ///
357    /// ```compile_fail
358    /// # use air_sys_syscall::io_uring::{IoUring, ProvidedBufferRing, ProvidedBufferRingOptions};
359    /// # use core::num::{NonZeroU16, NonZeroU32};
360    /// # fn demo(ring: &mut IoUring, completion: air_sys_syscall::io_uring::Completion) {
361    /// let mut group = ProvidedBufferRing::register(
362    ///     ring, 0, NonZeroU16::new(4).unwrap(), NonZeroU32::new(64).unwrap(),
363    ///     ProvidedBufferRingOptions::default()).unwrap();
364    /// let buf = completion.into_provided_buffer(&mut group).unwrap(); // emprunte `group`
365    /// let _ = group.unregister(ring).unwrap();  // déplace `group`…
366    /// let _ = buf.id();                          // …emprunt encore vivant ⇒ ERREUR
367    /// # }
368    /// ```
369    ///
370    /// # Errors
371    ///
372    /// Erreurs de `io_uring_register` ([`Errno::EINVAL`] si le groupe n'est pas
373    /// enregistré). En cas d'erreur, la mémoire est tout de même libérée.
374    pub fn unregister(self, ring: &mut IoUring) -> Result<(), Errno> {
375        let reg = raw::IoUringBufReg {
376            ring_addr: 0,
377            ring_entries: 0,
378            bgid: self.bgid,
379            flags: 0,
380            resv: [0; 3],
381        };
382        // SAFETY: UNREGISTER_PBUF_RING attend une `io_uring_buf_reg` (bgid,
383        // nr_args == 1) ; `reg` vit pendant l'appel.
384        let ret = unsafe {
385            syscall::register(
386                ring.fd_raw(),
387                raw::IORING_UNREGISTER_PBUF_RING,
388                core::ptr::from_ref(&reg) as u64,
389                1,
390            )
391        };
392        if ret < 0 {
393            return Err(raw::errno_from_negative_syscall_ret(ret));
394        }
395        Ok(())
396    }
397
398    /// `IORING_REGISTER_PBUF_STATUS` (26) : tête courante du groupe (diagnostic /
399    /// régulation).
400    ///
401    /// # Errors
402    ///
403    /// Erreurs de `io_uring_register`.
404    pub fn status(&self, ring: &IoUring) -> Result<ProvidedBufferRingStatus, Errno> {
405        let mut st = raw::IoUringBufStatus {
406            buf_group: u32::from(self.bgid),
407            head: 0,
408            resv: [0; 8],
409        };
410        // SAFETY: PBUF_STATUS attend une `io_uring_buf_status` (nr_args == 1) ; le
411        // kernel y réécrit `head`. `st` vit pendant l'appel.
412        let ret = unsafe {
413            syscall::register(
414                ring.fd_raw(),
415                raw::IORING_REGISTER_PBUF_STATUS,
416                core::ptr::from_mut(&mut st) as u64,
417                1,
418            )
419        };
420        if ret < 0 {
421            return Err(raw::errno_from_negative_syscall_ret(ret));
422        }
423        Ok(ProvidedBufferRingStatus { head: st.head })
424    }
425
426    /// Identifiant de groupe (`bgid`).
427    #[must_use]
428    pub fn group_id(&self) -> u16 {
429        self.bgid
430    }
431
432    /// Nombre de buffers actuellement disponibles (non checkout).
433    #[must_use]
434    pub fn available(&self) -> u16 {
435        self.available
436    }
437}
438
439// ───────────────────────────────────────────────────────────────────────────
440// Soumission avec sélection automatique
441// ───────────────────────────────────────────────────────────────────────────
442
443impl IoUring {
444    /// `IORING_OP_RECV` (27) avec **sélection automatique** de buffer dans
445    /// `group` (`IOSQE_BUFFER_SELECT`). Aucun buffer n'est attaché au slot S1.
446    /// `bundle` active `IORING_RECVSEND_BUNDLE` (plusieurs buffers en un recv).
447    /// Pénurie ⇒ complétion `-ENOBUFS`.
448    ///
449    /// # Errors
450    ///
451    /// [`Errno::EBUSY`] si la SQ ou le slab sont pleins ; à la complétion,
452    /// `-ENOBUFS` si le groupe est vide.
453    pub fn submit_receive_provided(
454        &mut self,
455        sock: BorrowedFd<'_>,
456        group: &ProvidedBufferRing,
457        flags: MessageFlags,
458        bundle: bool,
459    ) -> Result<SubmissionToken, Errno> {
460        let fd = sock.as_raw_fd();
461        let bgid = group.group_id();
462        let msg_flags = flags.bits().cast_unsigned();
463        self.pending_options = self.pending_options.buffer_select();
464        self.submit_op(None, |sqe| {
465            sqe.opcode = raw::IORING_OP_RECV;
466            sqe.fd = fd;
467            sqe.len = 0; // le kernel borne par la taille du buffer choisi
468            sqe.op_flags = msg_flags;
469            sqe.buf_index_or_group = bgid;
470            if bundle {
471                sqe.ioprio |= raw::IORING_RECVSEND_BUNDLE;
472            }
473        })
474    }
475
476    /// `IORING_OP_READ` (22) avec sélection automatique de buffer. `length`
477    /// borne le nombre d'octets lus (≤ taille du buffer). `bundle` active
478    /// `IORING_RECVSEND_BUNDLE`.
479    ///
480    /// # Errors
481    ///
482    /// Voir [`IoUring::submit_receive_provided`].
483    pub fn submit_read_provided(
484        &mut self,
485        fd: BorrowedFd<'_>,
486        group: &ProvidedBufferRing,
487        length: u32,
488        offset: Option<u64>,
489        bundle: bool,
490    ) -> Result<SubmissionToken, Errno> {
491        let fd = fd.as_raw_fd();
492        let bgid = group.group_id();
493        let off = offset.unwrap_or(u64::MAX);
494        self.pending_options = self.pending_options.buffer_select();
495        self.submit_op(None, |sqe| {
496            sqe.opcode = raw::IORING_OP_READ;
497            sqe.fd = fd;
498            sqe.len = length;
499            sqe.off_or_addr2 = off;
500            sqe.buf_index_or_group = bgid;
501            if bundle {
502                sqe.ioprio |= raw::IORING_RECVSEND_BUNDLE;
503            }
504        })
505    }
506}
507
508// ───────────────────────────────────────────────────────────────────────────
509// Consommation : le guard ProvidedBuffer
510// ───────────────────────────────────────────────────────────────────────────
511
512impl Completion {
513    /// Récupère le buffer **choisi par le kernel** pour cette complétion
514    /// (`CQE_F_BUFFER`). `None` si la complétion n'a pas consommé de buffer fourni
515    /// (op sans sélection, ou pénurie `-ENOBUFS`).
516    ///
517    /// L'emprunt `&'r mut ProvidedBufferRing` interdit le désenregistrement du
518    /// groupe tant que le buffer est en cours de traitement.
519    #[must_use]
520    pub fn into_provided_buffer<'r>(
521        self,
522        group: &'r mut ProvidedBufferRing,
523    ) -> Option<ProvidedBuffer<'r>> {
524        let bid = self.buffer_id()?;
525        let flags = self.flags();
526        // Longueur utile = `cqe->res` octets reçus, bornée à la taille du buffer.
527        let received = usize::try_from(self.raw_result()).unwrap_or(0);
528        let len = received.min(usize::try_from(group.buf_size).expect("u32 ⊆ usize"));
529        // Incrémental : `CQE_F_BUF_MORE` ⇒ le kernel garde le buffer (pas de
530        // réapprovisionnement à la libération du guard).
531        let replenish = !flags.contains(CompletionFlags::BUF_MORE);
532        group.checkout(bid);
533        Some(ProvidedBuffer {
534            group,
535            bid,
536            len,
537            replenish,
538        })
539    }
540}
541
542/// Accès RAII aux données reçues dans un buffer fourni. **Rend le buffer au
543/// groupe** (réapprovisionnement) à la libération — sauf en mode incrémental
544/// quand `CQE_F_BUF_MORE` est positionné (le kernel garde le buffer).
545///
546/// L'emprunt `&'r mut ProvidedBufferRing` empêche le désenregistrement du groupe
547/// tant que le guard vit (sûreté par construction).
548#[derive(Debug)]
549pub struct ProvidedBuffer<'r> {
550    /// Groupe propriétaire (emprunté mut → désenregistrement interdit).
551    group: &'r mut ProvidedBufferRing,
552    /// Id du buffer choisi par le kernel.
553    bid: u16,
554    /// Octets utiles reçus (`cqe->res`, borné à `buf_size`).
555    len: usize,
556    /// Réapprovisionner à la libération (faux si `CQE_F_BUF_MORE`).
557    replenish: bool,
558}
559
560impl ProvidedBuffer<'_> {
561    /// Id du buffer (16 bits hauts de `cqe->flags`).
562    #[must_use]
563    pub fn id(&self) -> u16 {
564        self.bid
565    }
566
567    /// Octets reçus (longueur = `cqe->res`).
568    #[must_use]
569    pub fn data(&self) -> &[u8] {
570        self.group.buffer_bytes(self.bid, self.len)
571    }
572
573    /// Octets reçus, en accès mutable.
574    #[must_use]
575    pub fn data_mut(&mut self) -> &mut [u8] {
576        self.group.buffer_bytes_mut(self.bid, self.len)
577    }
578}
579
580impl Drop for ProvidedBuffer<'_> {
581    fn drop(&mut self) {
582        if self.replenish {
583            self.group.return_buffer(self.bid);
584        }
585    }
586}
587
588// ───────────────────────────────────────────────────────────────────────────
589// Tests
590// ───────────────────────────────────────────────────────────────────────────
591//
592// Tous les tests touchent `io_uring_register`/`enter` (non modélisés par Miri) →
593// `#[cfg_attr(miri, ignore)]`. La logique du guard (réapprovisionnement,
594// incrémental `BUF_MORE`, branche `None`) est exercée **déterministiquement** via
595// des `Completion` **synthétiques** (champs construits dans ce module enfant),
596// sans dépendre du timing socket ni de la mise à `BUF_MORE` par le kernel. La
597// sûreté de lifetime (désenregistrement interdit sous un buffer vivant) est
598// prouvée par le doctest `compile_fail` sur `unregister`.
599
600#[cfg(test)]
601mod tests;