1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
use lazy_static::lazy_static;
use rand::{thread_rng, Rng};
use thiserror::Error;

use std::{
    convert::TryFrom,
    fmt,
    num::ParseIntError,
    str::FromStr,
    sync::atomic::{AtomicU32, Ordering},
    time::{Duration, SystemTime, UNIX_EPOCH},
};

use crate::Error as CrateError;

/// The `UniqueId` epoch (2021-01-01 00:00:00 GMT) in terms of time since the Unix epoch
const EPOCH_AS_UNIX: u64 = 1_609_459_200;

lazy_static! {
    /// A `SystemTime` representing the `UniqueId` epoch.
    pub static ref EPOCH: SystemTime = UNIX_EPOCH - Duration::from_secs(EPOCH_AS_UNIX);
}

/// Represents an error that can occur when constructing a new `UniqueId`.
#[derive(Debug, Error)]
pub(crate) enum UniqueIdError {
    #[error("SystemTime generated a timestamp that is before the UniqueId epoch")]
    SystemPastTime,
    #[error("UniqueId timestamp is more than 2^32 - 1 seconds past epoch")]
    Overflow,
    #[error("expected string to contain 32 characters, got one that contained {0}")]
    FromStrBadLen(usize),
    #[error("string passed to UniqueId::from_str could not be read because: {err}")]
    FromStrParseError {
        #[from]
        err: ParseIntError,
    },
}

/// Represents a UUID with a custom epoch of midnight January 1st 2021.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct UniqueId {
    index: u32,
    time: u32,
    random: i64,
}

static INDEX: AtomicU32 = AtomicU32::new(0);

impl UniqueId {
    /// Returns a 'nil' `UniqueId` that has every field set to `0`.
    /// This value may appear multiple times in a Roblox file safely.
    pub fn nil() -> Self {
        Self {
            index: 0,
            time: 0,
            random: 0,
        }
    }

    /// Returns a new `UniqueId` with each component set to the passed
    /// values.
    pub fn new(index: u32, time: u32, random: i64) -> Self {
        Self {
            index,
            time,
            random,
        }
    }

    /// Returns a new UniqueId.
    pub fn now() -> Result<Self, CrateError> {
        let time = SystemTime::now()
            .duration_since(*EPOCH)
            .map_err(|_| CrateError::from(UniqueIdError::SystemPastTime))?;

        Ok(Self {
            index: INDEX.fetch_add(1, Ordering::AcqRel),
            time: u32::try_from(time.as_secs())
                .map_err(|_| CrateError::from(UniqueIdError::Overflow))?,
            // This matches Roblox's behavior, where the value is both an i64
            // but is also always positive.
            random: thread_rng().gen_range(0..i64::MAX),
        })
    }

    /// Returns whether this `UniqueId` is 'nil' or not. That is, whether
    /// every field of the UniqueId is set to `0`.
    pub fn is_nil(&self) -> bool {
        self.time == 0 && self.index == 0 && self.random == 0
    }

    /// The 'time' portion of the UniqueId. This is the number of seconds since
    /// 1 January 2021.
    ///
    /// Pending system time errors, this value will always be above `0`.
    pub fn time(&self) -> u32 {
        self.time
    }

    /// The 'index' portion of the UniqueId.
    ///
    /// This value may be any number in the range `[0, u32::MAX]`.
    pub fn index(&self) -> u32 {
        self.index
    }

    /// The 'random' portion of the `UniqueId`.
    ///
    /// This value may be any number in the range `[0, i64::MAX]`.
    pub fn random(&self) -> i64 {
        self.random
    }
}

impl fmt::Display for UniqueId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:016x}{:08x}{:08x}", self.random, self.time, self.index)
    }
}

impl FromStr for UniqueId {
    type Err = CrateError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.len() == 32 {
            Ok(UniqueId {
                random: i64::from_str_radix(&s[0..16], 16)
                    .map_err(|e| CrateError::from(UniqueIdError::from(e)))?,
                time: u32::from_str_radix(&s[16..24], 16)
                    .map_err(|e| CrateError::from(UniqueIdError::from(e)))?,
                index: u32::from_str_radix(&s[24..32], 16)
                    .map_err(|e| CrateError::from(UniqueIdError::from(e)))?,
            })
        } else {
            Err(UniqueIdError::FromStrBadLen(s.len()).into())
        }
    }
}

#[cfg(feature = "serde")]
mod serde_impl {
    use super::UniqueId;
    use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
    use std::{convert::TryInto, fmt};

    impl Serialize for UniqueId {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            if serializer.is_human_readable() {
                serializer.serialize_str(&self.to_string())
            } else {
                let mut bytes = [0; 16];

                bytes[0..8].copy_from_slice(&self.random.to_be_bytes());
                bytes[8..12].copy_from_slice(&self.time.to_be_bytes());
                bytes[12..16].copy_from_slice(&self.index.to_be_bytes());

                serializer.serialize_bytes(&bytes)
            }
        }
    }

    impl<'de> Deserialize<'de> for UniqueId {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            if deserializer.is_human_readable() {
                deserializer.deserialize_str(HumanVisitor)
            } else {
                deserializer.deserialize_bytes(NonHumanVisitor)
            }
        }
    }

    struct HumanVisitor;
    struct NonHumanVisitor;

    impl<'de> de::Visitor<'de> for HumanVisitor {
        type Value = UniqueId;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            write!(formatter, "a sequence of 32 hexadecimal characters")
        }

        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            v.parse().map_err(E::custom)
        }
    }

    impl<'de> de::Visitor<'de> for NonHumanVisitor {
        type Value = UniqueId;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            write!(formatter, "a sequence of 16 bytes")
        }

        fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            if v.len() == 16 {
                Ok(UniqueId {
                    random: i64::from_be_bytes(v[0..8].try_into().unwrap()),
                    time: u32::from_be_bytes(v[8..12].try_into().unwrap()),
                    index: u32::from_be_bytes(v[12..16].try_into().unwrap()),
                })
            } else {
                Err(E::custom(format!(
                    "invalid length of byte sequence: {}",
                    v.len()
                )))
            }
        }
    }
}

#[cfg(test)]
mod test {
    use std::str::FromStr;

    use super::UniqueId;

    #[test]
    fn display() {
        let uid = UniqueId::new(0xdead_beef, 0x0013_3700, 0x0badf00dc0ffee42);
        assert_eq!(uid.to_string(), "0badf00dc0ffee4200133700deadbeef");
    }

    #[test]
    fn from_str() {
        let str = "0badf00dc0ffee4200133700deadbeef";
        let uid = UniqueId::from_str(str).unwrap();
        assert_eq!(
            uid,
            UniqueId::new(0xdead_beef, 0x0013_3700, 0x0badf00dc0ffee42)
        )
    }

    #[cfg(feature = "serde")]
    #[test]
    fn human_roundtrip() {
        let uid = UniqueId::new(0x1337_0000, 0xfaca_de00, 0x1020_3040_5060_7080);
        let ser = serde_json::to_string(&uid).unwrap();
        let de: UniqueId = serde_json::from_str(&ser).unwrap();

        assert_eq!(ser, r#""1020304050607080facade0013370000""#);
        assert_eq!(de, uid);
    }

    #[cfg(feature = "serde")]
    #[test]
    fn not_human_roundtrip() {
        let uid = UniqueId::new(0x1337_0000, 0xfaca_de00, 0x1020_3040_5060_7080);
        let ser = bincode::serialize(&uid).unwrap();
        let de: UniqueId = bincode::deserialize(&ser).unwrap();

        // Bincode prefixes vectors with the vector's length as a little-endian `u64`
        assert_eq!(ser[0..8].as_ref(), 16_u64.to_le_bytes());

        assert_eq!(
            ser[8..].as_ref(),
            b"\x10\x20\x30\x40\x50\x60\x70\x80\xfa\xca\xde\x00\x13\x37\x00\x00"
        );
        assert_eq!(de, uid);
    }
}