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            Some(MmapRegion::new_anonymous(
163                ring_bytes,
164                ProtectionFlags::READ | ProtectionFlags::WRITE,
165                MapFlags::PRIVATE,
166            )?)
167        };
168        let ring_addr = app_region.as_ref().map_or(0, |r| r.as_ptr() as u64);
169
170        let reg = raw::IoUringBufReg {
171            ring_addr,
172            ring_entries: u32::from(count),
173            bgid: group_id,
174            flags,
175            resv: [0; 3],
176        };
177        // SAFETY: PBUF_RING attend une `io_uring_buf_reg` (nr_args == 1) ; `reg`
178        // vit pendant l'appel ; `ring_addr` pointe l'anneau page-aligné (mode app)
179        // ou 0 (mode kernel).
180        let ret = unsafe {
181            syscall::register(
182                ring.fd_raw(),
183                raw::IORING_REGISTER_PBUF_RING,
184                core::ptr::from_ref(&reg) as u64,
185                1,
186            )
187        };
188        if ret < 0 {
189            return Err(raw::errno_from_negative_syscall_ret(ret));
190        }
191
192        // Mode kernel : mmap l'anneau alloué par le kernel.
193        let ring_mem = match app_region {
194            Some(region) => RingMemory::App(region),
195            None => {
196                let offset =
197                    raw::IORING_OFF_PBUF_RING | (u64::from(group_id) << raw::IORING_OFF_PBUF_SHIFT);
198                // SAFETY: `ring.fd_raw()` est un ring fd valide possédé, emprunté
199                // le temps de l'appel mmap.
200                let fd = unsafe { BorrowedFd::borrow_raw(ring.fd_raw()) };
201                let mapping = mmap_file(
202                    fd,
203                    ring_bytes,
204                    offset,
205                    ProtectionFlags::READ | ProtectionFlags::WRITE,
206                    MapFlags::SHARED,
207                )?;
208                RingMemory::Kernel(mapping)
209            }
210        };
211
212        let mut this = Self {
213            bgid: group_id,
214            count,
215            mask: count.wrapping_sub(1),
216            buf_size,
217            ring_mem,
218            data,
219            provide_tail: 0,
220            available: 0,
221            out_flags: vec![false; usize::from(count)],
222        };
223        this.provide_initial();
224        Ok(this)
225    }
226
227    /// Approvisionne les `count` buffers à l'enregistrement (descripteurs
228    /// `0..count`, `tail = count`).
229    fn provide_initial(&mut self) {
230        for bid in 0..self.count {
231            // Slot == bid à l'initialisation (tail part de 0).
232            self.write_descriptor(bid, bid);
233        }
234        self.provide_tail = self.count;
235        self.available = self.count;
236        self.publish_tail();
237    }
238
239    /// Écrit un descripteur `io_uring_buf` (`addr`/`len`/`bid`) dans `slot`, sans
240    /// toucher l'offset 14 (`resv`/`tail`).
241    fn write_descriptor(&self, slot: u16, bid: u16) {
242        let base = self.ring_mem.base();
243        let slot_off = usize::from(slot & self.mask)
244            .checked_mul(raw::IO_URING_BUF_SIZE)
245            .expect("slot borné par le masque");
246        let data_off = usize::from(bid)
247            .checked_mul(usize::try_from(self.buf_size).expect("u32 ⊆ usize"))
248            .expect("bid × buf_size ≤ total (borné par construction)");
249        let addr = self.data.as_ptr().wrapping_add(data_off) as u64;
250        // SAFETY: `base` pointe l'anneau page-aligné vivant ; `slot_off` est dans
251        // `count × 16` octets (slot masqué) ; les écritures `u64`/`u32`/`u16` sont
252        // alignées (base page-alignée, offsets multiples de 8/4/2). On n'écrit pas
253        // l'offset 14 (`tail`). `addr` désigne le buffer `bid` dans `data` (vivant).
254        unsafe {
255            let buf = base.add(slot_off);
256            buf.cast::<u64>().write(addr);
257            buf.add(8).cast::<u32>().write(self.buf_size);
258            buf.add(12).cast::<u16>().write(bid);
259        }
260    }
261
262    /// Publie le `tail` local par un **store-release** (rend les descripteurs
263    /// écrits visibles au kernel avant l'avancée de la queue).
264    fn publish_tail(&self) {
265        let base = self.ring_mem.base();
266        // SAFETY: `base + 14` est le mot `tail` (`u16`, 2-aligné) de l'anneau
267        // vivant ; `AtomicU16` a le même layout qu'un `u16`. Store-release apparié
268        // au load-acquire du kernel (protocole d'anneau de buffers).
269        unsafe {
270            let tail = base
271                .add(raw::IO_URING_BUF_RING_TAIL_OFFSET)
272                .cast::<AtomicU16>();
273            (*tail).store(self.provide_tail, Ordering::Release);
274        }
275    }
276
277    /// Comptabilise un **checkout** du buffer `bid` (le kernel l'a sorti pour une
278    /// complétion). Idempotent : une complétion supplémentaire sur un buffer
279    /// **déjà** sorti (incrémental, `BUF_MORE`) ne re-décrémente pas `available`.
280    fn checkout(&mut self, bid: u16) {
281        let newly_out = self
282            .out_flags
283            .get_mut(usize::from(bid))
284            .is_some_and(|flag| {
285                if *flag {
286                    false
287                } else {
288                    *flag = true;
289                    true
290                }
291            });
292        if newly_out {
293            self.available = self.available.saturating_sub(1);
294        }
295    }
296
297    /// **Rend** le buffer `bid` au groupe : réécrit son descripteur au `tail`,
298    /// avance et publie le `tail`, et — s'il était sorti — incrémente `available`.
299    fn return_buffer(&mut self, bid: u16) {
300        self.write_descriptor(self.provide_tail, bid);
301        self.provide_tail = self.provide_tail.wrapping_add(1);
302        self.publish_tail();
303        let was_out = self
304            .out_flags
305            .get_mut(usize::from(bid))
306            .is_some_and(|flag| {
307                let prev = *flag;
308                *flag = false;
309                prev
310            });
311        if was_out {
312            self.available = self.available.saturating_add(1).min(self.count);
313        }
314    }
315
316    /// Tranche d'octets du buffer `bid`, longueur `len` (bornée par construction :
317    /// `len ≤ buf_size` et `bid < count`).
318    fn buffer_bytes(&self, bid: u16, len: usize) -> &[u8] {
319        let bufsz = usize::try_from(self.buf_size).expect("u32 ⊆ usize");
320        let start = usize::from(bid)
321            .checked_mul(bufsz)
322            .expect("bid × buf_size borné");
323        let end = start.checked_add(len).expect("start + len borné");
324        self.data
325            .get(start..end)
326            .expect("buffer borné (len ≤ buf_size, bid < count)")
327    }
328
329    /// Variante mutable de [`Self::buffer_bytes`].
330    fn buffer_bytes_mut(&mut self, bid: u16, len: usize) -> &mut [u8] {
331        let bufsz = usize::try_from(self.buf_size).expect("u32 ⊆ usize");
332        let start = usize::from(bid)
333            .checked_mul(bufsz)
334            .expect("bid × buf_size borné");
335        let end = start.checked_add(len).expect("start + len borné");
336        self.data
337            .get_mut(start..end)
338            .expect("buffer borné (len ≤ buf_size, bid < count)")
339    }
340
341    /// `IORING_UNREGISTER_PBUF_RING` (23) : désenregistre le groupe et **libère**
342    /// l'anneau et la mémoire de données (à la consommation de `self`).
343    ///
344    /// **Sûreté par construction** : un [`ProvidedBuffer`] en cours de traitement
345    /// emprunte le groupe `&mut` — impossible de désenregistrer tant qu'il vit.
346    /// Le code suivant **ne compile pas** :
347    ///
348    /// ```compile_fail
349    /// # use air_sys_syscall::io_uring::{IoUring, ProvidedBufferRing, ProvidedBufferRingOptions};
350    /// # use core::num::{NonZeroU16, NonZeroU32};
351    /// # fn demo(ring: &mut IoUring, completion: air_sys_syscall::io_uring::Completion) {
352    /// let mut group = ProvidedBufferRing::register(
353    ///     ring, 0, NonZeroU16::new(4).unwrap(), NonZeroU32::new(64).unwrap(),
354    ///     ProvidedBufferRingOptions::default()).unwrap();
355    /// let buf = completion.into_provided_buffer(&mut group).unwrap(); // emprunte `group`
356    /// let _ = group.unregister(ring).unwrap();  // déplace `group`…
357    /// let _ = buf.id();                          // …emprunt encore vivant ⇒ ERREUR
358    /// # }
359    /// ```
360    ///
361    /// # Errors
362    ///
363    /// Erreurs de `io_uring_register` ([`Errno::EINVAL`] si le groupe n'est pas
364    /// enregistré). En cas d'erreur, la mémoire est tout de même libérée.
365    pub fn unregister(self, ring: &mut IoUring) -> Result<(), Errno> {
366        let reg = raw::IoUringBufReg {
367            ring_addr: 0,
368            ring_entries: 0,
369            bgid: self.bgid,
370            flags: 0,
371            resv: [0; 3],
372        };
373        // SAFETY: UNREGISTER_PBUF_RING attend une `io_uring_buf_reg` (bgid,
374        // nr_args == 1) ; `reg` vit pendant l'appel.
375        let ret = unsafe {
376            syscall::register(
377                ring.fd_raw(),
378                raw::IORING_UNREGISTER_PBUF_RING,
379                core::ptr::from_ref(&reg) as u64,
380                1,
381            )
382        };
383        if ret < 0 {
384            return Err(raw::errno_from_negative_syscall_ret(ret));
385        }
386        Ok(())
387    }
388
389    /// `IORING_REGISTER_PBUF_STATUS` (26) : tête courante du groupe (diagnostic /
390    /// régulation).
391    ///
392    /// # Errors
393    ///
394    /// Erreurs de `io_uring_register`.
395    pub fn status(&self, ring: &IoUring) -> Result<ProvidedBufferRingStatus, Errno> {
396        let mut st = raw::IoUringBufStatus {
397            buf_group: u32::from(self.bgid),
398            head: 0,
399            resv: [0; 8],
400        };
401        // SAFETY: PBUF_STATUS attend une `io_uring_buf_status` (nr_args == 1) ; le
402        // kernel y réécrit `head`. `st` vit pendant l'appel.
403        let ret = unsafe {
404            syscall::register(
405                ring.fd_raw(),
406                raw::IORING_REGISTER_PBUF_STATUS,
407                core::ptr::from_mut(&mut st) as u64,
408                1,
409            )
410        };
411        if ret < 0 {
412            return Err(raw::errno_from_negative_syscall_ret(ret));
413        }
414        Ok(ProvidedBufferRingStatus { head: st.head })
415    }
416
417    /// Identifiant de groupe (`bgid`).
418    #[must_use]
419    pub fn group_id(&self) -> u16 {
420        self.bgid
421    }
422
423    /// Nombre de buffers actuellement disponibles (non checkout).
424    #[must_use]
425    pub fn available(&self) -> u16 {
426        self.available
427    }
428}
429
430// ───────────────────────────────────────────────────────────────────────────
431// Soumission avec sélection automatique
432// ───────────────────────────────────────────────────────────────────────────
433
434impl IoUring {
435    /// `IORING_OP_RECV` (27) avec **sélection automatique** de buffer dans
436    /// `group` (`IOSQE_BUFFER_SELECT`). Aucun buffer n'est attaché au slot S1.
437    /// `bundle` active `IORING_RECVSEND_BUNDLE` (plusieurs buffers en un recv).
438    /// Pénurie ⇒ complétion `-ENOBUFS`.
439    ///
440    /// # Errors
441    ///
442    /// [`Errno::EBUSY`] si la SQ ou le slab sont pleins ; à la complétion,
443    /// `-ENOBUFS` si le groupe est vide.
444    pub fn submit_receive_provided(
445        &mut self,
446        sock: BorrowedFd<'_>,
447        group: &ProvidedBufferRing,
448        flags: MessageFlags,
449        bundle: bool,
450    ) -> Result<SubmissionToken, Errno> {
451        let fd = sock.as_raw_fd();
452        let bgid = group.group_id();
453        let msg_flags = flags.bits().cast_unsigned();
454        self.pending_options = self.pending_options.buffer_select();
455        self.submit_op(None, |sqe| {
456            sqe.opcode = raw::IORING_OP_RECV;
457            sqe.fd = fd;
458            sqe.len = 0; // le kernel borne par la taille du buffer choisi
459            sqe.op_flags = msg_flags;
460            sqe.buf_index_or_group = bgid;
461            if bundle {
462                sqe.ioprio |= raw::IORING_RECVSEND_BUNDLE;
463            }
464        })
465    }
466
467    /// `IORING_OP_READ` (22) avec sélection automatique de buffer. `length`
468    /// borne le nombre d'octets lus (≤ taille du buffer). `bundle` active
469    /// `IORING_RECVSEND_BUNDLE`.
470    ///
471    /// # Errors
472    ///
473    /// Voir [`IoUring::submit_receive_provided`].
474    pub fn submit_read_provided(
475        &mut self,
476        fd: BorrowedFd<'_>,
477        group: &ProvidedBufferRing,
478        length: u32,
479        offset: Option<u64>,
480        bundle: bool,
481    ) -> Result<SubmissionToken, Errno> {
482        let fd = fd.as_raw_fd();
483        let bgid = group.group_id();
484        let off = offset.unwrap_or(u64::MAX);
485        self.pending_options = self.pending_options.buffer_select();
486        self.submit_op(None, |sqe| {
487            sqe.opcode = raw::IORING_OP_READ;
488            sqe.fd = fd;
489            sqe.len = length;
490            sqe.off_or_addr2 = off;
491            sqe.buf_index_or_group = bgid;
492            if bundle {
493                sqe.ioprio |= raw::IORING_RECVSEND_BUNDLE;
494            }
495        })
496    }
497}
498
499// ───────────────────────────────────────────────────────────────────────────
500// Consommation : le guard ProvidedBuffer
501// ───────────────────────────────────────────────────────────────────────────
502
503impl Completion {
504    /// Récupère le buffer **choisi par le kernel** pour cette complétion
505    /// (`CQE_F_BUFFER`). `None` si la complétion n'a pas consommé de buffer fourni
506    /// (op sans sélection, ou pénurie `-ENOBUFS`).
507    ///
508    /// L'emprunt `&'r mut ProvidedBufferRing` interdit le désenregistrement du
509    /// groupe tant que le buffer est en cours de traitement.
510    #[must_use]
511    pub fn into_provided_buffer<'r>(
512        self,
513        group: &'r mut ProvidedBufferRing,
514    ) -> Option<ProvidedBuffer<'r>> {
515        let bid = self.buffer_id()?;
516        let flags = self.flags();
517        // Longueur utile = `cqe->res` octets reçus, bornée à la taille du buffer.
518        let received = usize::try_from(self.raw_result()).unwrap_or(0);
519        let len = received.min(usize::try_from(group.buf_size).expect("u32 ⊆ usize"));
520        // Incrémental : `CQE_F_BUF_MORE` ⇒ le kernel garde le buffer (pas de
521        // réapprovisionnement à la libération du guard).
522        let replenish = !flags.contains(CompletionFlags::BUF_MORE);
523        group.checkout(bid);
524        Some(ProvidedBuffer {
525            group,
526            bid,
527            len,
528            replenish,
529        })
530    }
531}
532
533/// Accès RAII aux données reçues dans un buffer fourni. **Rend le buffer au
534/// groupe** (réapprovisionnement) à la libération — sauf en mode incrémental
535/// quand `CQE_F_BUF_MORE` est positionné (le kernel garde le buffer).
536///
537/// L'emprunt `&'r mut ProvidedBufferRing` empêche le désenregistrement du groupe
538/// tant que le guard vit (sûreté par construction).
539#[derive(Debug)]
540pub struct ProvidedBuffer<'r> {
541    /// Groupe propriétaire (emprunté mut → désenregistrement interdit).
542    group: &'r mut ProvidedBufferRing,
543    /// Id du buffer choisi par le kernel.
544    bid: u16,
545    /// Octets utiles reçus (`cqe->res`, borné à `buf_size`).
546    len: usize,
547    /// Réapprovisionner à la libération (faux si `CQE_F_BUF_MORE`).
548    replenish: bool,
549}
550
551impl ProvidedBuffer<'_> {
552    /// Id du buffer (16 bits hauts de `cqe->flags`).
553    #[must_use]
554    pub fn id(&self) -> u16 {
555        self.bid
556    }
557
558    /// Octets reçus (longueur = `cqe->res`).
559    #[must_use]
560    pub fn data(&self) -> &[u8] {
561        self.group.buffer_bytes(self.bid, self.len)
562    }
563
564    /// Octets reçus, en accès mutable.
565    #[must_use]
566    pub fn data_mut(&mut self) -> &mut [u8] {
567        self.group.buffer_bytes_mut(self.bid, self.len)
568    }
569}
570
571impl Drop for ProvidedBuffer<'_> {
572    fn drop(&mut self) {
573        if self.replenish {
574            self.group.return_buffer(self.bid);
575        }
576    }
577}
578
579// ───────────────────────────────────────────────────────────────────────────
580// Tests
581// ───────────────────────────────────────────────────────────────────────────
582//
583// Tous les tests touchent `io_uring_register`/`enter` (non modélisés par Miri) →
584// `#[cfg_attr(miri, ignore)]`. La logique du guard (réapprovisionnement,
585// incrémental `BUF_MORE`, branche `None`) est exercée **déterministiquement** via
586// des `Completion` **synthétiques** (champs construits dans ce module enfant),
587// sans dépendre du timing socket ni de la mise à `BUF_MORE` par le kernel. La
588// sûreté de lifetime (désenregistrement interdit sous un buffer vivant) est
589// prouvée par le doctest `compile_fail` sur `unregister`.
590
591#[cfg(test)]
592mod tests {
593    use super::syscall::sim::{self, Syscall};
594    use super::*;
595    use crate::io_uring::CompletionFlags;
596    use crate::io_uring::SubmissionToken;
597    use crate::test_support::{sfd, std_file};
598    use air_sys_types::fd::AsFd;
599    use std::io::Write;
600
601    fn ring8() -> IoUring {
602        IoUring::new(NonZeroU32::new(8).expect("8 ≠ 0")).expect("io_uring_setup 6.12")
603    }
604
605    fn nz16(n: u16) -> NonZeroU16 {
606        NonZeroU16::new(n).expect("n ≠ 0")
607    }
608
609    fn nz32(n: u32) -> NonZeroU32 {
610        NonZeroU32::new(n).expect("n ≠ 0")
611    }
612
613    fn tcp_pair() -> (std::net::TcpStream, std::net::TcpStream) {
614        let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind loopback");
615        let addr = listener.local_addr().expect("local_addr");
616        let client = std::net::TcpStream::connect(addr).expect("connect loopback");
617        let (server, _) = listener.accept().expect("accept loopback");
618        (client, server)
619    }
620
621    fn complete_one(ring: &mut IoUring, expected: SubmissionToken) -> Completion {
622        ring.submit_and_wait(1).expect("submit_and_wait");
623        let c = ring.wait_completion().expect("wait_completion");
624        assert_eq!(c.token(), expected, "user_data round-trip");
625        c
626    }
627
628    /// Construit une `Completion` synthétique (id de buffer + flags + res).
629    fn synth_completion(bid: u16, res: i32, buf_more: bool) -> Completion {
630        let mut raw_flags = CompletionFlags::BUFFER.bits() | (u32::from(bid) << 16);
631        if buf_more {
632            raw_flags |= CompletionFlags::BUF_MORE.bits();
633        }
634        Completion {
635            token: SubmissionToken::new(0, 0),
636            res,
637            raw_flags,
638            payload: None,
639            multishot: false,
640        }
641    }
642
643    // ── Déterministe : logique du guard (réappro, incrémental, None) ──────────
644
645    #[test]
646    #[cfg_attr(miri, ignore = "io_uring_register non supporté par Miri")]
647    fn guard_replenishes_on_drop_and_exposes_data() {
648        let mut ring = ring8();
649        let mut group =
650            ProvidedBufferRing::register(&mut ring, 7, nz16(4), nz32(64), Default::default())
651                .expect("register");
652        assert_eq!(group.group_id(), 7);
653        assert_eq!(group.available(), 4);
654        // Complétion synthétique : buffer 2, 5 octets reçus, pas d'incrémental.
655        let completion = synth_completion(2, 5, false);
656        {
657            let mut buf = completion
658                .into_provided_buffer(&mut group)
659                .expect("buffer présent");
660            assert_eq!(buf.id(), 2);
661            assert_eq!(buf.data().len(), 5);
662            buf.data_mut()[0] = 0xAB;
663            assert_eq!(buf.data()[0], 0xAB);
664        } // drop → réapprovisionne
665        assert_eq!(group.available(), 4, "réappro après libération du guard");
666        // checkout sans return (guard oublié via mem::forget) → available baisse.
667        let completion = synth_completion(0, 3, false);
668        let buf = completion
669            .into_provided_buffer(&mut group)
670            .expect("buffer présent");
671        core::mem::forget(buf); // pas de réappro
672        assert_eq!(group.available(), 3, "checkout non rendu");
673        group.unregister(&mut ring).expect("unregister");
674    }
675
676    #[test]
677    #[cfg_attr(miri, ignore = "io_uring_register non supporté par Miri")]
678    fn incremental_buf_more_does_not_replenish() {
679        let mut ring = ring8();
680        let mut group = ProvidedBufferRing::register(
681            &mut ring,
682            3,
683            nz16(2),
684            nz32(64),
685            ProvidedBufferRingOptions {
686                kernel_mmap: false,
687                incremental: true,
688            },
689        )
690        .expect("register incremental");
691        assert_eq!(group.available(), 2);
692        // BUF_MORE positionné ⇒ le guard ne réapprovisionne pas (kernel garde le buffer).
693        let completion = synth_completion(1, 8, true);
694        {
695            let buf = completion
696                .into_provided_buffer(&mut group)
697                .expect("buffer présent");
698            assert_eq!(buf.id(), 1);
699        } // drop → PAS de réappro (BUF_MORE)
700        assert_eq!(
701            group.available(),
702            1,
703            "incrémental : pas de réappro sur BUF_MORE"
704        );
705        // Complétion finale (sans BUF_MORE) ⇒ réappro.
706        let completion = synth_completion(1, 8, false);
707        drop(completion.into_provided_buffer(&mut group).expect("buffer"));
708        assert_eq!(group.available(), 2, "réappro à la complétion finale");
709        group.unregister(&mut ring).expect("unregister");
710    }
711
712    #[test]
713    #[cfg_attr(miri, ignore = "io_uring_register non supporté par Miri")]
714    fn into_provided_buffer_none_without_buffer_flag() {
715        let mut ring = ring8();
716        let mut group =
717            ProvidedBufferRing::register(&mut ring, 0, nz16(2), nz32(16), Default::default())
718                .expect("register");
719        // Complétion sans CQE_F_BUFFER (ex. -ENOBUFS) ⇒ pas de guard.
720        let completion = Completion {
721            token: SubmissionToken::new(0, 0),
722            res: -105, // -ENOBUFS
723            raw_flags: 0,
724            payload: None,
725            multishot: false,
726        };
727        assert!(completion.into_provided_buffer(&mut group).is_none());
728        assert_eq!(group.available(), 2, "available inchangé sans checkout");
729        group.unregister(&mut ring).expect("unregister");
730    }
731
732    // ── Intégration kernel : recv loopback + réapprovisionnement ──────────────
733
734    #[test]
735    #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
736    fn provided_recv_round_trip_and_replenish() {
737        let (mut client, server) = tcp_pair();
738        client.write_all(b"hello").expect("write");
739        client.flush().expect("flush");
740        std::thread::sleep(std::time::Duration::from_millis(20));
741        let mut ring = ring8();
742        let mut group =
743            ProvidedBufferRing::register(&mut ring, 1, nz16(4), nz32(64), Default::default())
744                .expect("register");
745        let tok = ring
746            .submit_receive_provided(sfd(&server), &group, MessageFlags::empty(), false)
747            .expect("submit recv_provided");
748        let completion = complete_one(&mut ring, tok);
749        let id;
750        {
751            let buf = completion
752                .into_provided_buffer(&mut group)
753                .expect("buffer choisi par le kernel");
754            id = buf.id();
755            assert!(id < 4);
756            assert_eq!(buf.data(), b"hello");
757            // (available() ne peut être lu tant que le guard emprunte `group`
758            //  — la décrémentation au checkout est vérifiée par le test `forget`.)
759        } // drop → réappro
760        assert_eq!(group.available(), 4, "réappro après traitement");
761        group.unregister(&mut ring).expect("unregister");
762        let _ = id;
763    }
764
765    #[test]
766    #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
767    fn provided_enobufs_then_replenish() {
768        let (mut client, server) = tcp_pair();
769        client.write_all(b"AAAAAAAABBBBBBBB").expect("write 16"); // 2 × 8
770        client.flush().expect("flush");
771        std::thread::sleep(std::time::Duration::from_millis(20));
772        let mut ring = ring8();
773        let mut group =
774            ProvidedBufferRing::register(&mut ring, 2, nz16(1), nz32(8), Default::default())
775                .expect("register count=1");
776        // Deux recv soumis avant traitement : 1 buffer ⇒ l'un OK, l'autre -ENOBUFS.
777        let t1 = ring
778            .submit_receive_provided(sfd(&server), &group, MessageFlags::empty(), false)
779            .expect("recv1");
780        let t2 = ring
781            .submit_receive_provided(sfd(&server), &group, MessageFlags::empty(), false)
782            .expect("recv2");
783        ring.submit_and_wait(2).expect("submit 2");
784        let mut ok = 0;
785        let mut enobufs = 0;
786        for _ in 0..2 {
787            let c = ring.wait_completion().expect("completion");
788            assert!(c.token() == t1 || c.token() == t2);
789            if c.flags().contains(CompletionFlags::BUFFER) {
790                let buf = c.into_provided_buffer(&mut group).expect("buffer");
791                assert_eq!(buf.data().len(), 8);
792                ok += 1;
793                drop(buf); // réappro
794            } else {
795                assert_eq!(c.into_result(), Err(Errno::ENOBUFS));
796                enobufs += 1;
797            }
798        }
799        assert_eq!((ok, enobufs), (1, 1), "1 servi, 1 en pénurie");
800        // Réapprovisionné : un nouveau recv obtient les octets restants.
801        let t3 = ring
802            .submit_receive_provided(sfd(&server), &group, MessageFlags::empty(), false)
803            .expect("recv3");
804        let c = complete_one(&mut ring, t3);
805        let buf = c
806            .into_provided_buffer(&mut group)
807            .expect("buffer après réappro");
808        assert_eq!(buf.data(), b"BBBBBBBB");
809        drop(buf);
810        group.unregister(&mut ring).expect("unregister");
811    }
812
813    #[test]
814    #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
815    fn provided_kernel_mmap_mode_round_trip() {
816        let (mut client, server) = tcp_pair();
817        client.write_all(b"viamap").expect("write");
818        client.flush().expect("flush");
819        std::thread::sleep(std::time::Duration::from_millis(20));
820        let mut ring = ring8();
821        let mut group = ProvidedBufferRing::register(
822            &mut ring,
823            5,
824            nz16(4),
825            nz32(64),
826            ProvidedBufferRingOptions {
827                kernel_mmap: true,
828                incremental: false,
829            },
830        )
831        .expect("register kernel_mmap");
832        let tok = ring
833            .submit_receive_provided(sfd(&server), &group, MessageFlags::empty(), false)
834            .expect("recv");
835        let c = complete_one(&mut ring, tok);
836        let buf = c.into_provided_buffer(&mut group).expect("buffer");
837        assert_eq!(buf.data(), b"viamap");
838        drop(buf);
839        group.unregister(&mut ring).expect("unregister");
840    }
841
842    #[test]
843    #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
844    fn provided_status_and_read_and_bundle() {
845        let mut ring = ring8();
846        let mut group =
847            ProvidedBufferRing::register(&mut ring, 9, nz16(4), nz32(64), Default::default())
848                .expect("register");
849        // status() : tête courante (0 au départ, aucun buffer consommé).
850        let status = group.status(&ring).expect("status");
851        assert_eq!(status.head, 0);
852        // read_provided sur un pipe alimenté.
853        let (read_end, write_end) = pipe_pair();
854        {
855            let mut w = std_file(write_end);
856            w.write_all(b"piped!").expect("write pipe");
857            w.flush().expect("flush");
858        }
859        let tok = ring
860            .submit_read_provided(read_end.as_fd(), &group, 64, None, false)
861            .expect("read_provided");
862        let c = complete_one(&mut ring, tok);
863        let buf = c.into_provided_buffer(&mut group).expect("buffer read");
864        assert_eq!(buf.data(), b"piped!");
865        drop(buf);
866        // read_provided **bundlé** (couvre la branche `bundle` du read) : se soumet
867        // et complète (le résultat exact dépend du kernel ; on n'asserte que le
868        // câblage de la façade).
869        let (read2, write2) = pipe_pair();
870        {
871            let mut w = std_file(write2);
872            w.write_all(b"rd2").expect("write pipe2");
873            w.flush().expect("flush");
874        }
875        let tok = ring
876            .submit_read_provided(read2.as_fd(), &group, 64, Some(0), true)
877            .expect("read_provided bundle");
878        let c = complete_one(&mut ring, tok);
879        if let Some(buf) = c.into_provided_buffer(&mut group) {
880            drop(buf);
881        }
882        // bundle : un recv bundlé se soumet et complète (socket loopback).
883        let (mut client, server) = tcp_pair();
884        client.write_all(b"bundled").expect("write");
885        client.flush().expect("flush");
886        std::thread::sleep(std::time::Duration::from_millis(20));
887        let tok = ring
888            .submit_receive_provided(sfd(&server), &group, MessageFlags::empty(), true)
889            .expect("recv bundle");
890        let c = complete_one(&mut ring, tok);
891        // Bundle : au moins un buffer consommé, octets présents.
892        let buf = c.into_provided_buffer(&mut group).expect("buffer bundle");
893        assert!(buf.data().starts_with(b"bundled") || !buf.data().is_empty());
894        drop(buf);
895        group.unregister(&mut ring).expect("unregister");
896    }
897
898    // ── Property-style : invariants checkout/return déterministes ─────────────
899
900    #[test]
901    #[cfg_attr(miri, ignore = "io_uring_register non supporté par Miri")]
902    fn checkout_return_invariants() {
903        let mut ring = ring8();
904        let count: u16 = 4;
905        let mut group =
906            ProvidedBufferRing::register(&mut ring, 0, nz16(count), nz32(32), Default::default())
907                .expect("register");
908        // Le modèle `&mut group` du guard sérialise le traitement : un buffer à la
909        // fois. On vérifie sur de nombreux cycles checkout→return (ids variés) que
910        // `available` reste dans `[0, count]` et revient toujours à `count`.
911        let mut returned: Vec<u16> = Vec::new();
912        for step in 0u16..256 {
913            assert!(group.available() <= count, "available ≤ count");
914            let bid = step % count;
915            let before = group.available();
916            // checkout (synthétique) → traitement → return (drop).
917            let c = synth_completion(bid, 4, false);
918            {
919                let buf = c.into_provided_buffer(&mut group).expect("buffer");
920                assert_eq!(buf.id(), bid, "id rendu == id demandé, jamais mélangé");
921            } // drop → réappro
922            // Cycle complet : available revient à sa valeur d'avant (aucune perte).
923            assert_eq!(group.available(), before, "aucun buffer perdu sur un cycle");
924            returned.push(bid);
925        }
926        assert_eq!(group.available(), count, "tout rendu en fin");
927        assert_eq!(returned.len(), 256);
928        group.unregister(&mut ring).expect("unregister");
929    }
930
931    // ── Chemins d'erreur (registre injecté) ───────────────────────────────────
932
933    #[test]
934    #[cfg_attr(miri, ignore = "io_uring_register non supporté par Miri")]
935    fn register_validates_and_errors_propagate() {
936        sim::clear();
937        let mut ring = ring8();
938        // count non puissance de 2 ⇒ EINVAL en amont (aucun syscall).
939        assert_eq!(
940            ProvidedBufferRing::register(&mut ring, 0, nz16(3), nz32(64), Default::default()).err(),
941            Some(Errno::EINVAL)
942        );
943        // register : erreur kernel injectée.
944        sim::inject(Syscall::Register, -22);
945        assert_eq!(
946            ProvidedBufferRing::register(&mut ring, 0, nz16(4), nz32(64), Default::default()).err(),
947            Some(Errno::EINVAL)
948        );
949        // Groupe réel, puis erreurs injectées sur status / unregister.
950        let group =
951            ProvidedBufferRing::register(&mut ring, 0, nz16(4), nz32(64), Default::default())
952                .expect("register");
953        sim::inject(Syscall::Register, -22);
954        assert_eq!(group.status(&ring).err(), Some(Errno::EINVAL));
955        sim::inject(Syscall::Register, -22);
956        assert_eq!(group.unregister(&mut ring).err(), Some(Errno::EINVAL));
957        sim::clear();
958    }
959
960    /// Crée une paire de pipe (lecture, écriture) via la famille `ipc`.
961    fn pipe_pair() -> (air_sys_types::fd::OwnedFd, air_sys_types::fd::OwnedFd) {
962        let (r, w) = crate::ipc::pipe2(air_sys_types::ipc::PipeFlags::empty()).expect("pipe2");
963        (r, w)
964    }
965}