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}