1use coap_message::{MessageOption, ReadableMessage};
6
7pub trait Scope: Sized + core::fmt::Debug {
9 fn request_is_allowed<M: ReadableMessage>(&self, request: &M) -> bool;
11}
12
13impl Scope for core::convert::Infallible {
14 fn request_is_allowed<M: ReadableMessage>(&self, _request: &M) -> bool {
15 match *self {}
16 }
17}
18
19#[derive(Debug, Copy, Clone)]
24pub struct InvalidScope;
25
26#[cfg_attr(feature = "defmt", derive(defmt::Format))]
28#[derive(Debug)]
29pub struct AllowAll;
30
31impl Scope for AllowAll {
32 fn request_is_allowed<M: ReadableMessage>(&self, _request: &M) -> bool {
33 true
34 }
35}
36
37#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39#[derive(Debug)]
40pub struct DenyAll;
41
42impl Scope for DenyAll {
43 fn request_is_allowed<M: ReadableMessage>(&self, _request: &M) -> bool {
44 false
45 }
46}
47
48const AIF_SCOPE_MAX_LEN: usize = 64;
49
50#[cfg_attr(feature = "defmt", derive(defmt::Format))]
66#[derive(Debug, Clone)]
67pub struct AifValue([u8; AIF_SCOPE_MAX_LEN]);
68
69impl AifValue {
70 pub fn parse(bytes: &[u8]) -> Result<Self, InvalidScope> {
77 let mut buffer = [0; AIF_SCOPE_MAX_LEN];
78
79 buffer
80 .get_mut(..bytes.len())
81 .ok_or(InvalidScope)?
82 .copy_from_slice(bytes);
83
84 let mut decoder = minicbor::Decoder::new(bytes);
85 for item in decoder
86 .array_iter::<(&str, u32)>()
87 .map_err(|_| InvalidScope)?
88 {
89 let (path, _mask) = item.map_err(|_| InvalidScope)?;
90 if !path.starts_with('/') {
91 return Err(InvalidScope);
92 }
93 }
94
95 Ok(Self(buffer))
96 }
97}
98
99impl Scope for AifValue {
100 fn request_is_allowed<M: ReadableMessage>(&self, request: &M) -> bool {
101 let code: u8 = request.code().into();
102 let (codebit, false) = 1u32.overflowing_shl(
103 u32::from(code)
104 .checked_sub(1)
105 .expect("Request codes are != 0"),
106 ) else {
107 return false;
108 };
109 let mut decoder = minicbor::Decoder::new(&self.0);
110 'outer: for item in decoder.array_iter::<(&str, u32)>().unwrap() {
111 let (path, perms) = item.unwrap();
112 if perms & codebit == 0 {
113 continue;
114 }
115 let mut pathopts = request
119 .options()
120 .filter(|o| o.number() == coap_numbers::option::URI_PATH)
121 .peekable();
122 if path == "/" && pathopts.peek().is_none() {
123 return true;
125 }
126 assert!(path.starts_with('/'), "Invalid AIF");
127 let mut remainder = &path[1..];
128 while !remainder.is_empty() {
129 let (next_part, next_remainder) = match remainder.split_once('/') {
130 Some((next_part, next_remainder)) => (next_part, next_remainder),
131 None => (remainder, ""),
132 };
133 let Some(this_opt) = pathopts.next() else {
134 continue 'outer;
136 };
137 if this_opt.value() != next_part.as_bytes() {
138 continue 'outer;
140 }
141 remainder = next_remainder;
142 }
143 if pathopts.next().is_none() {
144 return true;
146 }
147 }
148 false
150 }
151}
152
153#[cfg_attr(feature = "defmt", derive(defmt::Format))]
161#[derive(Debug, Clone)]
162pub enum UnionScope {
163 AifValue(AifValue),
165 AllowAll,
167 DenyAll,
169}
170
171impl Scope for UnionScope {
172 fn request_is_allowed<M: ReadableMessage>(&self, request: &M) -> bool {
173 match self {
174 UnionScope::AifValue(v) => v.request_is_allowed(request),
175 UnionScope::AllowAll => AllowAll.request_is_allowed(request),
176 UnionScope::DenyAll => DenyAll.request_is_allowed(request),
177 }
178 }
179}
180
181impl From<AifValue> for UnionScope {
182 fn from(value: AifValue) -> Self {
183 UnionScope::AifValue(value)
184 }
185}
186
187impl From<AllowAll> for UnionScope {
188 fn from(_value: AllowAll) -> Self {
189 UnionScope::AllowAll
190 }
191}
192
193impl From<DenyAll> for UnionScope {
194 fn from(_value: DenyAll) -> Self {
195 UnionScope::DenyAll
196 }
197}
198
199impl From<core::convert::Infallible> for UnionScope {
200 fn from(value: core::convert::Infallible) -> Self {
201 match value {}
202 }
203}