Preview of the proposed up-rust native-frame-model branch (up-rust b6b99c6d, up-spec f0e9b17) — not released documentation. branch · write-up

up_rust/zero_copy/
tx.rs

1/********************************************************************************
2 * Copyright (c) 2026 Contributors to the Eclipse Foundation
3 *
4 * See the NOTICE file(s) distributed with this work for additional
5 * information regarding copyright ownership.
6 *
7 * This program and the accompanying materials are made available under the
8 * terms of the Apache License Version 2.0 which is available at
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * SPDX-License-Identifier: Apache-2.0
12 ********************************************************************************/
13
14use super::*;
15
16/// Payload presence and layout requested for a transmit loan.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub enum UTxPayloadSpec {
19    /// The frame carries no payload and no payload encoding metadata.
20    Absent,
21    /// The frame carries a payload, including a present empty payload.
22    Present {
23        /// Payload length in bytes.
24        len: usize,
25        /// Required payload alignment.
26        alignment: PayloadAlignment,
27    },
28}
29
30/// Validated visible application payload alignment in bytes.
31///
32/// The value is always nonzero and a power of two. Runtime values from config,
33/// metadata, or transport boundaries must be constructed with [`Self::new`] or
34/// [`TryFrom<usize>`] so those boundaries keep their explicit validation.
35#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
36pub struct PayloadAlignment(usize);
37
38impl PayloadAlignment {
39    /// Alignment for absent payloads and byte-oriented payloads.
40    pub const ONE: Self = Self(1);
41
42    /// Creates a payload alignment after validating the nonzero power-of-two invariant.
43    ///
44    /// # Errors
45    ///
46    /// Returns an error when `value` is zero or not a power of two.
47    pub fn new(value: usize) -> Result<Self, UStatus> {
48        if value == 0 {
49            return Err(invalid_argument(
50                "payload alignment must be greater than zero",
51            ));
52        }
53        if !value.is_power_of_two() {
54            return Err(invalid_argument("payload alignment must be a power of two"));
55        }
56        Ok(Self(value))
57    }
58
59    /// Returns the raw alignment value in bytes.
60    #[must_use]
61    pub const fn as_usize(self) -> usize {
62        self.0
63    }
64}
65
66impl TryFrom<usize> for PayloadAlignment {
67    type Error = UStatus;
68
69    fn try_from(value: usize) -> Result<Self, Self::Error> {
70        Self::new(value)
71    }
72}
73
74impl UTxPayloadSpec {
75    /// Creates a present-payload spec from length and alignment.
76    ///
77    /// # Errors
78    ///
79    /// Returns an error if `alignment` is zero or not a power of two.
80    pub fn present(len: usize, alignment: usize) -> Result<Self, UStatus> {
81        let alignment = PayloadAlignment::new(alignment)?;
82        Ok(Self::present_with_alignment(len, alignment))
83    }
84
85    /// Creates a present-payload spec from length and a validated alignment.
86    #[must_use]
87    pub fn present_with_alignment(len: usize, alignment: PayloadAlignment) -> Self {
88        Self::Present { len, alignment }
89    }
90
91    /// Creates a present-empty-payload spec.
92    #[must_use]
93    pub fn present_empty() -> Self {
94        Self::Present {
95            len: 0,
96            alignment: PayloadAlignment::ONE,
97        }
98    }
99
100    /// Returns whether this spec represents a present payload.
101    #[must_use]
102    pub fn is_present(self) -> bool {
103        matches!(self, Self::Present { .. })
104    }
105
106    /// Returns the visible application payload length.
107    #[must_use]
108    pub fn len(self) -> usize {
109        match self {
110            Self::Absent => 0,
111            Self::Present { len, .. } => len,
112        }
113    }
114
115    /// Returns true if the visible application payload length is zero.
116    #[must_use]
117    pub fn is_empty(self) -> bool {
118        self.len() == 0
119    }
120
121    /// Returns the requested visible application payload alignment.
122    #[must_use]
123    pub fn alignment(self) -> usize {
124        self.payload_alignment().as_usize()
125    }
126
127    /// Returns the validated payload alignment proof for this spec.
128    #[must_use]
129    pub fn payload_alignment(self) -> PayloadAlignment {
130        match self {
131            Self::Absent => PayloadAlignment::ONE,
132            Self::Present { alignment, .. } => alignment,
133        }
134    }
135}
136
137/// Validated transport-independent transmit loan specification.
138#[derive(Clone, Debug, PartialEq)]
139#[must_use = "a transmit loan specification has no effect until passed to a loan method"]
140pub struct UTxLoanSpec<S = crate::Validated> {
141    metadata: UFrameMetadata,
142    payload: UTxPayloadSpec,
143    _state: core::marker::PhantomData<S>,
144}
145
146impl UTxLoanSpec<crate::Validated> {
147    /// Creates a validated transmit loan spec.
148    ///
149    /// # Errors
150    ///
151    /// Returns an error if metadata is invalid or if payload presence and payload
152    /// encoding metadata disagree.
153    pub(crate) fn new(metadata: UFrameMetadata, payload: UTxPayloadSpec) -> Result<Self, UStatus> {
154        UTxLoanSpec::new_unchecked(metadata, payload).validate()
155    }
156
157    /// Creates a no-payload transmit loan spec.
158    ///
159    /// # Errors
160    ///
161    /// Returns an error if the metadata is invalid or still carries payload encoding.
162    pub fn no_payload(metadata: UFrameMetadata) -> Result<Self, UStatus> {
163        Self::new(metadata, UTxPayloadSpec::Absent)
164    }
165
166    /// Creates a present-payload transmit loan spec.
167    ///
168    /// # Errors
169    ///
170    /// Returns an error if metadata is invalid, has no payload encoding, or if
171    /// the requested payload alignment is invalid.
172    pub fn payload(
173        metadata: UFrameMetadata,
174        payload_len: usize,
175        alignment: usize,
176    ) -> Result<Self, UStatus> {
177        Self::new(metadata, UTxPayloadSpec::present(payload_len, alignment)?)
178    }
179
180    /// Creates a present-empty-payload transmit loan spec.
181    ///
182    /// # Errors
183    ///
184    /// Returns an error if the metadata is invalid or has no payload encoding.
185    pub fn present_empty_payload(metadata: UFrameMetadata) -> Result<Self, UStatus> {
186        Self::new(metadata, UTxPayloadSpec::present_empty())
187    }
188}
189
190impl UTxLoanSpec<crate::Unvalidated> {
191    /// Creates a transmit loan spec without validation.
192    #[must_use = "validate the specification before loaning"]
193    pub fn new_unchecked(metadata: UFrameMetadata, payload: UTxPayloadSpec) -> Self {
194        Self {
195            metadata,
196            payload,
197            _state: core::marker::PhantomData,
198        }
199    }
200
201    /// Validates the spec, transitioning it to the [`Validated`](crate::Validated) state.
202    ///
203    /// # Errors
204    ///
205    /// Returns an error if metadata is invalid or if payload presence and
206    /// payload encoding metadata disagree.
207    pub fn validate(self) -> Result<UTxLoanSpec<crate::Validated>, UStatus> {
208        let spec = UTxLoanSpec {
209            metadata: self.metadata,
210            payload: self.payload,
211            _state: core::marker::PhantomData,
212        };
213        validate_tx_loan_spec(&spec)?;
214        Ok(spec)
215    }
216}
217
218impl<S> UTxLoanSpec<S> {
219    /// Returns the immutable frame metadata associated with this loan.
220    #[must_use]
221    pub fn metadata(&self) -> &UFrameMetadata {
222        &self.metadata
223    }
224
225    /// Consumes the spec and returns its metadata.
226    #[must_use]
227    pub fn into_metadata(self) -> UFrameMetadata {
228        self.metadata
229    }
230
231    /// Returns the payload presence and layout spec.
232    #[must_use]
233    pub fn payload_spec(&self) -> UTxPayloadSpec {
234        self.payload
235    }
236
237    /// Returns whether the transmit frame carries a payload.
238    #[must_use]
239    pub fn has_payload(&self) -> bool {
240        self.payload.is_present()
241    }
242
243    /// Returns the visible application payload length.
244    #[must_use]
245    pub fn payload_len(&self) -> usize {
246        self.payload.len()
247    }
248
249    /// Returns the requested visible application payload alignment.
250    #[must_use]
251    pub fn payload_alignment(&self) -> usize {
252        self.payload.alignment()
253    }
254
255    /// Returns the validated payload alignment proof.
256    #[must_use]
257    pub fn payload_alignment_proof(&self) -> PayloadAlignment {
258        self.payload.payload_alignment()
259    }
260}
261
262/// *Role: transmit storage a zero-copy transport lends; write the payload in place, then commit — see the [trait map](crate::guide::trait_map).*
263///
264/// Mutable transmit storage reserved from a zero-copy transport.
265pub trait UTxBuffer {
266    /// Returns the immutable frame metadata associated with this transmit loan.
267    fn metadata(&self) -> &UFrameMetadata;
268
269    /// Returns the current payload bytes in the transmit loan.
270    fn payload(&self) -> &[u8];
271
272    /// Returns mutable payload storage for direct serialization into the loan.
273    fn payload_mut(&mut self) -> &mut [u8];
274}
275
276/// *Role: an uninitialized transmit loan; filled safely via the typed init API — see the [trait map](crate::guide::trait_map).*
277///
278/// Mutable transmit storage whose application payload bytes are not yet initialized.
279pub trait UUninitTxBuffer {
280    /// Initialized transmit loan type produced after payload initialization.
281    type Initialized: UTxBuffer;
282
283    /// Returns the immutable frame metadata associated with this transmit loan.
284    fn metadata(&self) -> &UFrameMetadata;
285
286    /// Returns the visible application payload length.
287    fn payload_len(&self) -> usize;
288
289    /// Returns mutable uninitialized application payload storage.
290    fn payload_uninit_mut(&mut self) -> &mut [MaybeUninit<u8>];
291
292    /// Converts this uninitialized loan into its initialized TX buffer form.
293    ///
294    /// # Safety
295    ///
296    /// The caller must guarantee every visible application payload byte has been
297    /// initialized before conversion.
298    unsafe fn assume_payload_init(self) -> Self::Initialized;
299}
300
301/// Mutable uninitialized payload bytes with explicit transport-loan provenance.
302pub struct LoanedPayloadUninitMut<'a> {
303    bytes: &'a mut [MaybeUninit<u8>],
304    provenance: PayloadLoanProvenance,
305}
306
307impl<'a> core::fmt::Debug for LoanedPayloadUninitMut<'a> {
308    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
309        f.debug_struct("LoanedPayloadUninitMut")
310            .finish_non_exhaustive()
311    }
312}
313
314impl<'a> LoanedPayloadUninitMut<'a> {
315    /// Creates a mutable uninitialized loaned payload view from transport-owned storage.
316    ///
317    /// # Safety
318    ///
319    /// `bytes` must be a valid mutable uninitialized byte slice for `'a`, with no
320    /// other active access path, and must be the exact visible application
321    /// payload range for the loan described by `provenance`.
322    #[must_use]
323    pub unsafe fn new_unchecked(
324        bytes: &'a mut [MaybeUninit<u8>],
325        provenance: PayloadLoanProvenance,
326    ) -> Self {
327        Self { bytes, provenance }
328    }
329
330    #[must_use]
331    /// Returns where this loan's storage came from.
332    pub fn provenance(&self) -> PayloadLoanProvenance {
333        self.provenance
334    }
335
336    #[must_use]
337    /// Returns the payload capacity of this loan in bytes.
338    pub fn len(&self) -> usize {
339        self.bytes.len()
340    }
341
342    #[must_use]
343    /// Returns `true` if the loan has zero payload capacity.
344    pub fn is_empty(&self) -> bool {
345        self.bytes.is_empty()
346    }
347
348    #[cfg(feature = "zero-copy-transport")]
349    pub(crate) fn as_uninit_bytes_mut_internal(&mut self) -> &mut [MaybeUninit<u8>] {
350        self.bytes
351    }
352
353    #[cfg(feature = "zero-copy-transport")]
354    pub(crate) fn into_uninit_bytes_mut_internal(self) -> &'a mut [MaybeUninit<u8>] {
355        self.bytes
356    }
357}
358
359/// Verifies the visible transmit payload layout exposed by a zero-copy loan.
360///
361/// # Errors
362///
363/// Returns an error if the requested layout is invalid or the exposed payload
364/// range does not match it.
365pub fn verify_tx_buffer_payload_layout(
366    buffer: &mut impl UTxBuffer,
367    payload_len: usize,
368    alignment: usize,
369) -> Result<(), UStatus> {
370    validate_payload_layout(payload_len, alignment)?;
371    let payload = buffer.payload_mut();
372    verify_payload_slice_layout(payload, payload_len, alignment)
373}
374
375/// Verifies the visible uninitialized transmit payload layout exposed by a loan.
376///
377/// # Errors
378///
379/// Returns an error if the requested layout is invalid or the exposed payload
380/// range does not match it.
381pub fn verify_uninit_tx_buffer_payload_layout(
382    buffer: &mut impl UUninitTxBuffer,
383    payload_len: usize,
384    alignment: usize,
385) -> Result<(), UStatus> {
386    validate_payload_layout(payload_len, alignment)?;
387    let payload = buffer.payload_uninit_mut();
388    if payload.len() != payload_len {
389        return Err(internal_zero_copy_error(format!(
390            "transport returned uninitialized TX payload length {} but {payload_len} was requested",
391            payload.len()
392        )));
393    }
394    if !payload.is_empty() && !(payload.as_ptr() as usize).is_multiple_of(alignment) {
395        return Err(internal_zero_copy_error(format!(
396            "transport returned uninitialized TX payload alignment {} but {alignment} was requested",
397            payload.as_ptr() as usize
398        )));
399    }
400    Ok(())
401}
402/// Validates a transmit buffer before committing it through a public zero-copy boundary.
403///
404/// # Errors
405///
406/// Returns an error if metadata is invalid or initialized payload bytes disagree
407/// with payload encoding metadata.
408pub fn validate_tx_buffer_for_transport(
409    buffer: &mut (impl UTxBuffer + ?Sized),
410) -> Result<(), UStatus> {
411    validate_metadata(buffer.metadata())?;
412    if !buffer.payload().is_empty() && buffer.metadata().payload_encoding().is_none() {
413        return Err(invalid_argument(
414            "TX buffer carries payload bytes without payload encoding",
415        ));
416    }
417    Ok(())
418}