-
Notifications
You must be signed in to change notification settings - Fork 286
feat: add ancillary data (control message) helpers for sendmsg/recvmsg #645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
seuros
wants to merge
1
commit into
rust-lang:master
Choose a base branch
from
seuros:ancillary-data-cmsg
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+430
−5
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,7 +38,7 @@ jobs: | |
| os: windows-latest | ||
| rust: stable | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@v6 | ||
| - uses: dtolnay/rust-toolchain@master | ||
| with: | ||
| toolchain: ${{ matrix.rust }} | ||
|
|
@@ -52,7 +52,7 @@ jobs: | |
| name: Rustfmt | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@v6 | ||
| - uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
| components: rustfmt | ||
|
|
@@ -110,7 +110,7 @@ jobs: | |
| - x86_64-unknown-redox | ||
| - wasm32-wasip2 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@v6 | ||
| - uses: dtolnay/rust-toolchain@nightly | ||
| with: | ||
| components: rust-src | ||
|
|
@@ -119,11 +119,44 @@ jobs: | |
| run: cargo hack check -Z build-std=std,panic_abort --feature-powerset --target ${{ matrix.target }} | ||
| - name: Check docs | ||
| run: RUSTDOCFLAGS="-D warnings --cfg docsrs" cargo doc -Z build-std=std,panic_abort --no-deps --all-features --target ${{ matrix.target }} | ||
| Cross: | ||
| name: Cross-test (${{ matrix.target }}) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this? Isn't |
||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| # 32-bit Linux: size_t=4 → cmsg_len is 4 bytes, CMSG_ALIGN factor=4. | ||
| # Exercises a different CMSG_* layout than x86_64 (factor=8). | ||
| - target: i686-unknown-linux-gnu | ||
| rust: stable | ||
| # 64-bit ARM Linux: same CMSG_ALIGN factor as x86_64 but different ABI. | ||
| - target: aarch64-unknown-linux-gnu | ||
| rust: stable | ||
| # 32-bit ARM Linux: like i686 but a distinct architecture. | ||
| - target: armv7-unknown-linux-gnueabihf | ||
| rust: stable | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - uses: dtolnay/rust-toolchain@master | ||
| with: | ||
| toolchain: ${{ matrix.rust }} | ||
| targets: ${{ matrix.target }} | ||
| - uses: taiki-e/install-action@cross | ||
| - name: Run cmsg tests (cross + QEMU) | ||
| run: | | ||
| cross test --target ${{ matrix.target }} --all-features -- cmsg | ||
| cross test --target ${{ matrix.target }} --all-features -- control_message | ||
| - name: Run cmsg tests release (cross + QEMU) | ||
| run: | | ||
| cross test --target ${{ matrix.target }} --all-features --release -- cmsg | ||
| cross test --target ${{ matrix.target }} --all-features --release -- control_message | ||
|
|
||
| Clippy: | ||
| name: Clippy | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@v6 | ||
| - uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
| components: clippy | ||
|
|
@@ -144,7 +177,7 @@ jobs: | |
| # the README for details: https://github.com/awslabs/cargo-check-external-types | ||
| - nightly-2024-06-30 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@v6 | ||
| - name: Install Rust ${{ matrix.rust }} | ||
| uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| use std::fmt; | ||
| use std::mem; | ||
|
|
||
| /// Returns the space required in a control message buffer for a single message | ||
| /// with `data_len` bytes of ancillary data. | ||
| /// | ||
| /// Returns `None` if `data_len` does not fit in `libc::c_uint`. | ||
| /// | ||
| /// Corresponds to `CMSG_SPACE(3)`. | ||
| pub fn cmsg_space(data_len: usize) -> Option<usize> { | ||
| let len = libc::c_uint::try_from(data_len).ok()?; | ||
| // SAFETY: pure arithmetic. | ||
| usize::try_from(unsafe { libc::CMSG_SPACE(len) }).ok() | ||
| } | ||
|
|
||
| /// A control message parsed from a `recvmsg(2)` control buffer. | ||
| /// | ||
| /// Returned by [`ControlMessages`]. | ||
| pub struct ControlMessage<'a> { | ||
| cmsg_level: i32, | ||
| cmsg_type: i32, | ||
| data: &'a [u8], | ||
| } | ||
|
|
||
| impl<'a> ControlMessage<'a> { | ||
| /// Corresponds to `cmsg_level` in `cmsghdr`. | ||
| pub fn cmsg_level(&self) -> i32 { | ||
| self.cmsg_level | ||
| } | ||
|
|
||
| /// Corresponds to `cmsg_type` in `cmsghdr`. | ||
| pub fn cmsg_type(&self) -> i32 { | ||
| self.cmsg_type | ||
| } | ||
|
|
||
| /// The ancillary data payload. | ||
| /// | ||
| /// Corresponds to the data portion following the `cmsghdr`. | ||
| pub fn data(&self) -> &'a [u8] { | ||
| self.data | ||
| } | ||
| } | ||
|
|
||
| impl<'a> fmt::Debug for ControlMessage<'a> { | ||
| fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| "ControlMessage".fmt(fmt) | ||
| } | ||
| } | ||
|
|
||
| /// Iterator over control messages in a `recvmsg(2)` control buffer. | ||
| /// | ||
| /// See [`crate::MsgHdrMut::with_control`] and [`crate::MsgHdrMut::control_len`]. | ||
| pub struct ControlMessages<'a> { | ||
| buf: &'a [u8], | ||
| offset: usize, | ||
| } | ||
|
|
||
| impl<'a> ControlMessages<'a> { | ||
| /// Create a new `ControlMessages` from the filled control buffer. | ||
| /// | ||
| /// Pass `&raw_buf[..msg.control_len()]` where `raw_buf` is the slice | ||
| /// passed to [`crate::MsgHdrMut::with_control`] before calling `recvmsg(2)`. | ||
| pub fn new(buf: &'a [u8]) -> Self { | ||
| Self { buf, offset: 0 } | ||
| } | ||
| } | ||
|
|
||
| impl<'a> Iterator for ControlMessages<'a> { | ||
| type Item = ControlMessage<'a>; | ||
|
|
||
| #[allow(clippy::useless_conversion)] | ||
| fn next(&mut self) -> Option<Self::Item> { | ||
| let hdr_size = mem::size_of::<libc::cmsghdr>(); | ||
| // SAFETY: pure arithmetic; gives CMSG_ALIGN(sizeof(cmsghdr)). | ||
| let data_offset: usize = | ||
| usize::try_from(unsafe { libc::CMSG_LEN(0) }).unwrap_or(usize::MAX); | ||
|
|
||
| if self.offset + hdr_size > self.buf.len() { | ||
| return None; | ||
| } | ||
|
|
||
| // SAFETY: range is within `buf`; read_unaligned handles any alignment. | ||
| let cmsg: libc::cmsghdr = unsafe { | ||
| std::ptr::read_unaligned(self.buf.as_ptr().add(self.offset) as *const libc::cmsghdr) | ||
| }; | ||
|
|
||
| let total_len = usize::try_from(cmsg.cmsg_len).unwrap_or(0); | ||
| if total_len < data_offset { | ||
| return None; | ||
| } | ||
| let data_len = total_len - data_offset; | ||
|
|
||
| let data_abs_start = self.offset + data_offset; | ||
| let data_abs_end = data_abs_start.saturating_add(data_len); | ||
| if data_abs_end > self.buf.len() { | ||
| return None; | ||
| } | ||
|
|
||
| let item = ControlMessage { | ||
| cmsg_level: cmsg.cmsg_level, | ||
| cmsg_type: cmsg.cmsg_type, | ||
| data: &self.buf[data_abs_start..data_abs_end], | ||
| }; | ||
|
|
||
| // SAFETY: pure arithmetic; CMSG_SPACE(data_len) == CMSG_ALIGN(total_len). | ||
| let advance = match libc::c_uint::try_from(data_len) { | ||
| Ok(dl) => usize::try_from(unsafe { libc::CMSG_SPACE(dl) }).unwrap_or(usize::MAX), | ||
| Err(_) => return None, | ||
| }; | ||
| self.offset = self.offset.saturating_add(advance); | ||
|
|
||
| Some(item) | ||
| } | ||
| } | ||
|
|
||
| impl<'a> fmt::Debug for ControlMessages<'a> { | ||
| fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| "ControlMessages".fmt(fmt) | ||
| } | ||
| } | ||
|
|
||
| /// Builds a control message buffer for use with `sendmsg(2)`. | ||
| /// | ||
| /// See [`crate::MsgHdr::with_control`] and [`cmsg_space`]. | ||
| pub struct ControlMessageEncoder<'a> { | ||
| buf: &'a mut [u8], | ||
| len: usize, | ||
| } | ||
|
|
||
| impl<'a> ControlMessageEncoder<'a> { | ||
| /// Create a new `ControlMessageEncoder` backed by `buf`. | ||
| /// | ||
| /// Zeroes `buf` on creation to ensure padding bytes are clean. | ||
| /// Allocate `buf` with the sum of [`cmsg_space`] for each intended message. | ||
| pub fn new(buf: &'a mut [u8]) -> Self { | ||
| buf.fill(0); | ||
| Self { buf, len: 0 } | ||
| } | ||
|
|
||
| /// Append a control message carrying `data`. | ||
| /// | ||
| /// Returns `Err` if `data` exceeds `c_uint::MAX` or the buffer is too small. | ||
| pub fn push(&mut self, cmsg_level: i32, cmsg_type: i32, data: &[u8]) -> std::io::Result<()> { | ||
| let data_len_uint = libc::c_uint::try_from(data.len()).map_err(|_| { | ||
| std::io::Error::new( | ||
| std::io::ErrorKind::InvalidInput, | ||
| "ancillary data payload too large (exceeds c_uint::MAX)", | ||
| ) | ||
| })?; | ||
| // SAFETY: pure arithmetic. | ||
| let space: usize = | ||
| usize::try_from(unsafe { libc::CMSG_SPACE(data_len_uint) }).unwrap_or(usize::MAX); | ||
| if self.len + space > self.buf.len() { | ||
| return Err(std::io::Error::new( | ||
| std::io::ErrorKind::InvalidInput, | ||
| "control message buffer too small", | ||
| )); | ||
| } | ||
| // SAFETY: pure arithmetic. | ||
| let cmsg_len = unsafe { libc::CMSG_LEN(data_len_uint) }; | ||
| unsafe { | ||
| // SAFETY: offset is within buf; write_unaligned handles alignment 1. | ||
| // Use zeroed() + field assignment to handle platform-specific padding | ||
| // (e.g. musl adds __pad1); buf is pre-zeroed but the write must be | ||
| // self-contained for correctness. | ||
| let cmsg_ptr = self.buf.as_mut_ptr().add(self.len) as *mut libc::cmsghdr; | ||
| let mut hdr: libc::cmsghdr = mem::zeroed(); | ||
| hdr.cmsg_len = cmsg_len as _; | ||
| hdr.cmsg_level = cmsg_level; | ||
| hdr.cmsg_type = cmsg_type; | ||
| std::ptr::write_unaligned(cmsg_ptr, hdr); | ||
| // SAFETY: CMSG_DATA gives the correct offset past alignment padding. | ||
| let data_ptr = libc::CMSG_DATA(cmsg_ptr); | ||
| std::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len()); | ||
| } | ||
| self.len += space; | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Returns the encoded bytes. | ||
| /// | ||
| /// Corresponds to the slice to pass to [`crate::MsgHdr::with_control`]. | ||
| pub fn as_bytes(&self) -> &[u8] { | ||
| &self.buf[..self.len] | ||
| } | ||
|
|
||
| /// Returns the number of bytes written. | ||
| pub fn len(&self) -> usize { | ||
| self.len | ||
| } | ||
|
|
||
| /// Returns `true` if no control messages have been pushed. | ||
| pub fn is_empty(&self) -> bool { | ||
| self.len == 0 | ||
| } | ||
| } | ||
|
|
||
| impl<'a> fmt::Debug for ControlMessageEncoder<'a> { | ||
| fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| "ControlMessageEncoder".fmt(fmt) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you drop the CI changes from this pr? Let's focus this pr on one thing.