Skip to main content

darkbio_crypto/cwt/claims/
eat.rs

1// crypto-rs: cryptography primitives and wrappers
2// Copyright 2026 Dark Bio AG. All rights reserved.
3//
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7//! EAT (Entity Attestation Token) claims.
8//!
9//! <https://datatracker.ietf.org/doc/html/rfc9711>
10//!
11//! These types encode claims and validate their supported wire representations.
12//! Applications must evaluate the claims against their attestation policy and
13//! enforce RFC 9711's relationships between claims. For example, `hwmodel` and
14//! `oemboot` require `oemid`, `hwversion` requires `hwmodel`, and `swversion`
15//! requires `swname`. [`DebugState::DisabledPermanently`] also requires `oemid`.
16//! These relationships are not checked by [`crate::cwt::verify`].
17
18use crate::cbor::{
19    self, Cbor, Decode, Encode, MapDecode, MapEncode, MapEncodeBuffer, MapEntryAccess,
20};
21
22/// A globally unique device identifier such as a serial number
23/// or IMEI (key 256). The value is an opaque byte string including a
24/// type prefix byte per RFC 9711 Section 4.2.1.
25///
26/// A RAND UEID uses prefix `0x01` followed by 16, 24, or 32 bytes of random
27/// identifier data provisioned once for the device. This type stores the bytes
28/// as supplied; callers must validate the prefix, length, and identifier policy.
29#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
30pub struct Ueid {
31    /// Opaque device identifier, its first byte the RFC 9711 type prefix.
32    #[cbor(key = 256)]
33    pub ueid: Vec<u8>,
34}
35
36/// The hardware manufacturer's identifier (key 258, RFC 9711 Section 4.2.3).
37/// The OEM can be identified by a random ID, an IEEE OUI, or an IANA PEN.
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct Oemid {
40    value: OemidValue,
41}
42
43impl Oemid {
44    /// Creates an OEMID from a 16-byte random manufacturer identifier.
45    pub fn new_random(id: [u8; 16]) -> Self {
46        Self {
47            value: OemidValue::Random(id),
48        }
49    }
50
51    /// Creates an OEMID from a 3-byte IEEE OUI/MA-L.
52    pub fn new_ieee(id: [u8; 3]) -> Self {
53        Self {
54            value: OemidValue::Ieee(id),
55        }
56    }
57
58    /// Creates an OEMID from an IANA Private Enterprise Number.
59    pub fn new_pen(pen: u64) -> Self {
60        Self {
61            value: OemidValue::Pen(pen),
62        }
63    }
64
65    /// Returns the 16-byte random OEM ID, or None if this is not a random OEM ID.
66    pub fn random(&self) -> Option<[u8; 16]> {
67        match self.value {
68            OemidValue::Random(id) => Some(id),
69            _ => None,
70        }
71    }
72
73    /// Returns the 3-byte IEEE OUI/MA-L, or None if this is not an IEEE OEM ID.
74    pub fn ieee(&self) -> Option<[u8; 3]> {
75        match self.value {
76            OemidValue::Ieee(id) => Some(id),
77            _ => None,
78        }
79    }
80
81    /// Returns the IANA Private Enterprise Number, or None if this is not a PEN OEM ID.
82    pub fn pen(&self) -> Option<u64> {
83        match self.value {
84            OemidValue::Pen(pen) => Some(pen),
85            _ => None,
86        }
87    }
88}
89
90/// Internal representation of the three OEM ID formats.
91#[derive(Clone, Debug, PartialEq, Eq)]
92enum OemidValue {
93    Random([u8; 16]),
94    Ieee([u8; 3]),
95    Pen(u64),
96}
97
98impl Encode for OemidValue {
99    fn encode_cbor_to(&self, buf: &mut Vec<u8>) -> Result<(), cbor::Error> {
100        match self {
101            OemidValue::Random(id) => id.as_slice().encode_cbor_to(buf),
102            OemidValue::Ieee(id) => id.as_slice().encode_cbor_to(buf),
103            OemidValue::Pen(pen) => pen.encode_cbor_to(buf),
104        }
105    }
106}
107
108impl Decode for OemidValue {
109    fn decode_cbor(data: &[u8]) -> Result<Self, cbor::Error> {
110        let mut dec = cbor::Decoder::new(data);
111        let result = Self::decode_cbor_notrail(&mut dec)?;
112        dec.finish()?;
113        Ok(result)
114    }
115
116    fn decode_cbor_notrail(dec: &mut cbor::Decoder<'_>) -> Result<Self, cbor::Error> {
117        // Peek at major type to determine format: uint -> PEN, bytes -> Random or IEEE
118        if dec.peek_uint().is_ok() {
119            let pen = dec.decode_uint()?;
120            return Ok(OemidValue::Pen(pen));
121        }
122        // Not an integer, must be bytes
123        let bytes = dec.decode_bytes()?;
124        match bytes.len() {
125            3 => {
126                let mut id = [0u8; 3];
127                id.copy_from_slice(&bytes);
128                Ok(OemidValue::Ieee(id))
129            }
130            16 => {
131                let mut id = [0u8; 16];
132                id.copy_from_slice(&bytes);
133                Ok(OemidValue::Random(id))
134            }
135            n => Err(cbor::Error::DecodeFailed(format!(
136                "oemid: unexpected bstr length {n}"
137            ))),
138        }
139    }
140}
141
142impl Encode for Oemid {
143    fn encode_cbor_to(&self, buf: &mut Vec<u8>) -> Result<(), cbor::Error> {
144        let mut enc = MapEncodeBuffer::new(1);
145        <Self as MapEncode>::encode_map(self, &mut enc)?;
146        enc.finish_to(buf)
147    }
148}
149
150impl Decode for Oemid {
151    fn decode_cbor(data: &[u8]) -> Result<Self, cbor::Error> {
152        let mut dec = cbor::Decoder::new(data);
153        let result = Self::decode_cbor_notrail(&mut dec)?;
154        dec.finish()?;
155        Ok(result)
156    }
157
158    fn decode_cbor_notrail(dec: &mut cbor::Decoder<'_>) -> Result<Self, cbor::Error> {
159        let entries = cbor::decode_map_entries_slices_notrail(dec)?;
160        let mut remaining = cbor::MapEntries::new(entries);
161        let value = <Self as MapDecode>::decode_map(&mut remaining)?;
162        if !remaining.is_empty() {
163            let unknown: Vec<i64> = remaining.remaining_keys();
164            return Err(cbor::Error::DecodeFailed(format!(
165                "unknown CBOR map keys: {unknown:?}"
166            )));
167        }
168        Ok(value)
169    }
170}
171
172impl MapEncode for Oemid {
173    fn encode_map(&self, enc: &mut MapEncodeBuffer) -> Result<(), cbor::Error> {
174        enc.push(258, &self.value)
175    }
176}
177
178impl MapDecode for Oemid {
179    fn cbor_map_keys() -> &'static [i64] {
180        &[258]
181    }
182
183    fn decode_map<'a, E: MapEntryAccess<'a>>(entries: &mut E) -> Result<Self, cbor::Error> {
184        let raw = entries
185            .take(258)
186            .ok_or(cbor::Error::DecodeFailed("missing required key 258".into()))?;
187        let value = OemidValue::decode_cbor(raw)?;
188        Ok(Self { value })
189    }
190}
191
192/// The product or board model identifier (key 259).
193#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
194pub struct HwModel {
195    /// Opaque model identifier, as the manufacturer defines it.
196    #[cbor(key = 259)]
197    pub hw_model: Vec<u8>,
198}
199
200/// The hardware revision identifier (key 260).
201/// CBOR-encodes as a 1-element array per RFC 9711 Section 4.2.5:
202/// `[version: tstr]`. The optional scheme is not supported.
203#[derive(Clone, Debug, PartialEq, Eq)]
204pub struct HwVersion {
205    version: String,
206}
207
208impl HwVersion {
209    /// Creates an HwVersion with the given version string.
210    pub fn new(version: String) -> Self {
211        Self { version }
212    }
213
214    /// Returns the hardware version string.
215    pub fn version(&self) -> &str {
216        &self.version
217    }
218}
219
220/// Internal 1-element array for HW/SW version encoding.
221#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
222#[cbor(array)]
223struct VersionArray {
224    version: String,
225}
226
227impl Encode for HwVersion {
228    fn encode_cbor_to(&self, buf: &mut Vec<u8>) -> Result<(), cbor::Error> {
229        let mut enc = MapEncodeBuffer::new(1);
230        <Self as MapEncode>::encode_map(self, &mut enc)?;
231        enc.finish_to(buf)
232    }
233}
234
235impl Decode for HwVersion {
236    fn decode_cbor(data: &[u8]) -> Result<Self, cbor::Error> {
237        let mut dec = cbor::Decoder::new(data);
238        let result = Self::decode_cbor_notrail(&mut dec)?;
239        dec.finish()?;
240        Ok(result)
241    }
242
243    fn decode_cbor_notrail(dec: &mut cbor::Decoder<'_>) -> Result<Self, cbor::Error> {
244        let entries = cbor::decode_map_entries_slices_notrail(dec)?;
245        let mut remaining = cbor::MapEntries::new(entries);
246        let value = <Self as MapDecode>::decode_map(&mut remaining)?;
247        if !remaining.is_empty() {
248            let unknown: Vec<i64> = remaining.remaining_keys();
249            return Err(cbor::Error::DecodeFailed(format!(
250                "unknown CBOR map keys: {unknown:?}"
251            )));
252        }
253        Ok(value)
254    }
255}
256
257impl MapEncode for HwVersion {
258    fn encode_map(&self, enc: &mut MapEncodeBuffer) -> Result<(), cbor::Error> {
259        let arr = VersionArray {
260            version: self.version.clone(),
261        };
262        enc.push(260, &arr)
263    }
264}
265
266impl MapDecode for HwVersion {
267    fn cbor_map_keys() -> &'static [i64] {
268        &[260]
269    }
270
271    fn decode_map<'a, E: MapEntryAccess<'a>>(entries: &mut E) -> Result<Self, cbor::Error> {
272        let raw = entries
273            .take(260)
274            .ok_or(cbor::Error::DecodeFailed("missing required key 260".into()))?;
275        let arr = VersionArray::decode_cbor(raw)?;
276        Ok(Self {
277            version: arr.version,
278        })
279    }
280}
281
282/// The number of seconds since the last boot (key 261).
283#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
284pub struct Uptime {
285    /// Seconds elapsed since the device last booted.
286    #[cbor(key = 261)]
287    pub uptime: u64,
288}
289
290/// Whether the boot chain is OEM-authorized,
291/// i.e. secure boot passed (key 262).
292#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
293pub struct OemBoot {
294    /// True when every boot stage was OEM authorized.
295    #[cbor(key = 262)]
296    pub oem_boot: bool,
297}
298
299/// The debug port state per RFC 9711 Section 4.2.9.
300#[derive(Clone, Copy, Debug, PartialEq, Eq)]
301#[repr(u64)]
302pub enum DebugState {
303    /// Debug is currently enabled.
304    Enabled = 0,
305    /// Debug is currently disabled.
306    Disabled = 1,
307    /// Debug was disabled at boot and has not been enabled since.
308    DisabledSinceBoot = 2,
309    /// All debug has been disabled since boot. End users and developers cannot
310    /// re-enable it, but the manufacturer identified by `oemid` may do so.
311    /// The `oemid` claim must be present; the application must enforce this.
312    DisabledPermanently = 3,
313    /// All debug facilities are permanently disabled, including manufacturer
314    /// facilities; none can be re-enabled.
315    DisabledFullyPermanently = 4,
316}
317
318impl Encode for DebugState {
319    fn encode_cbor_to(&self, buf: &mut Vec<u8>) -> Result<(), cbor::Error> {
320        (*self as u64).encode_cbor_to(buf)
321    }
322}
323
324impl Decode for DebugState {
325    fn decode_cbor(data: &[u8]) -> Result<Self, cbor::Error> {
326        let mut dec = cbor::Decoder::new(data);
327        let result = Self::decode_cbor_notrail(&mut dec)?;
328        dec.finish()?;
329        Ok(result)
330    }
331
332    fn decode_cbor_notrail(dec: &mut cbor::Decoder<'_>) -> Result<Self, cbor::Error> {
333        let value = dec.decode_uint()?;
334        match value {
335            0 => Ok(DebugState::Enabled),
336            1 => Ok(DebugState::Disabled),
337            2 => Ok(DebugState::DisabledSinceBoot),
338            3 => Ok(DebugState::DisabledPermanently),
339            4 => Ok(DebugState::DisabledFullyPermanently),
340            _ => Err(cbor::Error::DecodeFailed(format!(
341                "invalid debug state: {value}"
342            ))),
343        }
344    }
345}
346
347/// The debug port state (key 263).
348#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
349pub struct DebugStatus {
350    /// State of the device's debug facilities at attestation time.
351    #[cbor(key = 263)]
352    pub debug_status: DebugState,
353}
354
355/// The number of times the device has booted,
356/// as a monotonic counter (key 267).
357#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
358pub struct BootCount {
359    /// Number of boots so far, never decreasing.
360    #[cbor(key = 267)]
361    pub boot_count: u64,
362}
363
364/// A random value unique to the current boot cycle (key 268).
365#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
366pub struct BootSeed {
367    /// Random bytes drawn at boot, the same in every token of one boot cycle.
368    #[cbor(key = 268)]
369    pub boot_seed: Vec<u8>,
370}
371
372/// The name of the firmware or software running on the
373/// device (key 270).
374#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
375pub struct SwName {
376    /// Name of the running firmware or software.
377    #[cbor(key = 270)]
378    pub sw_name: String,
379}
380
381/// The software version identifier (key 271).
382/// CBOR-encodes as a 1-element array per RFC 9711 Section 4.2.7:
383/// `[version: tstr]`. The optional scheme is not supported.
384#[derive(Clone, Debug, PartialEq, Eq)]
385pub struct SwVersion {
386    version: String,
387}
388
389impl SwVersion {
390    /// Creates a SwVersion with the given version string.
391    pub fn new(version: String) -> Self {
392        Self { version }
393    }
394
395    /// Returns the software version string.
396    pub fn version(&self) -> &str {
397        &self.version
398    }
399}
400
401impl Encode for SwVersion {
402    fn encode_cbor_to(&self, buf: &mut Vec<u8>) -> Result<(), cbor::Error> {
403        let mut enc = MapEncodeBuffer::new(1);
404        <Self as MapEncode>::encode_map(self, &mut enc)?;
405        enc.finish_to(buf)
406    }
407}
408
409impl Decode for SwVersion {
410    fn decode_cbor(data: &[u8]) -> Result<Self, cbor::Error> {
411        let mut dec = cbor::Decoder::new(data);
412        let result = Self::decode_cbor_notrail(&mut dec)?;
413        dec.finish()?;
414        Ok(result)
415    }
416
417    fn decode_cbor_notrail(dec: &mut cbor::Decoder<'_>) -> Result<Self, cbor::Error> {
418        let entries = cbor::decode_map_entries_slices_notrail(dec)?;
419        let mut remaining = cbor::MapEntries::new(entries);
420        let value = <Self as MapDecode>::decode_map(&mut remaining)?;
421        if !remaining.is_empty() {
422            let unknown: Vec<i64> = remaining.remaining_keys();
423            return Err(cbor::Error::DecodeFailed(format!(
424                "unknown CBOR map keys: {unknown:?}"
425            )));
426        }
427        Ok(value)
428    }
429}
430
431impl MapEncode for SwVersion {
432    fn encode_map(&self, enc: &mut MapEncodeBuffer) -> Result<(), cbor::Error> {
433        let arr = VersionArray {
434            version: self.version.clone(),
435        };
436        enc.push(271, &arr)
437    }
438}
439
440impl MapDecode for SwVersion {
441    fn cbor_map_keys() -> &'static [i64] {
442        &[271]
443    }
444
445    fn decode_map<'a, E: MapEntryAccess<'a>>(entries: &mut E) -> Result<Self, cbor::Error> {
446        let raw = entries
447            .take(271)
448            .ok_or(cbor::Error::DecodeFailed("missing required key 271".into()))?;
449        let arr = VersionArray::decode_cbor(raw)?;
450        Ok(Self {
451            version: arr.version,
452        })
453    }
454}
455
456/// The token's intended purpose per RFC 9711 Section 4.3.3.
457#[derive(Clone, Copy, Debug, PartialEq, Eq)]
458#[repr(u64)]
459pub enum Use {
460    /// General-purpose attestation.
461    Generic = 1,
462    /// Attestation for service registration.
463    Registration = 2,
464    /// Attestation prior to key/config provisioning.
465    Provisioning = 3,
466    /// Attestation for certificate signing requests.
467    CertIssuance = 4,
468    /// Attestation accompanying a proof-of-possession.
469    ProofOfPossession = 5,
470}
471
472impl Encode for Use {
473    fn encode_cbor_to(&self, buf: &mut Vec<u8>) -> Result<(), cbor::Error> {
474        (*self as u64).encode_cbor_to(buf)
475    }
476}
477
478impl Decode for Use {
479    fn decode_cbor(data: &[u8]) -> Result<Self, cbor::Error> {
480        let mut dec = cbor::Decoder::new(data);
481        let result = Self::decode_cbor_notrail(&mut dec)?;
482        dec.finish()?;
483        Ok(result)
484    }
485
486    fn decode_cbor_notrail(dec: &mut cbor::Decoder<'_>) -> Result<Self, cbor::Error> {
487        let value = dec.decode_uint()?;
488        match value {
489            1 => Ok(Use::Generic),
490            2 => Ok(Use::Registration),
491            3 => Ok(Use::Provisioning),
492            4 => Ok(Use::CertIssuance),
493            5 => Ok(Use::ProofOfPossession),
494            _ => Err(cbor::Error::DecodeFailed(format!(
495                "invalid intended use: {value}"
496            ))),
497        }
498    }
499}
500
501/// The token's purpose (key 275).
502#[derive(Clone, Debug, PartialEq, Eq, Cbor)]
503pub struct IntendedUse {
504    /// Purpose the token was issued for.
505    #[cbor(key = 275)]
506    pub intended_use: Use,
507}
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512
513    /// Verifies round-trip encoding of a random OEM ID.
514    #[test]
515    fn test_oemid_random() {
516        let id: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
517
518        #[derive(Debug, Cbor)]
519        struct Token {
520            #[cbor(embed)]
521            oemid: Oemid,
522        }
523        let orig = Token {
524            oemid: Oemid::new_random(id),
525        };
526        let data = cbor::encode(&orig).expect("encode");
527        let got = Token::decode_cbor(&data).expect("decode");
528
529        assert_eq!(got.oemid.random(), Some(id));
530    }
531
532    /// Verifies round-trip encoding of an IEEE OEM ID.
533    #[test]
534    fn test_oemid_ieee() {
535        let id: [u8; 3] = [0xAC, 0xDE, 0x48];
536
537        #[derive(Debug, Cbor)]
538        struct Token {
539            #[cbor(embed)]
540            oemid: Oemid,
541        }
542        let orig = Token {
543            oemid: Oemid::new_ieee(id),
544        };
545        let data = cbor::encode(&orig).expect("encode");
546        let got = Token::decode_cbor(&data).expect("decode");
547
548        assert_eq!(got.oemid.ieee(), Some(id));
549    }
550
551    /// Verifies round-trip encoding of a PEN OEM ID.
552    #[test]
553    fn test_oemid_pen() {
554        let pen: u64 = 76543;
555
556        #[derive(Debug, Cbor)]
557        struct Token {
558            #[cbor(embed)]
559            oemid: Oemid,
560        }
561        let orig = Token {
562            oemid: Oemid::new_pen(pen),
563        };
564        let data = cbor::encode(&orig).expect("encode");
565        let got = Token::decode_cbor(&data).expect("decode");
566
567        assert_eq!(got.oemid.pen(), Some(pen));
568    }
569
570    /// Verifies that accessors return None for mismatched types.
571    #[test]
572    fn test_oemid_wrong_accessor() {
573        let o = Oemid::new_pen(42);
574        assert_eq!(o.random(), None);
575        assert_eq!(o.ieee(), None);
576    }
577
578    /// Verifies round-trip encoding of a hardware version.
579    #[test]
580    fn test_hw_version() {
581        #[derive(Debug, Cbor)]
582        struct Token {
583            #[cbor(embed)]
584            hw: HwVersion,
585        }
586        let orig = Token {
587            hw: HwVersion::new("1.2.3".into()),
588        };
589        let data = cbor::encode(&orig).expect("encode");
590        let got = Token::decode_cbor(&data).expect("decode");
591
592        assert_eq!(got.hw.version(), "1.2.3");
593    }
594
595    /// Verifies round-trip encoding of a software version.
596    #[test]
597    fn test_sw_version() {
598        #[derive(Debug, Cbor)]
599        struct Token {
600            #[cbor(embed)]
601            sw: SwVersion,
602        }
603        let orig = Token {
604            sw: SwVersion::new("4.5.6".into()),
605        };
606        let data = cbor::encode(&orig).expect("encode");
607        let got = Token::decode_cbor(&data).expect("decode");
608
609        assert_eq!(got.sw.version(), "4.5.6");
610    }
611
612    /// Verifies round-trip encoding of the simple EAT claims.
613    #[test]
614    fn test_simple_claims() {
615        #[derive(Debug, Cbor)]
616        struct Token {
617            #[cbor(embed)]
618            ueid: Ueid,
619            #[cbor(embed)]
620            hw_model: HwModel,
621            #[cbor(embed)]
622            uptime: Uptime,
623            #[cbor(embed)]
624            oem_boot: OemBoot,
625            #[cbor(embed)]
626            debug_status: DebugStatus,
627            #[cbor(embed)]
628            boot_count: BootCount,
629            #[cbor(embed)]
630            boot_seed: BootSeed,
631            #[cbor(embed)]
632            sw_name: SwName,
633            #[cbor(embed)]
634            intended_use: IntendedUse,
635        }
636        let orig = Token {
637            ueid: Ueid {
638                ueid: vec![0x01, 0x02, 0x03],
639            },
640            hw_model: HwModel {
641                hw_model: b"board-v2".to_vec(),
642            },
643            uptime: Uptime { uptime: 3600 },
644            oem_boot: OemBoot { oem_boot: true },
645            debug_status: DebugStatus {
646                debug_status: DebugState::DisabledPermanently,
647            },
648            boot_count: BootCount { boot_count: 42 },
649            boot_seed: BootSeed {
650                boot_seed: vec![0xDE, 0xAD],
651            },
652            sw_name: SwName {
653                sw_name: "firmware-v3".into(),
654            },
655            intended_use: IntendedUse {
656                intended_use: Use::CertIssuance,
657            },
658        };
659        let data = cbor::encode(&orig).expect("encode");
660        let got = Token::decode_cbor(&data).expect("decode");
661
662        assert_eq!(got.ueid.ueid, vec![0x01, 0x02, 0x03]);
663        assert_eq!(got.hw_model.hw_model, b"board-v2");
664        assert_eq!(got.uptime.uptime, 3600);
665        assert!(got.oem_boot.oem_boot);
666        assert_eq!(
667            got.debug_status.debug_status,
668            DebugState::DisabledPermanently
669        );
670        assert_eq!(got.boot_count.boot_count, 42);
671        assert_eq!(got.boot_seed.boot_seed, vec![0xDE, 0xAD]);
672        assert_eq!(got.sw_name.sw_name, "firmware-v3");
673        assert_eq!(got.intended_use.intended_use, Use::CertIssuance);
674    }
675
676    /// Tests that a byte string of invalid length is rejected for OEMID.
677    #[test]
678    fn test_oemid_invalid_length() {
679        let mut enc = cbor::Encoder::new();
680        enc.encode_map_header(1);
681        enc.encode_int(258);
682        enc.encode_bytes(&[1, 2, 3, 4, 5]); // 5 bytes: neither 3 nor 16
683
684        #[derive(Debug, Cbor)]
685        struct Token {
686            #[cbor(embed)]
687            oemid: Oemid,
688        }
689        assert!(Token::decode_cbor(&enc.finish()).is_err());
690    }
691
692    /// Tests that a non-bstr/uint CBOR type is rejected for OEMID.
693    #[test]
694    fn test_oemid_invalid_type() {
695        let mut enc = cbor::Encoder::new();
696        enc.encode_map_header(1);
697        enc.encode_int(258);
698        enc.encode_text("not-bytes");
699
700        #[derive(Debug, Cbor)]
701        struct Token {
702            #[cbor(embed)]
703            oemid: Oemid,
704        }
705        assert!(Token::decode_cbor(&enc.finish()).is_err());
706    }
707
708    /// Tests that an out-of-range debug state is rejected.
709    #[test]
710    fn test_debug_state_invalid() {
711        let mut enc = cbor::Encoder::new();
712        enc.encode_map_header(1);
713        enc.encode_int(263);
714        enc.encode_uint(99); // not in 0..4
715
716        #[derive(Debug, Cbor)]
717        struct Token {
718            #[cbor(embed)]
719            debug: DebugStatus,
720        }
721        assert!(Token::decode_cbor(&enc.finish()).is_err());
722    }
723
724    /// Tests that an out-of-range intended use is rejected.
725    #[test]
726    fn test_use_invalid() {
727        let mut enc = cbor::Encoder::new();
728        enc.encode_map_header(1);
729        enc.encode_int(275);
730        enc.encode_uint(0); // not in 1..5
731
732        #[derive(Debug, Cbor)]
733        struct Token {
734            #[cbor(embed)]
735            intended_use: IntendedUse,
736        }
737        assert!(Token::decode_cbor(&enc.finish()).is_err());
738    }
739}