ear 0.5.0

EAT Attestation Results implementation
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
use std::fmt::Display;

// SPDX-License-Identifier: Apache-2.0
use crate::error::Error;

use phf::{phf_map, Map};

use super::tier::TrustTier;

/// Description of a trustworthiness claim
#[derive(Debug, Clone)]
pub struct ClaimDescripiton<'a> {
    /// The key under which the claim is serialized in CBOR
    pub key: i8,
    /// The name under which the claim is serialized in JSON
    pub name: &'a str,
}

/// Description of the claim value
#[derive(Debug, Clone)]
pub struct ValueDescription<'a> {
    /// String tag given to the claim value
    pub tag: &'a str,
    /// A short description of the claim value
    ///
    /// This is intended to be used in error messages etc.
    pub short: &'a str,
    /// A longer description of the claim value
    ///
    /// This is a longer explanation of what the value is intended to represent.
    pub long: &'a str,
}

pub const VERIFIER_MALFUNCTION: i8 = -1;
pub const NO_CLAIM: i8 = 0;
pub const UNEXPECTED_EVIDENCE: i8 = 1;
pub const CRYPTO_VALIDATION_FAILED: i8 = 99;

// NOTE: a limitation of phf_map macro is that it cannot look up constant definitions, hence the
// use of literal in the keys below.

/// Values common to all claims.
pub static COMMON_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_map! {
    -1i8 => ValueDescription{
        tag: "verifier_malfunction",
        short: "verifier malfunction",
        long:  "A verifier malfunction occurred during evidence appraisal."
    },
    0i8 => ValueDescription{
        tag: "no_claim",
        short: "no claim is being made",
        long:  "The evidence received is insufficient to make a conclusion."
    },
    1i8 => ValueDescription{
        tag: "unexpected_evidence",
        short: "unexpected evidence",
        long:  "The evidence received contains unexpected elements which the \
                verifier is unable to parse."
    },
    99i8 => ValueDescription{
        tag:   "crypto_failed",
        short: "cryptographic validation failed",
        long:  "Cryptographic validation of the Evidence has failed.",
    },
};

pub static INSTANCE_CLAIM_DESC: &ClaimDescripiton<'static> = &ClaimDescripiton {
    key: 0,
    name: "instance-identity",
};

pub const TRUSTWORTHY_INSTANCE: i8 = 2;
pub const UNTRUSTWORTHY_INSTANCE: i8 = 96;
pub const UNRECOGNIZED_INSTANCE: i8 = 97;

pub static INSTANCE_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_map! {
    2i8 => ValueDescription{
        tag:   "recognized_instance",
        short: "trustworthy instance",
        long:  "The Attesting Environment is recognized, and the associated \
                instance of the Attester is not known to be compromised.",
    },
    96i8 => ValueDescription{
        tag:   "untrustworthy_instance",
        short: "recognized but not trustworthy",
        long:  "The Attesting Environment is recognized, but its unique private key \
                indicates a device which is not trustworthy.",
    },
    97i8 => ValueDescription{
        tag:   "unrecognized_instance",
        short: "not recognized",
        long:  "The Attesting Environment is not recognized; however the verifier \
                believes it should be.",
    },
};

pub static CONFIG_CLAIM_DESC: &ClaimDescripiton<'static> = &ClaimDescripiton {
    key: 1,
    name: "configuration",
};

pub const APPROVED_CONFIG: i8 = 2;
pub const NO_CONFIG_VULNS: i8 = 3;
pub const UNSAFE_CONFIG: i8 = 32;
pub const UNAVAIL_CONFIG_ELEMS: i8 = 36;
pub const UNSUPPORTABLE_CONFIG: i8 = 96;

pub static CONFIG_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_map! {
    2i8 => ValueDescription{
        tag:   "approved_config",
        short: "all recognized and approved",
        long:  "The configuration is a known and approved config.",
    },
    3i8 => ValueDescription{
        tag:   "safe_config",
        short: "no known vulnerabilities",
        long:  "The configuration includes or exposes no known vulnerabilities",
    },
    32i8 => ValueDescription{
        tag:   "unsafe_config",
        short: "known vulnerabilities",
        long:  "The configuration includes or exposes known vulnerabilities.",
    },
    36i8 => ValueDescription{
        tag:   "unavailable_config",
        short: "config elements unavailable",
        long:  "Elements of the configuration relevant to security are unavailable \
                to the Verifier.",
    },
    96i8 => ValueDescription{
        tag:   "unsupportable_config",
        short: "unacceptable security vulnerabilities",
        long:  "The configuration is unsupportable as it exposes unacceptable \
                security vulnerabilities",
    },
};

pub static EXECUTABLES_CLAIM_DESC: &ClaimDescripiton<'static> = &ClaimDescripiton {
    key: 2,
    name: "executables",
};

pub const APPROVED_RUNTIME: i8 = 2;
pub const APPROVED_BOOT: i8 = 3;
pub const UNSAFE_RUNTIME: i8 = 32;
pub const UNRECOGNIZED_RUNTIME: i8 = 33;
pub const CONTRAINDICATED_RUNTIME: i8 = 96;

pub static EXECUTABLES_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_map! {
    2i8 => ValueDescription{
        tag:   "approved_rt",
        short: "recognized and approved boot- and run-time",
        long:  "Only a recognized genuine set of approved executables, scripts, files, \
                and/or objects have been loaded during and after the boot process.",
    },
    3i8 => ValueDescription{
            tag:   "approved_boot",
            short: "recognized and approved boot-time",
            long:  "Only a recognized genuine set of approved executables have been \
                    loaded during the boot process.",
    },
    32i8 => ValueDescription{
        tag:   "unsafe_rt",
        short: "recognized but known bugs or vulnerabilities",
        long:  "Only a recognized genuine set of executables, scripts, files, and/or \
                objects have been loaded. However the Verifier cannot vouch for a subset \
                of these due to known bugs or other known vulnerabilities.",
    },
    33i8 => ValueDescription{
        tag:   "unrecognized_rt",
        short: "unrecognized run-time",
        long:  "Runtime memory includes executables, scripts, files, and/or objects which \
                are not recognized.",
    },
    96i8 => ValueDescription{
        tag:   "contraindicated_rt",
        short: "contraindicated run-time",
        long:  "Runtime memory includes executables, scripts, files, and/or object which \
                are contraindicated.",
    },
};

pub static FILE_SYSTEM_CLAIM_DESC: &ClaimDescripiton<'static> = &ClaimDescripiton {
    key: 3,
    name: "file-system",
};

pub const APPROVED_FILES: i8 = 2;
pub const UNRECOGNIZED_FILES: i8 = 32;
pub const CONTRAINDICATED_FILES: i8 = 96;

pub static FILE_SYSTEM_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_map! {
    2i8 => ValueDescription{
        tag:   "approved_fs",
        short: "all recognized and approved",
        long:  "Only a recognized set of approved files are found.",
    },
    32i8 => ValueDescription{
        tag:   "unrecognized_fs",
        short: "unrecognized item(s) found",
        long:  "The file system includes unrecognized executables, scripts, or files.",
    },
    96i8 => ValueDescription{
        tag:   "contraindicated_fs",
        short: "contraindicated item(s) found",
        long:  "The file system includes contraindicated executables, scripts, or files.",
    },
};

pub static HARDWARE_CLAIM_DESC: &ClaimDescripiton<'static> = &ClaimDescripiton {
    key: 4,
    name: "hardware",
};

pub const GENUINE_HARDWARE: i8 = 2;
pub const UNSAFE_HARDWARE: i8 = 32;
pub const CONTRAINDICATED_HARDWARE: i8 = 96;
pub const UNRECOGNIZED_HARDWARE: i8 = 97;

pub static HARDWARE_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_map! {
    2i8 => ValueDescription{
        tag:   "genuine_hw",
        short: "genuine",
        long:  "An Attester has passed its hardware and/or firmware verifications \
                needed to demonstrate that these are genuine/supported.",
    },
    32i8 => ValueDescription{
        tag:   "unsafe_hw",
        short: "genuine but known bugs or vulnerabilities",
        long:  "An Attester contains only genuine/supported hardware and/or firmware, \
                but there are known security vulnerabilities.",
    },
    96i8 => ValueDescription{
        tag:   "contraindicated_hw",
        short: "genuine but contraindicated",
        long:  "Attester hardware and/or firmware is recognized, but its trustworthiness \
                is contraindicated.",
    },
    97i8 => ValueDescription{
        tag:   "unrecognized_hw",
        short: "unrecognized",
        long:  "A Verifier does not recognize an Attester's hardware or firmware, but it \
                should be recognized.",
    },
};

pub static RUNTIME_CLAIM_DESC: &ClaimDescripiton<'static> = &ClaimDescripiton {
    key: 5,
    name: "runtime-opaque",
};

pub const ENCRYPTED_MEMORY_RUNTIME: i8 = 2;
pub const ISOLATED_MEMORY_RUNTIME: i8 = 32;
pub const VISIBLE_MEMORY_RUNTIME: i8 = 96;

pub static RUNTIME_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_map! {
    2i8 => ValueDescription{
        tag:   "encrypted_rt",
        short: "memory encryption",
        long:  "the Attester's executing Target Environment and Attesting Environments \
                are encrypted and within Trusted Execution Environment(s) opaque to \
                the operating system, virtual machine manager, and peer applications.",
    },
    32i8 => ValueDescription{
        tag:   "isolated_rt",
        short: "memory isolation",
        long:  "the Attester's executing Target Environment and Attesting Environments \
                are inaccessible from any other parallel application or Guest VM running \
                on the Attester's physical device.",
    },
    96i8 => ValueDescription{
        tag:   "visible_rt",
        short: "visible",
        long:  "The Verifier has concluded that in memory objects are unacceptably visible \
                within the physical host that supports the Attester.",
    },
};

pub static STORAGE_CLAIM_DESC: &ClaimDescripiton<'static> = &ClaimDescripiton {
    key: 6,
    name: "storage-opaque",
};

pub const HW_KEYS_ENCRYPTED_SECRETS: i8 = 2;
pub const SW_KEYS_ENCRYPTED_SECRETS: i8 = 32;
pub const UNENCRYPTED_SECRETS: i8 = 96;

pub static STORAGE_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_map! {
    2i8 => ValueDescription{
        tag:   "hw_encrypted_secrets",
        short: "encrypted secrets with HW-backed keys",
        long:  "the Attester encrypts all secrets in persistent storage via using keys \
                which are never visible outside an HSM or the Trusted Execution Environment \
                hardware.",
    },
    32i8 => ValueDescription{
        tag:   "sw_encrypted_secrets",
        short: "encrypted secrets with non HW-backed keys",
        long:  "the Attester encrypts all persistently stored secrets, but without using \
                hardware backed keys.",
    },
    96i8 => ValueDescription{
        tag:   "unencrypted_secrets",
        short: "unencrypted secrets",
        long:  "There are persistent secrets which are stored unencrypted in an Attester.",
    },
};

pub static SOURCED_DATA_CLAIM_DESC: &ClaimDescripiton<'static> = &ClaimDescripiton {
    key: 7,
    name: "sourced-data",
};

pub const TRUSTED_SOURCES: i8 = 2;
pub const UNTRUSTED_SOURCES: i8 = 32;
pub const CONTRAINDICATED_SOURCES: i8 = 96;

pub static SOURCED_DATA_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_map! {
    2i8 => ValueDescription{
        tag:   "trusted_sources",
        short: "from attesters in the affirming tier",
        long:  "All essential Attester source data objects have been provided by other \
                Attester(s) whose most recent appraisal(s) had both no Trustworthiness \
                Claims of \"0\" where the current Trustworthiness Claim is \"Affirmed\", \
                as well as no \"Warning\" or \"Contraindicated\" Trustworthiness Claims.",
    },
    32i8 => ValueDescription{
        tag:   "untrusted_sources",
        short: "from unattested sources or attesters in the warning tier",
        long:  "Attester source data objects come from unattested sources, or attested \
                sources with \"Warning\" type Trustworthiness Claims",
    },
    96i8 => ValueDescription{
        tag:   "contraindicated_sources",
        short: "from attesters in the contraindicated tier",
        long:  "Attester source data objects come from contraindicated sources.",
    },
};

/// A trustworthiness claim
///
/// This is a claim regarding the trustworthiness of one aspect of the attested environment, as
/// defined in
/// <https://datatracker.ietf.org/doc/html/draft-ietf-rats-ar4si-04#name-trustworthiness-claims>
#[derive(Clone, Copy)]
pub struct TrustClaim {
    /// Claim value
    pub value: Option<i8>,
    desc: &'static ClaimDescripiton<'static>,
    value_desc: &'static Map<i8, ValueDescription<'static>>,
}

impl TrustClaim {
    /// Create a new claim based on the specified descriptions
    pub fn new(
        desc_map: &'static ClaimDescripiton<'static>,
        val_desc_map: &'static Map<i8, ValueDescription<'static>>,
    ) -> TrustClaim {
        TrustClaim {
            value: None,
            desc: desc_map,
            value_desc: val_desc_map,
        }
    }

    /// Return `true` if the value of this claim has been set, and `false` otherwise
    pub fn is_set(&self) -> bool {
        self.value.is_some()
    }

    /// Set the claim to the specified value
    pub fn set(&mut self, v: i8) {
        self.value = Some(v);
    }

    /// Return the claim's value
    ///
    /// If the value is unset, `0i8` is returned, indicating that no claim is being made.
    pub fn get(&self) -> i8 {
        self.value.unwrap_or(0i8)
    }

    /// Return the claim's value
    ///
    /// If the value is unset, `0i8` is returned, indicating that no claim is being made.
    pub fn value(&self) -> i8 {
        self.get()
    }

    /// Unset set the value of the claim
    pub fn unset(&mut self) {
        self.value = None
    }

    /// Get the string tag of the claim
    pub fn tag(&self) -> &str {
        self.desc.name
    }

    /// Get the integer key of the claim
    pub fn key(&self) -> i8 {
        self.desc.key
    }

    /// Get the string name of the claim's value
    ///
    /// If the value is one of those defined by [draft-ietf-rats-ar4si-04], its standard name is
    /// returned. Otherwise, the name is `"TrustClaim(i)"`, where `i` is the value.
    ///
    /// [draft-ietf-rats-ar4si-04]: https://datatracker.ietf.org/doc/html/draft-ietf-rats-ar4si-04
    pub fn value_name(&self) -> String {
        match self.value_desc() {
            Some(v) => v.tag.to_string(),
            None => format!("TrustClaim({})", self.value()),
        }
    }

    /// Get the short description of the claim's value
    ///
    /// If the value is one of those defined by [draft-ietf-rats-ar4si-04], its known description
    /// is returned. Otherwise, the description is an empty string.
    ///
    /// [draft-ietf-rats-ar4si-04]: https://datatracker.ietf.org/doc/html/draft-ietf-rats-ar4si-04
    pub fn value_short_desc(&self) -> String {
        match self.value_desc() {
            Some(v) => v.short.to_string(),
            None => "".to_string(),
        }
    }

    /// Get the long description of the claim's value
    ///
    /// If the value is one of those defined by [draft-ietf-rats-ar4si-04], its known description
    /// is returned. Otherwise, the description is an empty string.
    ///
    /// [draft-ietf-rats-ar4si-04]: https://datatracker.ietf.org/doc/html/draft-ietf-rats-ar4si-04
    pub fn value_long_desc(&self) -> String {
        match self.value_desc() {
            Some(v) => v.long.to_string(),
            None => "".to_string(),
        }
    }

    /// Return the trust tier of the claim's value
    ///
    /// If the value is unset, `TrustTier::None` is returned.
    pub fn tier(&self) -> TrustTier {
        let val = self.value();
        if (-1..=1).contains(&val) {
            TrustTier::None
        } else if (-32..32).contains(&val) {
            TrustTier::Affirming
        } else if (-96..96).contains(&val) {
            TrustTier::Warning
        } else {
            TrustTier::Contraindicated
        }
    }

    /// Return the `ValueDescription` for the current value of this claim.
    fn value_desc(&self) -> Option<&ValueDescription> {
        let val = self.value();
        if (-1..=1).contains(&val) || val == 99 {
            return COMMON_CLAIM_MAP.get(&val);
        }
        self.value_desc.get(&val)
    }
}

impl std::fmt::Debug for TrustClaim {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            r#"TrustClaim{{"{0}({1})": {2}}}"#,
            self.tag(),
            self.key(),
            self.value()
        )
    }
}

impl Display for TrustClaim {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.value_name().fmt(f)
    }
}

impl PartialEq<TrustClaim> for TrustClaim {
    fn eq(&self, other: &TrustClaim) -> bool {
        self.value() == other.value()
    }
}

impl PartialEq<&str> for TrustClaim {
    fn eq(&self, other: &&str) -> bool {
        self.value_name().as_str() == *other
    }
}

impl PartialEq<i8> for TrustClaim {
    fn eq(&self, other: &i8) -> bool {
        self.value() == *other
    }
}

impl TryFrom<&str> for TrustClaim {
    type Error = Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "instance-identity" => Ok(TrustClaim::new(INSTANCE_CLAIM_DESC, INSTANCE_CLAIM_MAP)),
            "configuration" => Ok(TrustClaim::new(CONFIG_CLAIM_DESC, CONFIG_CLAIM_MAP)),
            "executables" => Ok(TrustClaim::new(
                EXECUTABLES_CLAIM_DESC,
                EXECUTABLES_CLAIM_MAP,
            )),
            "file-system" => Ok(TrustClaim::new(
                FILE_SYSTEM_CLAIM_DESC,
                FILE_SYSTEM_CLAIM_MAP,
            )),
            "hardware" => Ok(TrustClaim::new(HARDWARE_CLAIM_DESC, HARDWARE_CLAIM_MAP)),
            "runtime-opaque" => Ok(TrustClaim::new(RUNTIME_CLAIM_DESC, RUNTIME_CLAIM_MAP)),
            "storage-opaque" => Ok(TrustClaim::new(STORAGE_CLAIM_DESC, STORAGE_CLAIM_MAP)),
            "sourced-data" => Ok(TrustClaim::new(
                SOURCED_DATA_CLAIM_DESC,
                SOURCED_DATA_CLAIM_MAP,
            )),
            _ => Err(Error::InvalidName(value.to_string())),
        }
    }
}

impl TryFrom<i8> for TrustClaim {
    type Error = Error;

    fn try_from(value: i8) -> Result<Self, Self::Error> {
        match value {
            0i8 => Ok(TrustClaim::new(INSTANCE_CLAIM_DESC, INSTANCE_CLAIM_MAP)),
            1i8 => Ok(TrustClaim::new(CONFIG_CLAIM_DESC, CONFIG_CLAIM_MAP)),
            2i8 => Ok(TrustClaim::new(
                EXECUTABLES_CLAIM_DESC,
                EXECUTABLES_CLAIM_MAP,
            )),
            3i8 => Ok(TrustClaim::new(
                FILE_SYSTEM_CLAIM_DESC,
                FILE_SYSTEM_CLAIM_MAP,
            )),
            4i8 => Ok(TrustClaim::new(HARDWARE_CLAIM_DESC, HARDWARE_CLAIM_MAP)),
            5i8 => Ok(TrustClaim::new(RUNTIME_CLAIM_DESC, RUNTIME_CLAIM_MAP)),
            6i8 => Ok(TrustClaim::new(STORAGE_CLAIM_DESC, STORAGE_CLAIM_MAP)),
            7i8 => Ok(TrustClaim::new(
                SOURCED_DATA_CLAIM_DESC,
                SOURCED_DATA_CLAIM_MAP,
            )),
            _ => Err(Error::InvalidValue(value)),
        }
    }
}

impl From<TrustClaim> for String {
    fn from(val: TrustClaim) -> String {
        val.tag().to_string()
    }
}

impl From<&TrustClaim> for String {
    fn from(val: &TrustClaim) -> String {
        val.tag().to_string()
    }
}

impl From<TrustClaim> for i8 {
    fn from(val: TrustClaim) -> i8 {
        val.key()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn equality() {
        let claim: TrustClaim = TrustClaim {
            value: Some(2i8),
            desc: INSTANCE_CLAIM_DESC,
            value_desc: INSTANCE_CLAIM_MAP,
        };
        assert_eq!(claim, claim.clone());
        assert_eq!(claim, TRUSTWORTHY_INSTANCE);
        assert_eq!(claim, 2i8);
        assert_eq!(claim, "recognized_instance");
    }

    #[test]
    fn tier() {
        let mut claim: TrustClaim = TrustClaim {
            value: None,
            desc: INSTANCE_CLAIM_DESC,
            value_desc: INSTANCE_CLAIM_MAP,
        };

        assert_eq!(claim.tier(), TrustTier::None);

        claim.set(1i8);
        assert_eq!(claim.tier(), TrustTier::None);

        claim.set(2i8);
        assert_eq!(claim.tier(), TrustTier::Affirming);

        claim.set(31i8);
        assert_eq!(claim.tier(), TrustTier::Affirming);

        claim.set(32i8);
        assert_eq!(claim.tier(), TrustTier::Warning);

        claim.set(95);
        assert_eq!(claim.tier(), TrustTier::Warning);

        claim.set(96);
        assert_eq!(claim.tier(), TrustTier::Contraindicated);

        claim.set(-1i8);
        assert_eq!(claim.tier(), TrustTier::None);

        claim.set(-2i8);
        assert_eq!(claim.tier(), TrustTier::Affirming);

        claim.set(-32i8);
        assert_eq!(claim.tier(), TrustTier::Affirming);

        claim.set(-33i8);
        assert_eq!(claim.tier(), TrustTier::Warning);

        claim.set(-96);
        assert_eq!(claim.tier(), TrustTier::Warning);

        claim.set(-97);
        assert_eq!(claim.tier(), TrustTier::Contraindicated);
    }
}