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

up_rust/
wire.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
14#![cfg_attr(not(feature = "wire-implementer-api"), allow(dead_code))]
15
16//! Selected-wire identities, payload codecs, and metadata profiles.
17//!
18//! Wire authors define a [`UWire`] marker and stable [`WireIdentity`], associate
19//! payload types through [`UWirePayload`], and implement the codec traits the
20//! payload family requires. Physical carriage does not belong here. The
21//! adapter in `wire_transport` composes a wire and metadata codec over any
22//! compatible encoded core.
23//!
24//! Receive checks identity before profile decoding. A mismatch is an explicit
25//! unknown-identity error, never fallback to another decoder
26//! (`req~selected-wire-explicit-rejection~1`). The conformance and golden tests
27//! under `tests/wire_metadata_*` plus external `up-wire-xcdrv2-rust` are the
28//! implementation references.
29
30#[cfg(any(feature = "protobuf-support", feature = "up-core-api"))]
31use std::io::Read;
32#[cfg(feature = "selected-wire-codec-core")]
33use std::{error::Error, fmt::Display};
34
35#[cfg(feature = "protobuf-support")]
36use crate::payload::codec::{PayloadFormat, PayloadLayout, ProtobufPayload};
37#[cfg(feature = "protobuf-support")]
38use crate::payload::UWireError;
39use crate::payload::{
40    codec::{DecodePayload, EncodePayload, PayloadCodec, ReadDecodePayload},
41    stable::{StableContainerPayload, StablePayload},
42};
43#[cfg(any(feature = "protobuf-support", feature = "up-core-api"))]
44use crate::PayloadEncoding;
45#[cfg(feature = "protobuf-support")]
46use crate::ProtobufMappable;
47#[cfg(feature = "selected-wire-codec-core")]
48use crate::{UCode, UFrameMetadata, UFrameMetadataError, UStatus};
49
50#[cfg(feature = "selected-wire-codec-core")]
51const NATIVE_PREFIX_MAGIC: &[u8; 4] = b"UPWM";
52#[cfg(feature = "selected-wire-codec-core")]
53const ID_REF_COMPACT: u8 = 0x00;
54#[cfg(feature = "selected-wire-codec-core")]
55const ID_REF_LITERAL: u8 = 0x01;
56
57/// First supported native-prefix metadata byte-format version.
58pub const FORMAT_VERSION: u16 = 1;
59
60/// Compact ID reserved for invalid-ID fixtures.
61pub const INVALID_ID_FIXTURE_COMPACT_ID: u16 = 0xFFFF;
62
63/// First compact ID reserved for local or experimental future tools.
64pub const LOCAL_EXPERIMENTAL_COMPACT_ID_START: u16 = 0x8000;
65
66/// Last compact ID reserved for local or experimental future tools.
67pub const LOCAL_EXPERIMENTAL_COMPACT_ID_END: u16 = 0xFFFE;
68
69/// Identity for the first-wave uProtocol native selected wire.
70pub const UPROTOCOL_NATIVE_WIRE_ID: WireIdentity =
71    WireIdentity::new("org.eclipse.uprotocol.wire.native", 0x0001);
72
73/// Payload-family identity for explicit native payload bytes.
74pub const NATIVE_EXPLICIT_PAYLOAD_FAMILY_ID: WireIdentity =
75    WireIdentity::new("native-explicit", 0x0001);
76
77/// Identity for the first-wave Protocol Buffers selected wire.
78pub const PROTOBUF_WIRE_ID: WireIdentity =
79    WireIdentity::new("org.eclipse.uprotocol.wire.protobuf", 0x0002);
80
81/// Payload-family identity for Protocol Buffers application payload bytes.
82pub const PROTOBUF_PAYLOAD_FAMILY_ID: WireIdentity = WireIdentity::new("protobuf", 0x0002);
83
84/// Identity reserved for the external first-wave XCDRv2 selected wire.
85///
86/// The production `XcdrV2Wire` type is intentionally owned by the external
87/// `up-wire-xcdrv2-rust` crate. `up-rust` exposes only the shared identity.
88pub const XCDR_V2_WIRE_ID: WireIdentity =
89    WireIdentity::new("org.eclipse.uprotocol.wire.xcdr-v2", 0x0003);
90
91/// Payload-family identity reserved for external XCDRv2 payload bytes.
92pub const XCDR_V2_PAYLOAD_FAMILY_ID: WireIdentity = WireIdentity::new("xcdr-v2", 0x0003);
93
94/// Identity for the first-wave stable-container selected wire.
95pub const STABLE_CONTAINER_WIRE_ID: WireIdentity =
96    WireIdentity::new("org.eclipse.uprotocol.wire.stable-container", 0x0004);
97
98/// Payload-family identity for stable-container payloads.
99pub const STABLE_CONTAINER_PAYLOAD_FAMILY_ID: WireIdentity =
100    WireIdentity::new("stable-container", 0x0004);
101
102/// Identity for the first-wave native-prefix metadata layout.
103pub const NATIVE_PREFIX_METADATA_LAYOUT_ID: WireIdentity =
104    WireIdentity::new("org.eclipse.uprotocol.metadata.native-prefix", 0x0001);
105
106/// Metadata layout identity for the canonical UFrame metadata field block
107/// carried behind native-prefix framing.
108///
109/// Distinct from [`NATIVE_PREFIX_METADATA_LAYOUT_ID`] (the legacy
110/// protobuf-`UAttributes` block) so that canonical and legacy metadata are
111/// selectable, rejectable profiles: decoding bytes of one profile with the
112/// other codec fails as `UWireMetadataError::UnknownMetadataLayoutId`,
113/// never as generic malformed metadata.
114pub const UFRAME_FIELDS_METADATA_LAYOUT_ID: WireIdentity =
115    WireIdentity::new("org.eclipse.uprotocol.metadata.uframe-fields", 0x0002);
116
117/// Stable wire, payload-family, or metadata-layout identity.
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub struct WireIdentity {
120    literal_id: &'static str,
121    compact_id: u16,
122}
123
124impl WireIdentity {
125    /// Creates an identity from its literal and compact representations.
126    #[must_use]
127    pub const fn new(literal_id: &'static str, compact_id: u16) -> Self {
128        Self {
129            literal_id,
130            compact_id,
131        }
132    }
133
134    /// Returns the language-neutral literal identity.
135    #[must_use]
136    pub fn literal_id(self) -> &'static str {
137        self.literal_id
138    }
139
140    /// Returns the compact first-wave identity.
141    #[must_use]
142    pub fn compact_id(self) -> u16 {
143        self.compact_id
144    }
145}
146
147/// Identity reference decoded from native-prefix metadata bytes.
148#[derive(Clone, Debug, Eq, PartialEq)]
149pub enum WireIdentityRef {
150    /// Compact `u16` identity reference.
151    Compact(u16),
152    /// Literal UTF-8 identity reference.
153    Literal(String),
154}
155
156impl WireIdentityRef {
157    /// Returns whether this reference identifies `expected`.
158    #[must_use]
159    pub fn matches(&self, expected: WireIdentity) -> bool {
160        match self {
161            Self::Compact(actual) => *actual == expected.compact_id(),
162            Self::Literal(actual) => actual == expected.literal_id(),
163        }
164    }
165}
166
167/// Compatibility decision between decoded metadata and a selected wire.
168#[derive(Clone, Copy, Debug, Eq, PartialEq)]
169pub enum WireCompatibility {
170    /// Decoded metadata is compatible with the selected wire.
171    Compatible,
172    /// Decoded metadata is incompatible with the selected wire.
173    Incompatible,
174}
175
176impl WireCompatibility {
177    /// Returns true when the compatibility result is compatible.
178    #[must_use]
179    pub fn is_compatible(self) -> bool {
180        matches!(self, Self::Compatible)
181    }
182}
183
184/// Static selected-wire contract.
185pub trait UWire {
186    /// Stable identity for the full selected wire.
187    const WIRE_ID: WireIdentity;
188    /// Payload operation family supported by this selected wire.
189    const PAYLOAD_FAMILY_ID: WireIdentity;
190    /// Metadata byte layout used by this selected wire.
191    const METADATA_LAYOUT_ID: WireIdentity;
192    /// Metadata byte-format version supported by this selected wire.
193    const FORMAT_VERSION: u16;
194
195    /// Checks selected-wire compatibility before public frame exposure.
196    fn wire_compatibility(actual: &WireIdentityRef) -> WireCompatibility {
197        if actual.matches(Self::WIRE_ID) {
198            WireCompatibility::Compatible
199        } else {
200            WireCompatibility::Incompatible
201        }
202    }
203
204    /// Checks payload-family compatibility before public frame exposure.
205    fn payload_family_compatibility(actual: &WireIdentityRef) -> WireCompatibility {
206        if actual.matches(Self::PAYLOAD_FAMILY_ID) {
207            WireCompatibility::Compatible
208        } else {
209            WireCompatibility::Incompatible
210        }
211    }
212
213    /// Returns the selected-wire metadata context consumed by metadata codecs.
214    #[cfg(feature = "selected-wire-codec-core")]
215    fn metadata_context() -> UWireMetadataContext
216    where
217        Self: Sized,
218    {
219        UWireMetadataContext::from_wire::<Self>()
220    }
221}
222
223/// Selected-wire metadata context consumed by metadata codecs.
224#[cfg(feature = "selected-wire-codec-core")]
225#[derive(Clone, Copy, Debug)]
226pub struct UWireMetadataContext {
227    /// Stable identity for the full selected wire.
228    pub wire_id: WireIdentity,
229    /// Payload operation family supported by this selected wire.
230    pub payload_family_id: WireIdentity,
231    /// Metadata byte layout used by this selected wire.
232    pub metadata_layout_id: WireIdentity,
233    /// Metadata byte-format version supported by this selected wire.
234    pub format_version: u16,
235    wire_compatibility: Option<fn(&WireIdentityRef) -> WireCompatibility>,
236    payload_family_compatibility: Option<fn(&WireIdentityRef) -> WireCompatibility>,
237}
238
239#[cfg(feature = "selected-wire-codec-core")]
240impl UWireMetadataContext {
241    /// Builds a metadata context for a selected wire marker type.
242    #[must_use]
243    pub fn from_wire<W>() -> Self
244    where
245        W: UWire,
246    {
247        Self {
248            wire_id: W::WIRE_ID,
249            payload_family_id: W::PAYLOAD_FAMILY_ID,
250            metadata_layout_id: W::METADATA_LAYOUT_ID,
251            format_version: W::FORMAT_VERSION,
252            wire_compatibility: Some(W::wire_compatibility),
253            payload_family_compatibility: Some(W::payload_family_compatibility),
254        }
255    }
256
257    /// Builds a metadata context with exact identity matching.
258    #[must_use]
259    pub fn new_exact(
260        wire_id: WireIdentity,
261        payload_family_id: WireIdentity,
262        metadata_layout_id: WireIdentity,
263        format_version: u16,
264    ) -> Self {
265        Self {
266            wire_id,
267            payload_family_id,
268            metadata_layout_id,
269            format_version,
270            wire_compatibility: None,
271            payload_family_compatibility: None,
272        }
273    }
274
275    /// Returns this context with the metadata layout identity replaced.
276    ///
277    /// Metadata codecs overlay their own profile identity so the layout id on
278    /// the wire always names the codec's byte profile, independent of the
279    /// legacy per-wire declaration.
280    #[must_use]
281    pub fn with_metadata_layout(mut self, metadata_layout_id: WireIdentity) -> Self {
282        self.metadata_layout_id = metadata_layout_id;
283        self
284    }
285
286    /// Checks selected-wire compatibility before public frame exposure.
287    #[must_use]
288    pub fn wire_compatibility(&self, actual: &WireIdentityRef) -> WireCompatibility {
289        if let Some(wire_compatibility) = self.wire_compatibility {
290            return wire_compatibility(actual);
291        }
292        if actual.matches(self.wire_id) {
293            WireCompatibility::Compatible
294        } else {
295            WireCompatibility::Incompatible
296        }
297    }
298
299    /// Checks payload-family compatibility before public frame exposure.
300    #[must_use]
301    pub fn payload_family_compatibility(&self, actual: &WireIdentityRef) -> WireCompatibility {
302        if let Some(payload_family_compatibility) = self.payload_family_compatibility {
303            return payload_family_compatibility(actual);
304        }
305        if actual.matches(self.payload_family_id) {
306            WireCompatibility::Compatible
307        } else {
308            WireCompatibility::Incompatible
309        }
310    }
311}
312
313/// Default first-wave native wire for explicit native payload paths.
314#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
315pub struct UProtocolNativeWire;
316
317impl UWire for UProtocolNativeWire {
318    const WIRE_ID: WireIdentity = UPROTOCOL_NATIVE_WIRE_ID;
319    const PAYLOAD_FAMILY_ID: WireIdentity = NATIVE_EXPLICIT_PAYLOAD_FAMILY_ID;
320    const METADATA_LAYOUT_ID: WireIdentity = NATIVE_PREFIX_METADATA_LAYOUT_ID;
321    const FORMAT_VERSION: u16 = FORMAT_VERSION;
322}
323
324/// Selected wire for Protocol Buffers application payloads.
325#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
326pub struct ProtobufWire;
327
328impl UWire for ProtobufWire {
329    const WIRE_ID: WireIdentity = PROTOBUF_WIRE_ID;
330    const PAYLOAD_FAMILY_ID: WireIdentity = PROTOBUF_PAYLOAD_FAMILY_ID;
331    const METADATA_LAYOUT_ID: WireIdentity = NATIVE_PREFIX_METADATA_LAYOUT_ID;
332    const FORMAT_VERSION: u16 = FORMAT_VERSION;
333}
334
335#[cfg(feature = "protobuf-support")]
336impl PayloadFormat for ProtobufWire {
337    fn name() -> &'static str {
338        ProtobufPayload::name()
339    }
340
341    fn encoding() -> PayloadEncoding {
342        ProtobufPayload::encoding()
343    }
344}
345
346#[cfg(feature = "protobuf-support")]
347impl<T> EncodePayload<T> for ProtobufWire
348where
349    T: ProtobufMappable,
350{
351    fn payload_layout(value: &T) -> Result<PayloadLayout, UWireError> {
352        ProtobufPayload::payload_layout(value)
353    }
354
355    fn encode_payload(value: &T, dst: &mut [u8]) -> Result<(), UWireError> {
356        ProtobufPayload::encode_payload(value, dst)
357    }
358}
359
360#[cfg(feature = "protobuf-support")]
361impl<'a, T> DecodePayload<'a, T> for ProtobufWire
362where
363    T: ProtobufMappable,
364{
365    fn decode_payload(src: &'a [u8]) -> Result<T, UWireError> {
366        ProtobufPayload::decode_payload(src)
367    }
368}
369
370#[cfg(feature = "protobuf-support")]
371impl<T> ReadDecodePayload<T> for ProtobufWire
372where
373    T: ProtobufMappable,
374{
375    fn decode_payload_from_reader<R: Read>(reader: R, payload_len: usize) -> Result<T, UWireError> {
376        ProtobufPayload::decode_payload_from_reader(reader, payload_len)
377    }
378}
379
380/// Selected wire for stable-container payloads.
381#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
382pub struct StableContainerWireFormat;
383
384impl UWire for StableContainerWireFormat {
385    const WIRE_ID: WireIdentity = STABLE_CONTAINER_WIRE_ID;
386    const PAYLOAD_FAMILY_ID: WireIdentity = STABLE_CONTAINER_PAYLOAD_FAMILY_ID;
387    const METADATA_LAYOUT_ID: WireIdentity = NATIVE_PREFIX_METADATA_LAYOUT_ID;
388    const FORMAT_VERSION: u16 = FORMAT_VERSION;
389}
390
391/// Explicit metadata codec used by selected-wire transport adapters.
392#[cfg(feature = "selected-wire-codec-core")]
393pub trait UWireMetadataCodec {
394    /// Metadata byte-profile identity this codec writes and accepts.
395    ///
396    /// Defaults to the legacy native-prefix protobuf layout for compatibility
397    /// with pre-R2 external codecs; profile codecs override it. Codecs MUST
398    /// overlay this identity onto the context they encode/decode with so that
399    /// cross-profile decode fails as
400    /// [`UWireMetadataError::UnknownMetadataLayoutId`].
401    const METADATA_LAYOUT_ID: WireIdentity = NATIVE_PREFIX_METADATA_LAYOUT_ID;
402
403    /// Encodes native frame metadata for the selected-wire context.
404    ///
405    /// # Errors
406    ///
407    /// Returns an error when metadata is invalid or cannot be serialized.
408    fn encode_frame_metadata(
409        &self,
410        context: UWireMetadataContext,
411        metadata: &UFrameMetadata,
412    ) -> Result<Vec<u8>, UWireMetadataError>;
413
414    /// Decodes and validates native frame metadata for the selected-wire context.
415    ///
416    /// # Errors
417    ///
418    /// Returns an error when bytes are malformed, use an unsupported layout or
419    /// version, or declare an incompatible wire or payload family.
420    fn decode_frame_metadata(
421        &self,
422        context: UWireMetadataContext,
423        src: &[u8],
424    ) -> Result<UFrameMetadata, UWireMetadataError>;
425}
426
427/// Metadata codec that is statically compatible with selected wire `W`.
428///
429/// This marker keeps the ordinary metadata codec contract reusable while
430/// allowing adapter constructors to reject invalid wire/codec pairings at
431/// compile time.
432#[cfg(feature = "selected-wire-codec-core")]
433pub trait UWireMetadataCodecFor<W>: UWireMetadataCodec
434where
435    W: UWire,
436{
437}
438
439/// Canonical native-prefix metadata codec carrying the clean UFrame metadata
440/// field block.
441///
442/// This codec serializes [`UFrameMetadata`] directly using the canonical
443/// field block defined in [`crate::frame::codec`] — no protobuf, no legacy
444/// `UAttributes` projection, full fidelity for open payload encodings. The
445/// outer native-prefix framing (magic, metadata layout id, format version,
446/// wire id, payload family id) is identical to the legacy codec so that
447/// transports can carry either block behind the same placement layouts.
448#[cfg(feature = "selected-wire-codec-core")]
449#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
450pub struct NativePrefixFrameMetadataCodec;
451
452#[cfg(feature = "selected-wire-codec-core")]
453impl UWireMetadataCodec for NativePrefixFrameMetadataCodec {
454    const METADATA_LAYOUT_ID: WireIdentity = UFRAME_FIELDS_METADATA_LAYOUT_ID;
455
456    fn encode_frame_metadata(
457        &self,
458        context: UWireMetadataContext,
459        metadata: &UFrameMetadata,
460    ) -> Result<Vec<u8>, UWireMetadataError> {
461        let context = context.with_metadata_layout(Self::METADATA_LAYOUT_ID);
462        let fields = crate::frame::codec::encode_frame_metadata_fields(metadata)
463            .map_err(|error| UWireMetadataError::FrameMetadata(error.to_string()))?;
464        let mut out = Vec::with_capacity(
465            NATIVE_PREFIX_MAGIC.len()
466                + (3 * (1 + std::mem::size_of::<u16>()))
467                + (2 * std::mem::size_of::<u16>())
468                + std::mem::size_of::<u32>()
469                + fields.len(),
470        );
471        out.extend_from_slice(NATIVE_PREFIX_MAGIC);
472        write_identity_ref(&mut out, context.metadata_layout_id);
473        write_u16(&mut out, context.format_version);
474        write_identity_ref(&mut out, context.wire_id);
475        write_identity_ref(&mut out, context.payload_family_id);
476        write_u16(&mut out, 0);
477        write_len_prefixed_bytes(&mut out, &fields)?;
478        Ok(out)
479    }
480
481    fn decode_frame_metadata(
482        &self,
483        context: UWireMetadataContext,
484        src: &[u8],
485    ) -> Result<UFrameMetadata, UWireMetadataError> {
486        let context = context.with_metadata_layout(Self::METADATA_LAYOUT_ID);
487        let mut reader = MetadataReader::new(src);
488        read_and_check_native_prefix(&mut reader, context)?;
489        let fields = reader.read_len_prefixed_bytes()?;
490        reader.finish()?;
491        crate::frame::codec::decode_frame_metadata_fields(fields)
492            .map_err(|error| UWireMetadataError::MalformedMetadata(error.to_string()))
493    }
494}
495
496#[cfg(feature = "selected-wire-codec-core")]
497impl<W> UWireMetadataCodecFor<W> for NativePrefixFrameMetadataCodec where W: UWire {}
498
499/// Wire-level encode helper alias for existing payload codecs.
500pub trait UWireEncode<T: ?Sized>: EncodePayload<T> {}
501
502impl<C, T: ?Sized> UWireEncode<T> for C where C: EncodePayload<T> {}
503
504/// Wire-level borrowed decode helper alias for existing payload codecs.
505pub trait UWireDecode<'a, T>: DecodePayload<'a, T> {}
506
507impl<'a, C, T> UWireDecode<'a, T> for C where C: DecodePayload<'a, T> {}
508
509/// Wire-level owned decode helper alias for existing payload codecs.
510pub trait UWireDecodeOwned<T>: for<'a> DecodePayload<'a, T> {}
511
512impl<C, T> UWireDecodeOwned<T> for C where C: for<'a> DecodePayload<'a, T> {}
513
514/// Wire-level reader decode helper alias for existing payload codecs.
515pub trait UWireReadDecode<T>: ReadDecodePayload<T> {}
516
517impl<C, T> UWireReadDecode<T> for C where C: ReadDecodePayload<T> {}
518
519/// Selected-wire payload mapping for typed payloads.
520///
521/// The wire marker, not the payload type, chooses one payload codec for `T`.
522/// The associated codec need not be the wire marker itself. Its implemented
523/// capability traits decide which operations are available: encode/decode,
524/// initialized TX loan, uninitialized TX loan, and receive-side typed borrow.
525/// Implementing this mapping does not grant capabilities the codec does not
526/// implement and does not make [`ReadDecodePayload`] a borrowed or in-place
527/// decode API.
528pub trait UWirePayload<T>: UWire {
529    /// Concrete payload codec used by this selected wire for `T`.
530    type Codec: PayloadCodec;
531}
532
533impl<T> UWirePayload<T> for StableContainerWireFormat
534where
535    T: StablePayload,
536{
537    type Codec = StableContainerPayload<T>;
538}
539
540#[cfg(feature = "protobuf-support")]
541impl<T> UWirePayload<T> for ProtobufWire
542where
543    T: ProtobufMappable,
544{
545    type Codec = ProtobufPayload;
546}
547
548/// Errors returned by native-prefix selected-wire metadata handling.
549#[cfg(feature = "selected-wire-codec-core")]
550#[derive(Clone, Debug, Eq, PartialEq)]
551#[non_exhaustive]
552pub enum UWireMetadataError {
553    /// The input did not start with the native-prefix metadata magic bytes.
554    WrongMagic,
555    /// The metadata layout id was not native-prefix.
556    UnknownMetadataLayoutId {
557        /// The metadata layout id actually present in the input.
558        actual: WireIdentityRef,
559    },
560    /// The metadata layout id that was actually present.
561    /// The metadata version is unsupported by the selected wire.
562    UnsupportedVersion {
563        /// The version this codec supports.
564        expected: u16,
565        /// The version present in the input.
566        actual: u16,
567    },
568    /// The selected-wire id is incompatible with `W`.
569    WrongWireMetadata {
570        /// The selected wire's identity.
571        expected: WireIdentity,
572        /// The wire identity present in the input.
573        actual: WireIdentityRef,
574    },
575    /// The payload-family id is incompatible with `W`.
576    PayloadFamilyMismatch {
577        /// The selected wire's payload-family identity.
578        expected: WireIdentity,
579        /// The payload-family identity present in the input.
580        actual: WireIdentityRef,
581    },
582    /// The reserved flags field contained unsupported bits.
583    UnsupportedReservedFlags(u16),
584    /// The payload encoding block is unsupported or malformed.
585    UnsupportedPayloadEncoding(String),
586    /// The metadata bytes are malformed.
587    MalformedMetadata(String),
588    /// Frame metadata validation failed.
589    FrameMetadata(String),
590    /// Metadata serialization or parsing failed.
591    SerializationError(String),
592}
593
594#[cfg(feature = "selected-wire-codec-core")]
595impl Display for UWireMetadataError {
596    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
597        match self {
598            Self::WrongMagic => f.write_str("wrong native-prefix metadata magic"),
599            Self::UnknownMetadataLayoutId { actual } => {
600                write!(f, "unknown metadata layout id: {actual:?}")
601            }
602            Self::UnsupportedVersion { expected, actual } => write!(
603                f,
604                "unsupported metadata version: expected {expected}, got {actual}"
605            ),
606            Self::WrongWireMetadata { expected, actual } => write!(
607                f,
608                "wrong selected wire metadata: expected {expected:?}, got {actual:?}"
609            ),
610            Self::PayloadFamilyMismatch { expected, actual } => write!(
611                f,
612                "payload family mismatch: expected {expected:?}, got {actual:?}"
613            ),
614            Self::UnsupportedReservedFlags(flags) => {
615                write!(f, "unsupported native-prefix reserved flags: {flags:#06x}")
616            }
617            Self::UnsupportedPayloadEncoding(message) => {
618                write!(f, "unsupported payload encoding: {message}")
619            }
620            Self::MalformedMetadata(message) => write!(f, "malformed wire metadata: {message}"),
621            Self::FrameMetadata(message) => write!(f, "invalid frame metadata: {message}"),
622            Self::SerializationError(message) => {
623                write!(f, "metadata serialization error: {message}")
624            }
625        }
626    }
627}
628
629#[cfg(feature = "selected-wire-codec-core")]
630impl Error for UWireMetadataError {}
631
632#[cfg(feature = "selected-wire-codec-core")]
633impl From<UFrameMetadataError> for UWireMetadataError {
634    fn from(value: UFrameMetadataError) -> Self {
635        Self::FrameMetadata(value.to_string())
636    }
637}
638
639#[cfg(feature = "selected-wire-codec-core")]
640impl From<UWireMetadataError> for UStatus {
641    fn from(value: UWireMetadataError) -> Self {
642        UStatus::fail_with_code(UCode::InvalidArgument, value.to_string())
643    }
644}
645
646/// Reads and validates the shared native-prefix framing: magic, metadata
647/// layout id, format version, wire id, payload family id, and reserved flags.
648#[cfg(feature = "selected-wire-codec-core")]
649fn read_and_check_native_prefix(
650    reader: &mut MetadataReader<'_>,
651    context: UWireMetadataContext,
652) -> Result<(), UWireMetadataError> {
653    if reader.take(NATIVE_PREFIX_MAGIC.len())? != NATIVE_PREFIX_MAGIC.as_slice() {
654        return Err(UWireMetadataError::WrongMagic);
655    }
656
657    let layout_id = reader.read_identity_ref()?;
658    if !layout_id.matches(context.metadata_layout_id) {
659        return Err(UWireMetadataError::UnknownMetadataLayoutId { actual: layout_id });
660    }
661
662    let version = reader.read_u16()?;
663    if version != context.format_version {
664        return Err(UWireMetadataError::UnsupportedVersion {
665            expected: context.format_version,
666            actual: version,
667        });
668    }
669
670    let wire_id = reader.read_identity_ref()?;
671    if !context.wire_compatibility(&wire_id).is_compatible() {
672        return Err(UWireMetadataError::WrongWireMetadata {
673            expected: context.wire_id,
674            actual: wire_id,
675        });
676    }
677
678    let payload_family_id = reader.read_identity_ref()?;
679    if !context
680        .payload_family_compatibility(&payload_family_id)
681        .is_compatible()
682    {
683        return Err(UWireMetadataError::PayloadFamilyMismatch {
684            expected: context.payload_family_id,
685            actual: payload_family_id,
686        });
687    }
688
689    let flags = reader.read_u16()?;
690    if flags != 0 {
691        return Err(UWireMetadataError::UnsupportedReservedFlags(flags));
692    }
693    Ok(())
694}
695
696#[cfg(feature = "selected-wire-codec-core")]
697fn write_identity_ref(out: &mut Vec<u8>, identity: WireIdentity) {
698    out.push(ID_REF_COMPACT);
699    write_u16(out, identity.compact_id());
700}
701
702#[cfg(feature = "selected-wire-codec-core")]
703fn write_len_prefixed_bytes(out: &mut Vec<u8>, value: &[u8]) -> Result<(), UWireMetadataError> {
704    let len = u32::try_from(value.len()).map_err(|_| {
705        UWireMetadataError::MalformedMetadata(format!(
706            "length {} exceeds u32 native-prefix limit",
707            value.len()
708        ))
709    })?;
710    write_u32(out, len);
711    out.extend_from_slice(value);
712    Ok(())
713}
714
715#[cfg(feature = "selected-wire-codec-core")]
716fn write_u16(out: &mut Vec<u8>, value: u16) {
717    out.extend_from_slice(&value.to_le_bytes());
718}
719
720#[cfg(feature = "selected-wire-codec-core")]
721fn write_u32(out: &mut Vec<u8>, value: u32) {
722    out.extend_from_slice(&value.to_le_bytes());
723}
724
725#[cfg(feature = "selected-wire-codec-core")]
726struct MetadataReader<'a> {
727    src: &'a [u8],
728    pos: usize,
729}
730
731#[cfg(feature = "selected-wire-codec-core")]
732impl<'a> MetadataReader<'a> {
733    fn new(src: &'a [u8]) -> Self {
734        Self { src, pos: 0 }
735    }
736
737    fn finish(&self) -> Result<(), UWireMetadataError> {
738        if self.pos == self.src.len() {
739            Ok(())
740        } else {
741            Err(UWireMetadataError::MalformedMetadata(format!(
742                "{} trailing byte(s)",
743                self.src.len() - self.pos
744            )))
745        }
746    }
747
748    fn take(&mut self, len: usize) -> Result<&'a [u8], UWireMetadataError> {
749        let end = self.pos.checked_add(len).ok_or_else(|| {
750            UWireMetadataError::MalformedMetadata("metadata length overflow".to_string())
751        })?;
752        let chunk = self.src.get(self.pos..end).ok_or_else(|| {
753            UWireMetadataError::MalformedMetadata(format!(
754                "needed {len} byte(s) at offset {}, input has {} byte(s)",
755                self.pos,
756                self.src.len()
757            ))
758        })?;
759        self.pos = end;
760        Ok(chunk)
761    }
762
763    fn read_u8(&mut self) -> Result<u8, UWireMetadataError> {
764        let bytes = self.take(1)?;
765        bytes
766            .first()
767            .copied()
768            .ok_or_else(|| UWireMetadataError::MalformedMetadata("missing u8 field".to_string()))
769    }
770
771    fn read_u16(&mut self) -> Result<u16, UWireMetadataError> {
772        let bytes = self.take(2)?;
773        let array = <[u8; 2]>::try_from(bytes)
774            .map_err(|_| UWireMetadataError::MalformedMetadata("invalid u16 field".to_string()))?;
775        Ok(u16::from_le_bytes(array))
776    }
777
778    fn read_u32(&mut self) -> Result<u32, UWireMetadataError> {
779        let bytes = self.take(4)?;
780        let array = <[u8; 4]>::try_from(bytes)
781            .map_err(|_| UWireMetadataError::MalformedMetadata("invalid u32 field".to_string()))?;
782        Ok(u32::from_le_bytes(array))
783    }
784
785    fn read_identity_ref(&mut self) -> Result<WireIdentityRef, UWireMetadataError> {
786        match self.read_u8()? {
787            ID_REF_COMPACT => Ok(WireIdentityRef::Compact(self.read_u16()?)),
788            ID_REF_LITERAL => {
789                let bytes = self.read_len_prefixed_bytes()?;
790                let value = std::str::from_utf8(bytes).map_err(|error| {
791                    UWireMetadataError::MalformedMetadata(format!(
792                        "identity literal is not UTF-8: {error}"
793                    ))
794                })?;
795                Ok(WireIdentityRef::Literal(value.to_string()))
796            }
797            tag => Err(UWireMetadataError::MalformedMetadata(format!(
798                "unknown identity reference tag {tag}"
799            ))),
800        }
801    }
802
803    fn read_len_prefixed_bytes(&mut self) -> Result<&'a [u8], UWireMetadataError> {
804        let len = usize::try_from(self.read_u32()?).map_err(|_| {
805            UWireMetadataError::MalformedMetadata("length prefix does not fit usize".to_string())
806        })?;
807        self.take(len)
808    }
809}
810
811/// Binds payload types to a wire whose marker is its own codec
812/// (`Codec = Self`), removing the most repetitive line of an all-in-one
813/// wire implementation.
814///
815/// ```rust,no_run
816/// # #[cfg(feature = "wire-implementer-api")]
817/// # fn main() {
818/// # use up_rust::{bind_wire_self_codec, DecodePayload, EncodePayload,
819/// #     PayloadCodec, PayloadEncoding, PayloadLayout, UWire, UWireError,
820/// #     WireIdentity};
821/// # struct MyWire;
822/// # struct A(u32); struct B(u32);
823/// # impl UWire for MyWire {
824/// #     const WIRE_ID: WireIdentity = WireIdentity::new("demo.self-codec", 0x8043);
825/// #     const PAYLOAD_FAMILY_ID: WireIdentity = WireIdentity::new("demo.self", 0x8043);
826/// #     const METADATA_LAYOUT_ID: WireIdentity =
827/// #         WireIdentity::new("uprotocol.native-prefix.v1", 0x0001);
828/// #     const FORMAT_VERSION: u16 = 1;
829/// # }
830/// # impl PayloadCodec for MyWire {
831/// #     fn codec_name() -> &'static str { "demo-self-codec" }
832/// #     fn payload_encoding() -> PayloadEncoding { PayloadEncoding::RAW }
833/// # }
834/// # impl EncodePayload<A> for MyWire {
835/// #     fn payload_layout(_: &A) -> Result<PayloadLayout, UWireError> { PayloadLayout::new(4, 4) }
836/// #     fn encode_payload(v: &A, dst: &mut [u8]) -> Result<(), UWireError> {
837/// #         dst.copy_from_slice(&v.0.to_le_bytes());
838/// #         Ok(())
839/// #     }
840/// # }
841/// # impl<'a> DecodePayload<'a, A> for MyWire {
842/// #     fn decode_payload(src: &'a [u8]) -> Result<A, UWireError> {
843/// #         Ok(A(u32::from_le_bytes(src.try_into().map_err(|_| UWireError::invalid_payload("demo payload must be 4 bytes"))?)))
844/// #     }
845/// # }
846/// # impl EncodePayload<B> for MyWire {
847/// #     fn payload_layout(_: &B) -> Result<PayloadLayout, UWireError> { PayloadLayout::new(4, 4) }
848/// #     fn encode_payload(v: &B, dst: &mut [u8]) -> Result<(), UWireError> {
849/// #         dst.copy_from_slice(&v.0.to_le_bytes());
850/// #         Ok(())
851/// #     }
852/// # }
853/// # impl<'a> DecodePayload<'a, B> for MyWire {
854/// #     fn decode_payload(src: &'a [u8]) -> Result<B, UWireError> {
855/// #         Ok(B(u32::from_le_bytes(src.try_into().map_err(|_| UWireError::invalid_payload("demo payload must be 4 bytes"))?)))
856/// #     }
857/// # }
858/// // One wire identity, two payload types, marker as codec for both:
859/// bind_wire_self_codec!(MyWire: A, B);
860/// # }
861/// # #[cfg(not(feature = "wire-implementer-api"))]
862/// # fn main() {}
863/// ```
864///
865/// Why not a trait default or a blanket impl? `type Codec = Self;` as an
866/// associated-type default is unstable Rust, and a blanket
867/// `impl<W, T> UWirePayload<T> for W` would forbid, by coherence, exactly the
868/// wire that binds a *different* codec for some payload type — the case the
869/// three-trait split exists to support. The macro adds the convenience without
870/// closing that door.
871#[macro_export]
872macro_rules! bind_wire_self_codec {
873    ($wire:ty : $($payload:ty),+ $(,)?) => {
874        $(
875            impl $crate::UWirePayload<$payload> for $wire {
876                type Codec = $wire;
877            }
878        )+
879    };
880}
881
882#[cfg(test)]
883mod tests {
884    #[cfg(feature = "protobuf-support")]
885    use protobuf::well_known_types::wrappers::StringValue;
886
887    use super::*;
888    use crate::payload::{
889        codec::PayloadCodec,
890        loan::{BorrowPayload, LoanPayload},
891    };
892    use crate::test_support::StableTestBytes as WireStableBytes;
893
894    fn assert_wire_payload<T, W>()
895    where
896        W: UWirePayload<T>,
897        <W as UWirePayload<T>>::Codec: LoanPayload<T> + BorrowPayload<T> + PayloadCodec,
898    {
899    }
900
901    #[cfg(feature = "zero-copy-transport")]
902    fn assert_wire_uninit_payload<T, W>()
903    where
904        W: UWirePayload<T>,
905        <W as UWirePayload<T>>::Codec: crate::payload::loan::LoanUninitPayload<T>,
906    {
907    }
908
909    #[test]
910    fn first_wave_wire_identity_constants_match_register() {
911        assert_eq!(
912            PROTOBUF_WIRE_ID.literal_id(),
913            "org.eclipse.uprotocol.wire.protobuf"
914        );
915        assert_eq!(PROTOBUF_WIRE_ID.compact_id(), 0x0002);
916        assert_eq!(PROTOBUF_PAYLOAD_FAMILY_ID.literal_id(), "protobuf");
917        assert_eq!(PROTOBUF_PAYLOAD_FAMILY_ID.compact_id(), 0x0002);
918        assert_eq!(
919            XCDR_V2_WIRE_ID.literal_id(),
920            "org.eclipse.uprotocol.wire.xcdr-v2"
921        );
922        assert_eq!(XCDR_V2_WIRE_ID.compact_id(), 0x0003);
923        assert_eq!(XCDR_V2_PAYLOAD_FAMILY_ID.literal_id(), "xcdr-v2");
924        assert_eq!(XCDR_V2_PAYLOAD_FAMILY_ID.compact_id(), 0x0003);
925        assert_eq!(
926            STABLE_CONTAINER_WIRE_ID.literal_id(),
927            "org.eclipse.uprotocol.wire.stable-container"
928        );
929        assert_eq!(STABLE_CONTAINER_WIRE_ID.compact_id(), 0x0004);
930        assert_eq!(
931            STABLE_CONTAINER_PAYLOAD_FAMILY_ID.literal_id(),
932            "stable-container"
933        );
934        assert_eq!(STABLE_CONTAINER_PAYLOAD_FAMILY_ID.compact_id(), 0x0004);
935    }
936
937    #[cfg(feature = "protobuf-support")]
938    #[test]
939    fn protobuf_wire_delegates_application_payload_codec() {
940        let value = StringValue {
941            value: "wire".to_string(),
942            special_fields: Default::default(),
943        };
944
945        let encoded = ProtobufWire::encode_payload_owned(&value).expect("encode protobuf wire");
946        let decoded: StringValue =
947            ProtobufWire::decode_payload(&encoded).expect("decode protobuf wire");
948        assert_eq!(decoded.value, "wire");
949        assert_eq!(
950            ProtobufWire::payload_encoding(),
951            ProtobufPayload::payload_encoding()
952        );
953    }
954
955    #[test]
956    fn stable_container_wire_maps_type_to_capable_codec() {
957        assert_wire_payload::<WireStableBytes, StableContainerWireFormat>();
958        #[cfg(feature = "zero-copy-transport")]
959        assert_wire_uninit_payload::<WireStableBytes, StableContainerWireFormat>();
960
961        let encoding =
962            <StableContainerWireFormat as UWirePayload<WireStableBytes>>::Codec::payload_encoding();
963        let expected = StableContainerPayload::<WireStableBytes>::payload_encoding();
964        assert_eq!(encoding, expected);
965    }
966}