doughnut_rs/doughnut/topping/
module.rs

1// Copyright 2023-2024 Futureverse Corporation Limited
2//!
3//! # Topping - Module
4//!
5//! Delegated runtime module permissions of Topping for use in TRN
6//!
7
8use super::method::Method;
9use super::WILDCARD;
10use crate::doughnut::topping::topping::MAX_METHODS;
11use alloc::{
12    string::{String, ToString},
13    vec::Vec,
14};
15use codec::{Decode, Encode, Input, Output};
16use core::convert::TryFrom;
17
18const BLOCK_COOLDOWN_MASK: u8 = 0b0000_0001;
19
20/// A TRN permission topping module
21#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
22pub struct Module {
23    pub name: String,
24    pub block_cooldown: Option<u32>,
25    pub methods: Vec<Method>,
26}
27
28impl Module {
29    pub fn new(name: &str) -> Self {
30        Self {
31            name: name.into(),
32            block_cooldown: None,
33            methods: Vec::new(),
34        }
35    }
36
37    pub fn block_cooldown(mut self, block_cooldown: u32) -> Self {
38        self.block_cooldown = Some(block_cooldown);
39        self
40    }
41
42    pub fn methods(mut self, methods: Vec<Method>) -> Self {
43        self.methods = methods;
44        self
45    }
46
47    /// Returns the method, if it exists in the Module
48    /// Wildcard methods have lower priority than defined methods
49    pub fn get_method(&self, method: &str) -> Option<&Method> {
50        let mut outcome: Option<&Method> = None;
51        for m in &self.methods {
52            if m.name == method {
53                outcome = Some(m);
54                break;
55            } else if m.name == WILDCARD {
56                outcome = Some(m);
57            }
58        }
59        outcome
60    }
61}
62
63impl Encode for Module {
64    fn encode_to<T: Output + ?Sized>(&self, buf: &mut T) {
65        if self.methods.is_empty() || self.methods.len() > MAX_METHODS {
66            return;
67        }
68        let method_count = u8::try_from(self.methods.len() - 1);
69        if method_count.is_err() {
70            return;
71        }
72        let mut method_count_and_has_cooldown_byte = method_count.unwrap() << 1;
73        if self.block_cooldown.is_some() {
74            method_count_and_has_cooldown_byte |= BLOCK_COOLDOWN_MASK;
75        }
76        buf.push_byte(method_count_and_has_cooldown_byte);
77
78        let mut name = [0_u8; 32];
79        let length = 32.min(self.name.len());
80        name[0..length].clone_from_slice(&self.name.as_bytes()[0..length]);
81
82        buf.write(&name);
83
84        if let Some(cooldown) = self.block_cooldown {
85            for b in &cooldown.to_le_bytes() {
86                buf.push_byte(*b);
87            }
88        }
89
90        for method in &self.methods {
91            method.encode_to(buf);
92        }
93    }
94}
95
96impl Decode for Module {
97    fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
98        let block_cooldown_and_method_count: u8 = input.read_byte()?;
99        let method_count = (block_cooldown_and_method_count >> 1) + 1;
100
101        let mut name_buf: [u8; 32] = Default::default();
102        input
103            .read(&mut name_buf)
104            .map_err(|_| "expected 32 byte module name")?;
105        let name = core::str::from_utf8(&name_buf)
106            .map_err(|_| codec::Error::from("module names should be utf8 encoded"))?
107            .trim_matches(char::from(0))
108            .to_string();
109
110        let module_cooldown =
111            if (block_cooldown_and_method_count & BLOCK_COOLDOWN_MASK) == BLOCK_COOLDOWN_MASK {
112                Some(u32::from_le_bytes([
113                    input.read_byte()?,
114                    input.read_byte()?,
115                    input.read_byte()?,
116                    input.read_byte()?,
117                ]))
118            } else {
119                None
120            };
121
122        let mut methods: Vec<Method> = Vec::default();
123
124        for _ in 0..method_count {
125            let m: Method = Decode::decode(input)?;
126            methods.push(m);
127        }
128
129        Ok(Self {
130            name,
131            block_cooldown: module_cooldown,
132            methods,
133        })
134    }
135}
136
137#[cfg(test)]
138mod test {
139    use super::{Method, Module, BLOCK_COOLDOWN_MASK};
140    use codec::{Decode, Encode};
141    use std::assert_eq;
142
143    macro_rules! methods {
144        ($($name:expr),*) => {
145            vec![
146                $( ( Method::new($name) ), )*
147            ]
148        }
149    }
150
151    // Constructor tests
152    #[test]
153    fn it_initializes() {
154        let module = Module::new("TestModule");
155
156        assert_eq!(module.name, "TestModule");
157        assert_eq!(module.block_cooldown, None);
158        assert_eq!(module.methods, vec![]);
159    }
160
161    // Encoding Tests
162    #[test]
163    fn it_encodes() {
164        let module = Module::new("TestModule").methods(methods!("TestMethod"));
165
166        let expected_name = String::from("TestModule").into_bytes();
167        let remainder = vec![0x00_u8; 32_usize - expected_name.len()];
168        let expected: Vec<u8> = [
169            vec![0_u8],
170            expected_name,
171            remainder,
172            Method::new("TestMethod").encode(),
173        ]
174        .concat();
175
176        assert_eq!(module.encode(), expected);
177    }
178
179    #[test]
180    fn it_does_not_encode_without_methods() {
181        let module = Module::new("TestModule");
182        assert_eq!(module.encode(), Vec::<u8>::default());
183    }
184
185    #[test]
186    fn it_encodes_only_32_characters_for_name() {
187        let module = Module::new("I don't like green eggs and ham, I don't like you Sam I am;")
188            .methods(methods!("TestMethod"));
189        let expected_length = 33 + 33;
190
191        assert_eq!(module.encode().len(), expected_length);
192    }
193
194    #[test]
195    fn it_encodes_with_block_cooldown() {
196        let module = Module::new("TestModule")
197            .methods(methods!("TestMethod"))
198            .block_cooldown(0x10204080);
199
200        let expected_name = String::from("TestModule").into_bytes();
201        let remainder = vec![0x00_u8; 32_usize - expected_name.len()];
202        let expected: Vec<u8> = [
203            vec![BLOCK_COOLDOWN_MASK],
204            expected_name,
205            remainder,
206            vec![0x80, 0x40, 0x20, 0x10],
207            Method::new("TestMethod").encode(),
208        ]
209        .concat();
210
211        assert_eq!(module.encode(), expected);
212    }
213
214    #[test]
215    fn it_encodes_with_many_methods() {
216        let module = Module::new("TestModule")
217            .methods(methods!("I", "do", "not", "like", "them", "Sam", "I am"));
218
219        let expected_name = String::from("TestModule").into_bytes();
220        let remainder = vec![0x00_u8; 32_usize - expected_name.len()];
221        let expected: Vec<u8> = [
222            vec![0x06_u8 << 1], // 6 + 1 methods
223            expected_name,
224            remainder,
225            Method::new("I").encode(),
226            Method::new("do").encode(),
227            Method::new("not").encode(),
228            Method::new("like").encode(),
229            Method::new("them").encode(),
230            Method::new("Sam").encode(),
231            Method::new("I am").encode(),
232        ]
233        .concat();
234
235        assert_eq!(module.encode(), expected);
236    }
237
238    // Decoding Tests
239    #[test]
240    fn it_decodes() {
241        let name_bytes = String::from("TestModule").into_bytes();
242        let remainder = vec![0x00_u8; 32_usize - name_bytes.len()];
243        let encoded: Vec<u8> = [
244            vec![0_u8],
245            name_bytes,
246            remainder,
247            Method::new("TestMethod").encode(),
248        ]
249        .concat();
250
251        let module = Module::decode(&mut &encoded[..]).unwrap();
252        assert_eq!(module.name, "TestModule");
253        assert_eq!(module.block_cooldown, None);
254        assert_eq!(module.methods.len(), 1);
255    }
256
257    #[test]
258    fn decode_fails_with_junk_bytes_in_the_name() {
259        let name_bytes = String::from("TestModule").into_bytes();
260        let remainder = vec![0xf0_u8; 32_usize - name_bytes.len()];
261        let encoded: Vec<u8> = [
262            vec![0_u8],
263            name_bytes,
264            remainder,
265            Method::new("TestMethod").encode(),
266        ]
267        .concat();
268
269        assert_eq!(
270            Module::decode(&mut &encoded[..]),
271            Err(codec::Error::from("module names should be utf8 encoded"))
272        );
273    }
274
275    #[test]
276    fn it_decodes_with_block_cooldown() {
277        let name_bytes = String::from("TestModule").into_bytes();
278        let remainder = vec![0x00_u8; 32_usize - name_bytes.len()];
279        let encoded: Vec<u8> = [
280            vec![BLOCK_COOLDOWN_MASK],
281            name_bytes,
282            remainder,
283            vec![0x80, 0x40, 0x20, 0x10],
284            Method::new("TestMethod").encode(),
285        ]
286        .concat();
287
288        let module = Module::decode(&mut &encoded[..]).unwrap();
289        assert_eq!(module.name, "TestModule");
290        assert_eq!(module.block_cooldown, Some(0x10204080));
291        assert_eq!(module.methods.len(), 1);
292    }
293
294    #[test]
295    fn decode_fails_with_insufficient_bytes_for_block_cooldown() {
296        let name_bytes = String::from("TestModule").into_bytes();
297        let remainder = vec![0x00_u8; 32_usize - name_bytes.len()];
298        let encoded: Vec<u8> = [
299            vec![BLOCK_COOLDOWN_MASK],
300            name_bytes,
301            remainder,
302            vec![0x01, 0x02],
303            Method::new("TestMethod").encode(),
304        ]
305        .concat();
306
307        assert_eq!(
308            Module::decode(&mut &encoded[..]),
309            Err(codec::Error::from("expected 32 byte method name"))
310        );
311    }
312
313    #[test]
314    fn it_decodes_with_many_methods() {
315        let name_bytes = String::from("TestModule").into_bytes();
316        let remainder = vec![0x00_u8; 32_usize - name_bytes.len()];
317        let encoded: Vec<u8> = [
318            vec![0x06_u8 << 1], // 6 + 1 methods
319            name_bytes,
320            remainder,
321            Method::new("I").encode(),
322            Method::new("do").encode(),
323            Method::new("not").encode(),
324            Method::new("like").encode(),
325            Method::new("them").encode(),
326            Method::new("Sam").encode(),
327            Method::new("I am").encode(),
328        ]
329        .concat();
330
331        let module = Module::decode(&mut &encoded[..]).unwrap();
332
333        assert_eq!(module.methods[0].name, "I");
334        assert_eq!(module.methods[1].name, "do");
335        assert_eq!(module.methods[2].name, "not");
336        assert_eq!(module.methods[3].name, "like");
337        assert_eq!(module.methods[4].name, "them");
338        assert_eq!(module.methods[5].name, "Sam");
339        assert_eq!(module.methods[6].name, "I am");
340    }
341}