nuts_directory/
id.rs

1// MIT License
2//
3// Copyright (c) 2022-2024 Robin Doer
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22
23#[cfg(test)]
24mod tests;
25
26use nuts_backend::{Binary, IdSize};
27use std::convert::TryInto;
28use std::fmt;
29use std::path::{Path, PathBuf};
30use std::str::FromStr;
31
32use crate::error::{Error, Result};
33
34#[cfg(test)]
35fn rand_bytes() -> Result<[u8; SIZE]> {
36    Ok([
37        0xdb, 0x3d, 0x05, 0x23, 0xd4, 0x50, 0x75, 0x30, 0xe8, 0x6d, 0xf9, 0x6a, 0x1b, 0x76, 0xaa,
38        0x0c,
39    ])
40}
41
42#[cfg(not(test))]
43fn rand_bytes() -> Result<[u8; SIZE]> {
44    let mut buf = [0; SIZE];
45
46    // std::io::Error implements From<getrandom::Error>
47    // 1. Convert getrandom::Error into std::io::Error
48    // 2. Convert std::io::Error into $crate::error::Error
49    getrandom::getrandom(&mut buf).map_err(Into::<std::io::Error>::into)?;
50
51    Ok(buf)
52}
53
54const SIZE: usize = 16;
55const HEX: [char; SIZE] = [
56    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
57];
58
59/// The [id](nuts_backend::Backend::Id) of the backend.
60///
61/// This id as an 16 byte random number.
62///
63/// When storing a block to disks the path to the file is derived from the id:
64/// * The id is converted into a hex string.
65/// * The path then would be: `<first two chars>/<next two chars>/<remaining chars>`
66#[derive(Clone, PartialEq)]
67pub struct Id([u8; SIZE]);
68
69impl Binary for Id {
70    fn from_bytes(bytes: &[u8]) -> Option<Id> {
71        match bytes.try_into() {
72            Ok(buf) => Some(Id(buf)),
73            Err(_) => None,
74        }
75    }
76
77    fn as_bytes(&self) -> Vec<u8> {
78        self.0.to_vec()
79    }
80}
81
82impl IdSize for Id {
83    fn size() -> usize {
84        SIZE
85    }
86}
87
88impl Id {
89    pub(crate) fn generate() -> Result<Id> {
90        rand_bytes().map(Id)
91    }
92
93    pub(crate) fn min() -> Id {
94        Id([u8::MIN; SIZE])
95    }
96
97    fn as_hex(&self) -> String {
98        let mut target = String::with_capacity(2 * SIZE);
99
100        for b in self.0.iter() {
101            target.push(HEX[(*b as usize >> 4) & 0x0f]);
102            target.push(HEX[(*b as usize) & 0x0f]);
103        }
104
105        target
106    }
107
108    pub(crate) fn to_pathbuf(&self, parent: &Path) -> PathBuf {
109        let hex = self.as_hex();
110        let mut path = PathBuf::new();
111        let mut pos = 0;
112
113        path.push(parent);
114
115        for _ in 0..2 {
116            path.push(&hex[pos..pos + 2]);
117            pos += 2;
118        }
119
120        path.push(&hex[pos..]);
121
122        path
123    }
124}
125
126impl fmt::Display for Id {
127    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
128        fmt.write_str(&self.as_hex())
129    }
130}
131
132impl fmt::Debug for Id {
133    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
134        fmt.debug_tuple("Id").field(&self.to_string()).finish()
135    }
136}
137
138impl FromStr for Id {
139    type Err = Error;
140
141    fn from_str(s: &str) -> Result<Self> {
142        if s.len() != 2 * SIZE {
143            return Err(Error::InvalidId(s.to_string()));
144        }
145
146        let mut id = Id([0; SIZE]);
147
148        for (idx, c) in s.chars().enumerate() {
149            if let Some(n) = c.to_digit(16) {
150                let m = idx / 2;
151
152                if idx % 2 == 0 {
153                    id.0[m] |= (n as u8 & 0x0f) << 4;
154                } else {
155                    id.0[m] |= n as u8 & 0x0f;
156                }
157            } else {
158                return Err(Error::InvalidId(s.to_string()));
159            }
160        }
161
162        Ok(id)
163    }
164}