treasury_id/
lib.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Debug, Display, LowerHex, UpperHex},
4    num::{NonZeroU64, ParseIntError},
5    str::FromStr,
6    sync::Mutex,
7    time::{Duration, SystemTime},
8};
9
10use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
11
12/// 64-bit id value.
13/// FFI-safe.
14#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[repr(transparent)]
16pub struct AssetId(NonZeroU64);
17
18impl Serialize for AssetId {
19    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
20    where
21        S: Serializer,
22    {
23        use std::io::Write;
24
25        if serializer.is_human_readable() {
26            let mut hex = [0u8; 16];
27            write!(std::io::Cursor::new(&mut hex[..]), "{:016x}", self.0).expect("Must fit");
28            let hex = std::str::from_utf8(&hex).expect("Must be UTF-8");
29            serializer.serialize_str(hex)
30        } else {
31            serializer.serialize_u64(self.0.get())
32        }
33    }
34}
35
36impl<'de> Deserialize<'de> for AssetId {
37    fn deserialize<D>(deserializer: D) -> Result<AssetId, D::Error>
38    where
39        D: Deserializer<'de>,
40    {
41        if deserializer.is_human_readable() {
42            let hex = Cow::<str>::deserialize(deserializer)?;
43            hex.parse().map_err(Error::custom)
44        } else {
45            let value = NonZeroU64::deserialize(deserializer)?;
46            Ok(AssetId(value))
47        }
48    }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
52pub enum ParseAssetIdError {
53    #[error(transparent)]
54    ParseIntError(#[from] ParseIntError),
55
56    #[error("AssetId cannot be zero")]
57    ZeroId,
58}
59
60impl FromStr for AssetId {
61    type Err = ParseAssetIdError;
62    fn from_str(s: &str) -> Result<Self, ParseAssetIdError> {
63        let value = u64::from_str_radix(s, 16)?;
64        match NonZeroU64::new(value) {
65            None => Err(ParseAssetIdError::ZeroId),
66            Some(value) => Ok(AssetId(value)),
67        }
68    }
69}
70
71#[derive(Debug)]
72pub struct ZeroIDError;
73
74impl AssetId {
75    pub fn new(value: u64) -> Option<Self> {
76        NonZeroU64::new(value).map(AssetId)
77    }
78
79    pub fn value(&self) -> NonZeroU64 {
80        self.0
81    }
82}
83
84impl From<NonZeroU64> for AssetId {
85    fn from(value: NonZeroU64) -> Self {
86        AssetId(value)
87    }
88}
89
90impl TryFrom<u64> for AssetId {
91    type Error = ZeroIDError;
92
93    fn try_from(value: u64) -> Result<Self, ZeroIDError> {
94        match NonZeroU64::try_from(value) {
95            Ok(value) => Ok(AssetId(value)),
96            Err(_) => Err(ZeroIDError),
97        }
98    }
99}
100
101impl Debug for AssetId {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        LowerHex::fmt(&self.0.get(), f)
104    }
105}
106
107impl UpperHex for AssetId {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        UpperHex::fmt(&self.0.get(), f)
110    }
111}
112
113impl LowerHex for AssetId {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        LowerHex::fmt(&self.0.get(), f)
116    }
117}
118
119impl Display for AssetId {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        LowerHex::fmt(&self.0.get(), f)
122    }
123}
124
125/// Context to generate IDs.
126/// Each context guarantees to generate unique IDs.
127/// Context initialized with different salt are guaranteed to generate different IDs.
128pub struct AssetIdContext {
129    /// Start of the epoch.
130    epoch: SystemTime,
131
132    /// 10 bits of the node id.
133    node: u16,
134
135    /// Counter first 12 bits of which are used for ID generation.
136    data: Mutex<ContextSyncData>,
137}
138
139struct ContextSyncData {
140    counter: u16,
141    last_timestamp: u64,
142}
143
144impl AssetIdContext {
145    pub fn new(epoch: SystemTime, node: u16) -> Self {
146        assert!(epoch <= SystemTime::now());
147
148        AssetIdContext {
149            epoch,
150            node: node & 0x3FF,
151            data: Mutex::new(ContextSyncData {
152                counter: 0,
153                last_timestamp: 0,
154            }),
155        }
156    }
157
158    /// Generate new ID.
159    pub fn generate(&self) -> AssetId {
160        loop {
161            let timestamp = SystemTime::now()
162                .duration_since(self.epoch)
163                .expect("Epoch must be in relatively distant past")
164                .as_millis() as u64;
165
166            let mut guard = self.data.lock().unwrap();
167
168            if guard.last_timestamp > timestamp {
169                panic!("Time goes backwards");
170            }
171
172            if guard.last_timestamp == timestamp {
173                if guard.counter == 0xFFF {
174                    // That's too fast. Throttle.
175                    std::thread::sleep(Duration::from_millis(1));
176                    continue;
177                }
178
179                guard.counter += 1;
180            } else {
181                guard.counter = 1;
182            }
183
184            let counter = guard.counter as u64;
185
186            let node = self.node as u64;
187            let id = (timestamp << 22) | (node << 12) | counter;
188            let id = NonZeroU64::new(id.wrapping_mul(ID_MUL)).expect("Zero id cannot be generated");
189            return AssetId(id);
190        }
191    }
192}
193
194/// GCD of values with least significant bit set and 2^N is always 1.
195/// Meaning that multiplying any value with this constant is reversible
196/// and thus does not break uniqueness.
197const ID_MUL: u64 = 0xF89A4B715E26C30D;
198
199#[allow(unused)]
200const GUARANTEE_LEAST_SIGNIFICANT_BIT_OF_ID_MUL_IS_SET: [(); (ID_MUL & 1) as usize] = [()];