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

up_rust/frame/
view.rs

1/********************************************************************************
2 * Copyright (c) 2023 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
14//! Family-neutral frame views.
15//!
16//! [`UFrameView`] is the shared vocabulary for reading a validated frame's
17//! metadata and ordered payload bytes. Both experimental transport families
18//! (owned frames and zero-copy leases) expose their received frames through
19//! it, and [`validate_frame_view_for_transport`] is the shared boundary check
20//! a transport runs before handing a frame to the caller.
21
22#[cfg(feature = "owned-frame-transport")]
23use std::io::Cursor;
24use std::io::Read;
25
26use crate::frame::metadata::{UFrameMetadata, UFrameMetadataError};
27use crate::payload::codec::{PayloadCodec, ReadDecodePayload};
28use crate::payload::UWireError;
29#[cfg(feature = "owned-frame-transport")]
30use crate::UOwnedFrame;
31use crate::{UCode, UStatus};
32
33fn invalid_argument(message: impl Into<String>) -> UStatus {
34    UStatus::fail_with_code(UCode::InvalidArgument, message)
35}
36
37fn internal_view_error(message: impl Into<String>) -> UStatus {
38    UStatus::fail_with_code(UCode::Internal, message)
39}
40
41pub(crate) fn validate_metadata(metadata: &UFrameMetadata) -> Result<(), UStatus> {
42    metadata.validate().map_err(frame_metadata_error)
43}
44
45pub(crate) fn frame_metadata_error(error: UFrameMetadataError) -> UStatus {
46    invalid_argument(format!("invalid frame metadata: {error}"))
47}
48
49pub(crate) fn validate_payload_presence(
50    has_payload: bool,
51    has_encoding: bool,
52    context: &str,
53) -> Result<(), UStatus> {
54    match (has_payload, has_encoding) {
55        (true, true) | (false, false) => Ok(()),
56        (true, false) => Err(invalid_argument(format!(
57            "{context} carries payload bytes without payload encoding"
58        ))),
59        (false, true) => Err(invalid_argument(format!(
60            "{context} carries payload encoding without payload bytes"
61        ))),
62    }
63}
64
65/// *Role: the neutral read vocabulary every received frame speaks, whatever the family; implemented by frames and leases, consumed by decoding code — see the [trait map](crate::guide::trait_map).*
66///
67/// Neutral view of frame metadata plus ordered payload bytes.
68pub trait UFrameView {
69    /// Reader over the payload bytes in order.
70    type PayloadReader<'a>: Read + 'a
71    where
72        Self: 'a;
73    /// Iterator over the payload's storage segments in order.
74    type PayloadSlices<'a>: Iterator<Item = &'a [u8]> + 'a
75    where
76        Self: 'a;
77
78    /// Returns the native frame metadata.
79    fn metadata(&self) -> &UFrameMetadata;
80
81    /// Returns the number of application payload bytes visible through this view.
82    fn payload_len(&self) -> usize;
83
84    /// Returns whether this view carries a payload, including a present empty payload.
85    fn has_payload(&self) -> bool {
86        self.payload_len() > 0 || self.metadata().payload_encoding().is_some()
87    }
88
89    /// Returns an ordered reader over the application payload bytes.
90    fn payload_reader(&self) -> Self::PayloadReader<'_>;
91
92    /// Returns ordered borrowed payload slices.
93    fn payload_slices(&self) -> Self::PayloadSlices<'_>;
94
95    /// Returns a contiguous borrowed payload view when this view can provide one without copying.
96    fn try_contiguous_payload(&self) -> Option<&[u8]> {
97        None
98    }
99
100    /// Decodes this frame view from its ordered payload reader with codec `C`.
101    ///
102    /// This path works for both contiguous and segmented receive storage without
103    /// forcing a coalescing copy before the selected codec sees the bytes.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if the frame has no payload, has missing or incompatible
108    /// encoding metadata, or if the codec cannot decode the payload bytes.
109    fn decode_payload_from_reader_as<C, T>(&self) -> Result<T, UWireError>
110    where
111        C: PayloadCodec + ReadDecodePayload<T>,
112    {
113        C::verify_encoding(self.metadata().payload_encoding())?;
114        if !self.has_payload() {
115            return Err(UWireError::MissingPayload);
116        }
117        C::decode_payload_from_reader(self.payload_reader(), self.payload_len())
118    }
119}
120
121#[cfg(feature = "owned-frame-transport")]
122impl UFrameView for UOwnedFrame {
123    type PayloadReader<'a>
124        = Cursor<&'a [u8]>
125    where
126        Self: 'a;
127    type PayloadSlices<'a>
128        = std::option::IntoIter<&'a [u8]>
129    where
130        Self: 'a;
131
132    fn metadata(&self) -> &UFrameMetadata {
133        self.metadata()
134    }
135
136    fn payload_len(&self) -> usize {
137        self.payload_bytes().len()
138    }
139
140    fn has_payload(&self) -> bool {
141        UOwnedFrame::has_payload(self)
142    }
143
144    fn payload_reader(&self) -> Self::PayloadReader<'_> {
145        Cursor::new(self.payload_bytes())
146    }
147
148    fn payload_slices(&self) -> Self::PayloadSlices<'_> {
149        self.payload().map(bytes::Bytes::as_ref).into_iter()
150    }
151
152    fn try_contiguous_payload(&self) -> Option<&[u8]> {
153        self.payload().map(bytes::Bytes::as_ref)
154    }
155}
156
157/// Validates a frame view before returning it from a public zero-copy boundary.
158///
159/// # Errors
160///
161/// Returns an error if metadata is invalid, payload presence disagrees with
162/// payload encoding, or ordered slices do not match `payload_len`.
163pub fn validate_frame_view_for_transport(
164    frame: &(impl UFrameView + ?Sized),
165) -> Result<(), UStatus> {
166    validate_metadata(frame.metadata())?;
167    validate_payload_presence(
168        frame.has_payload(),
169        frame.metadata().payload_encoding().is_some(),
170        "frame view",
171    )?;
172    let mut observed = 0_usize;
173    for slice in frame.payload_slices() {
174        observed = observed
175            .checked_add(slice.len())
176            .ok_or_else(|| internal_view_error("frame view payload slices overflow usize"))?;
177    }
178    if observed != frame.payload_len() {
179        return Err(internal_view_error(format!(
180            "frame view payload slices yielded {observed} bytes but payload_len returned {}",
181            frame.payload_len()
182        )));
183    }
184    Ok(())
185}