Skip to main content

hurray_core/layout/
unknown.rs

1//! Unknown layout descriptor — permissive-mode fallback.
2//!
3//! Carries a raw tag byte and opaque payload. Only reachable via the permissive
4//! constructor path; the named-variant constructors always reject unrecognised tags.
5
6use crate::{Error, Result};
7
8/// Descriptor for an unrecognized layout, accepted only in permissive mode.
9///
10/// A conforming reader in **permissive mode** MAY accept tensor descriptors
11/// whose layout tag is not recognized, but MUST NOT dereference or interpret
12/// the tensor data buffer for such tensors. The `Unknown` variant preserves the
13/// raw tag and bytes for inspection without dereferencing.
14///
15/// Named-variant constructors on [`LayoutDescriptor`](super::LayoutDescriptor)
16/// reject tags `0x00`, `0xFF`, reserved ranges, and private-extension tags —
17/// callers that want to pass unknown layouts through must construct
18/// `Unknown` explicitly (or via a permissive decoder in a higher layer).
19///
20/// `buffer_count` is always `None` for `Unknown` because the number of
21/// required buffers is not known without understanding the layout.
22///
23/// The constructor rejects any tag this implementation *does* understand — see
24/// [`UnknownLayout::new`]. The type means "I could not parse this", and that claim
25/// has to stay true, because a permissive relay downstream acts on it.
26///
27/// # Examples
28///
29/// ```
30/// use hurray_core::layout::{LayoutDescriptor, UnknownLayout};
31///
32/// // Simulate a permissive reader accepting an unrecognised tag.
33/// let layout = LayoutDescriptor::Unknown(UnknownLayout::new(0x0C, vec![0x00, 0x01]).unwrap());
34/// assert_eq!(layout.tag(), 0x0C);
35/// assert!(layout.buffer_count().is_none());
36/// ```
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38#[non_exhaustive]
39pub struct UnknownLayout {
40    /// The raw tag byte from the wire, exactly as received.
41    pub tag: u8,
42
43    /// The raw layout-specific bytes following the tag, as received.
44    pub raw_bytes: Vec<u8>,
45}
46
47impl UnknownLayout {
48    /// Creates a new [`UnknownLayout`] from a raw tag byte and opaque payload bytes.
49    ///
50    /// # Errors
51    ///
52    /// | Tag | Error |
53    /// |-----|-------|
54    /// | `0x00`, `0xFF` | [`Error::InvalidLayoutTag`] — permanently invalid in every mode |
55    /// | a tag with a named variant | [`Error::NamedLayoutTag`] |
56    /// | `0xF0`–`0xFE` | [`Error::PrivateLayoutTag`] — use [`PrivateExtensionLayout`](super::PrivateExtensionLayout) |
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use hurray_core::layout::UnknownLayout;
62    ///
63    /// let u = UnknownLayout::new(0x0C, vec![1, 2, 3]).unwrap();
64    /// assert_eq!(u.tag, 0x0C);
65    /// assert_eq!(u.raw_bytes, [1, 2, 3]);
66    ///
67    /// // Permanently-invalid sentinels are rejected even in permissive mode.
68    /// assert!(UnknownLayout::new(0x00, vec![]).is_err());
69    /// assert!(UnknownLayout::new(0xFF, vec![]).is_err());
70    ///
71    /// // So is a tag this implementation understands.
72    /// assert!(UnknownLayout::new(0x07, vec![]).is_err()); // CSR
73    /// assert!(UnknownLayout::new(0xF0, vec![]).is_err()); // private extension
74    /// ```
75    pub fn new(tag: u8, raw_bytes: Vec<u8>) -> Result<Self> {
76        // Permanently-invalid sentinels must be rejected in all modes per spec.
77        if super::is_invalid_tag(tag) {
78            return Err(Error::InvalidLayoutTag(tag));
79        }
80        // "Unknown" must not claim a tag this implementation understands. Unknown has
81        // no buffer count and no shape constraints, so such a descriptor would skip
82        // every check the named variant applies, then encode to a wire tag a
83        // conforming reader parses as that named layout.
84        if super::is_named_tag(tag) {
85            return Err(Error::NamedLayoutTag(tag));
86        }
87        // A private tag is understood too — as an extension id plus payload, which
88        // PrivateExtensionLayout preserves and this type would flatten into opaque
89        // bytes.
90        if super::is_private_tag(tag) {
91            return Err(Error::PrivateLayoutTag(tag));
92        }
93        Ok(Self { tag, raw_bytes })
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::{layout::LayoutDescriptor, Error};
101
102    #[test]
103    fn unknown_tag_passthrough() {
104        let layout = LayoutDescriptor::Unknown(UnknownLayout::new(0x0C, vec![]).unwrap());
105        assert_eq!(layout.tag(), 0x0C);
106    }
107
108    #[test]
109    fn unknown_buffer_count_is_none() {
110        let layout = LayoutDescriptor::Unknown(UnknownLayout::new(0x0C, vec![1, 2, 3]).unwrap());
111        assert!(layout.buffer_count().is_none());
112    }
113
114    #[test]
115    fn rejects_invalid_sentinel_0x00() {
116        assert!(matches!(
117            UnknownLayout::new(0x00, vec![]),
118            Err(Error::InvalidLayoutTag(0x00))
119        ));
120    }
121
122    #[test]
123    fn rejects_invalid_sentinel_0xff() {
124        assert!(matches!(
125            UnknownLayout::new(0xFF, vec![]),
126            Err(Error::InvalidLayoutTag(0xFF))
127        ));
128    }
129
130    #[test]
131    fn accepts_reserved_range_tag_in_permissive_mode() {
132        // Reserved tags are not permanently invalid — permissive mode may accept them.
133        assert!(UnknownLayout::new(0x0C, vec![]).is_ok());
134        assert!(UnknownLayout::new(0x3F, vec![]).is_ok());
135        assert!(UnknownLayout::new(0xEF, vec![]).is_ok());
136    }
137
138    #[test]
139    fn rejects_every_named_tag() {
140        // Wrapping a tag this crate understands would skip the checks its named
141        // variant applies, while still encoding to that tag on the wire.
142        for tag in [
143            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x40,
144        ] {
145            assert!(
146                matches!(UnknownLayout::new(tag, vec![]), Err(Error::NamedLayoutTag(t)) if t == tag),
147                "tag 0x{tag:02X} must be rejected as named"
148            );
149        }
150    }
151
152    #[test]
153    fn rejects_private_tags_in_favour_of_the_extension_type() {
154        // PrivateExtensionLayout keeps the extension id; Unknown would flatten it.
155        assert!(matches!(
156            UnknownLayout::new(0xF0, vec![]),
157            Err(Error::PrivateLayoutTag(0xF0))
158        ));
159        assert!(matches!(
160            UnknownLayout::new(0xFE, vec![]),
161            Err(Error::PrivateLayoutTag(0xFE))
162        ));
163    }
164}