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}