Skip to content

Commit dba9672

Browse files
committed
types: add RecvMsgOut helper structure
This introduces a `RecvMsgOut` structure which allows consumers to safely parse the buffered result from a multishot `IORING_OP_RECVMSG` completion event.
1 parent 49c200b commit dba9672

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed

src/types.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,134 @@ impl DestinationSlot {
330330
self.dest.get()
331331
}
332332
}
333+
334+
/// Helper structure for parsing the result of a multishot [`opcode::RecvMsg`](crate::opcode::RecvMsg).
335+
#[derive(Debug)]
336+
pub struct RecvMsgOut<'buf> {
337+
buf: &'buf [u8],
338+
header: sys::io_uring_recvmsg_out,
339+
/// The fixed length of the name field, in bytes.
340+
///
341+
/// If the incoming name data is larger than this, it gets truncated to this.
342+
/// If it is smaller, it gets 0-padded to fill the whole field. In either case,
343+
/// this fixed amount of space is reserved in the result buffer.
344+
msghdr_name_len: usize,
345+
/// The fixed length of the control field, in bytes.
346+
///
347+
/// This follows the same semantics as the field above, but for control data.
348+
msghdr_control_len: usize,
349+
}
350+
351+
impl<'buf> RecvMsgOut<'buf> {
352+
const DATA_START: usize = std::mem::size_of::<sys::io_uring_recvmsg_out>();
353+
354+
/// Parse the data buffered upon completion of a `RecvMsg` multishot operation.
355+
///
356+
/// `buffer` is the whole buffer previously provided to the ring, while `msghdr`
357+
/// is the same content provided as input to the corresponding SQE
358+
/// (only `msg_namelen` and `msg_controllen` fields are relevant).
359+
pub fn parse(buffer: &'buf [u8], msghdr: &libc::msghdr) -> Result<Self, ()> {
360+
if buffer.len() < std::mem::size_of::<sys::io_uring_recvmsg_out>() {
361+
return Err(());
362+
}
363+
// SAFETY: buffer (minimum) length is checked here above.
364+
let header: sys::io_uring_recvmsg_out =
365+
unsafe { std::ptr::read_unaligned(buffer.as_ptr() as _) };
366+
367+
let msghdr_name_len = msghdr.msg_namelen as _;
368+
let msghdr_control_len = msghdr.msg_controllen as _;
369+
370+
// Check total length upfront, so that all the methods further
371+
// below can safely use unchecked/saturating math.
372+
let length_overflow = Some(Self::DATA_START)
373+
.and_then(|acc| acc.checked_add(msghdr_name_len))
374+
.and_then(|acc| acc.checked_add(msghdr_control_len))
375+
.and_then(|acc| acc.checked_add(header.payloadlen as usize))
376+
.map(|total_len| total_len > buffer.len())
377+
.unwrap_or(true);
378+
if length_overflow {
379+
return Err(());
380+
}
381+
382+
Ok(Self {
383+
buf: buffer,
384+
header,
385+
msghdr_name_len,
386+
msghdr_control_len,
387+
})
388+
}
389+
390+
/// Return the length of the incoming `name` data.
391+
///
392+
/// This may be larger than the size of the content returned by
393+
/// `name_data()`, if the kernel could not fit all the incoming
394+
/// data in the provided buffer size. In that case, name data in
395+
/// the result buffer gets truncated.
396+
pub fn incoming_name_len(&self) -> u32 {
397+
self.header.namelen
398+
}
399+
400+
/// Return whether the incoming name data was larger than the provided limit/buffer.
401+
///
402+
/// When `true`, data returned by `name_data()` is truncated and
403+
/// incomplete.
404+
pub fn is_name_data_truncated(&self) -> bool {
405+
self.header.namelen as usize > self.msghdr_name_len
406+
}
407+
408+
/// Message control data, with the same semantics as `msghdr.msg_control`.
409+
pub fn name_data(&self) -> &[u8] {
410+
let name_start = Self::DATA_START;
411+
let name_size = usize::min(self.header.namelen as usize, self.msghdr_name_len);
412+
let name_end = name_start.saturating_add(name_size);
413+
&self.buf[name_start..name_end]
414+
}
415+
416+
/// Return the length of the incoming `control` data.
417+
///
418+
/// This may be larger than the size of the content returned by
419+
/// `control_data()`, if the kernel could not fit all the incoming
420+
/// data in the provided buffer size. In that case, control data in
421+
/// the result buffer gets truncated.
422+
pub fn incoming_control_len(&self) -> u32 {
423+
self.header.controllen
424+
}
425+
426+
/// Return whether the incoming control data was larger than the provided limit/buffer.
427+
///
428+
/// When `true`, data returned by `control_data()` is truncated and
429+
/// incomplete.
430+
pub fn is_control_data_truncated(&self) -> bool {
431+
self.header.controllen as usize > self.msghdr_control_len
432+
}
433+
434+
/// Message control data, with the same semantics as `msghdr.msg_control`.
435+
pub fn control_data(&self) -> &[u8] {
436+
let control_start = Self::DATA_START.saturating_add(self.msghdr_name_len);
437+
let control_size = usize::min(self.header.controllen as usize, self.msghdr_control_len);
438+
let control_end = control_start.saturating_add(control_size);
439+
&self.buf[control_start..control_end]
440+
}
441+
442+
/// Return whether the incoming payload was larger than the provided limit/buffer.
443+
///
444+
/// When `true`, data returned by `payload_data()` is truncated and
445+
/// incomplete.
446+
pub fn is_payload_truncated(&self) -> bool {
447+
self.header.flags & (libc::MSG_TRUNC as u32) != 0
448+
}
449+
450+
/// Message payload, as buffered by the kernel.
451+
pub fn payload_data(&self) -> &[u8] {
452+
let payload_start = Self::DATA_START
453+
.saturating_add(self.msghdr_name_len)
454+
.saturating_add(self.msghdr_control_len);
455+
let payload_end = payload_start.saturating_add(self.header.payloadlen as usize);
456+
&self.buf[payload_start..payload_end]
457+
}
458+
459+
/// Message flags, with the same semantics as `msghdr.msg_flags`.
460+
pub fn flags(&self) -> u32 {
461+
self.header.flags
462+
}
463+
}

0 commit comments

Comments
 (0)