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