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(®) 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(®) 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;