1use alloc::string::{String, ToString};
9
10use pamoja_core::Error;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum Refusal {
15 Malformed,
17 UnsupportedVersion,
19 Signature,
21 Digest,
23 Size,
25 WrongDevice,
27 Rollback,
30 Expired,
32 NoClock,
35 SlotTooSmall,
37 NoSuchSlot,
39 WrongState,
41 NothingToRevert,
43}
44
45impl core::fmt::Display for Refusal {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 f.write_str(self.reason())
48 }
49}
50
51impl Refusal {
52 pub fn reason(self) -> &'static str {
58 match self {
59 Self::Malformed => "the manifest is malformed",
60 Self::UnsupportedVersion => "the manifest structure version is not supported",
61 Self::Signature => "the manifest signature is not from the trusted key",
62 Self::Digest => "the image does not match the manifest digest",
63 Self::Size => "the image is not the size the manifest declares",
64 Self::WrongDevice => "the manifest is for a different vendor or device class",
65 Self::Rollback => "the sequence number would roll the device back",
66 Self::Expired => "the manifest has expired",
67 Self::NoClock => "the manifest expires and this device has no clock",
68 Self::SlotTooSmall => "the image does not fit the target slot",
69 Self::NoSuchSlot => "no such slot on this device",
70 Self::WrongState => "the slot is not in a state that allows this",
71 Self::NothingToRevert => "there is no confirmed image to revert to",
72 }
73 }
74}
75
76impl From<Refusal> for Error {
77 fn from(value: Refusal) -> Self {
78 let message: String = value.reason().to_string();
79 match value {
80 Refusal::Signature
83 | Refusal::Digest
84 | Refusal::WrongDevice
85 | Refusal::Rollback
86 | Refusal::Expired => Error::Auth(message),
87 Refusal::Malformed | Refusal::UnsupportedVersion => Error::Codec(message),
88 _ => Error::Io(message),
89 }
90 }
91}
92
93pub type Result<T> = core::result::Result<T, Refusal>;
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn authenticity_failures_map_to_auth_errors() {
102 for refusal in [
103 Refusal::Signature,
104 Refusal::Digest,
105 Refusal::WrongDevice,
106 Refusal::Rollback,
107 ] {
108 assert!(matches!(Error::from(refusal), Error::Auth(_)));
109 }
110 }
111
112 #[test]
113 fn parse_failures_map_to_codec_errors() {
114 assert!(matches!(Error::from(Refusal::Malformed), Error::Codec(_)));
115 assert!(matches!(
116 Error::from(Refusal::UnsupportedVersion),
117 Error::Codec(_)
118 ));
119 }
120
121 #[test]
122 fn every_refusal_names_its_rule() {
123 assert!(!Refusal::Rollback.reason().is_empty());
124 assert!(Refusal::Rollback.reason().contains("roll"));
125 }
126}