1use std::cmp::Ordering;
4use std::fmt;
5use std::str::FromStr;
6
7use async_hash::{Digest, Hash, Output};
8use async_trait::async_trait;
9use get_size::GetSize;
10use get_size_derive::*;
11use rand::Rng;
12use safecast::TryCastFrom;
13
14use destream::IntoStream;
15
16use tc_error::*;
17use tcgeneric::{Id, NetworkTime};
18
19pub const MIN_ID: TxnId = TxnId {
21 timestamp: 0,
22 nonce: 0,
23};
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, GetSize)]
27pub struct TxnId {
28 timestamp: u64, nonce: u16,
30}
31
32impl TxnId {
33 pub fn new(time: NetworkTime) -> Self {
35 let mut rng = rand::thread_rng();
36 let nonce = loop {
37 let nonce = rng.gen();
38 if nonce > 0 && nonce < (u16::MAX - 1) {
39 break nonce;
40 }
41 };
42
43 Self {
44 timestamp: time.as_nanos(),
45 nonce,
46 }
47 }
48
49 pub fn prev(&self) -> Self {
51 Self {
52 timestamp: self.timestamp,
53 nonce: self.nonce - 1,
54 }
55 }
56
57 pub fn time(&self) -> NetworkTime {
59 NetworkTime::from_nanos(self.timestamp)
60 }
61
62 pub fn to_id(&self) -> Id {
64 Id::try_cast_from(self.to_string(), |_| {
65 unreachable!("number failed ID validation")
66 })
67 .unwrap()
68 }
69}
70
71impl FromStr for TxnId {
72 type Err = TCError;
73
74 fn from_str(s: &str) -> TCResult<TxnId> {
75 let i = s
76 .find('-')
77 .ok_or_else(|| TCError::unexpected(s, "a transaction ID"))?;
78
79 if i == s.len() - 1 {
80 return Err(TCError::unexpected(s, "a transaction ID"));
81 }
82
83 let timestamp = &s[..i];
84 let nonce = &s[i + 1..];
85
86 let timestamp = timestamp
87 .parse()
88 .map_err(|cause| TCError::unexpected(timestamp, "a timestamp").consume(cause))?;
89
90 let nonce = nonce
91 .parse()
92 .map_err(|cause| TCError::unexpected(nonce, "a nonce").consume(cause))?;
93
94 Ok(TxnId { timestamp, nonce })
95 }
96}
97
98impl Ord for TxnId {
99 fn cmp(&self, other: &TxnId) -> std::cmp::Ordering {
100 if self.timestamp == other.timestamp {
101 self.nonce.cmp(&other.nonce)
102 } else {
103 self.timestamp.cmp(&other.timestamp)
104 }
105 }
106}
107
108impl PartialOrd for TxnId {
109 fn partial_cmp(&self, other: &TxnId) -> Option<std::cmp::Ordering> {
110 Some(self.cmp(other))
111 }
112}
113
114impl PartialEq<str> for TxnId {
115 fn eq(&self, other: &str) -> bool {
116 if let Ok(other) = Self::from_str(other) {
117 self == &other
118 } else {
119 false
120 }
121 }
122}
123
124impl PartialEq<Id> for TxnId {
125 fn eq(&self, other: &Id) -> bool {
126 self == other.as_str()
127 }
128}
129
130impl PartialOrd<str> for TxnId {
131 fn partial_cmp(&self, other: &str) -> Option<Ordering> {
132 if let Ok(other) = Self::from_str(other) {
133 self.partial_cmp(&other)
134 } else {
135 None
136 }
137 }
138}
139
140impl freqfs::Name for TxnId {
141 fn partial_cmp(&self, other: &String) -> Option<Ordering> {
142 if let Ok(other) = Self::from_str(other) {
143 Some(self.cmp(&other))
144 } else {
145 None
146 }
147 }
148}
149
150#[async_trait]
151impl destream::de::FromStream for TxnId {
152 type Context = ();
153
154 async fn from_stream<D: destream::de::Decoder>(
155 context: (),
156 d: &mut D,
157 ) -> Result<Self, D::Error> {
158 let s = <String as destream::de::FromStream>::from_stream(context, d).await?;
159 Self::from_str(&s).map_err(destream::de::Error::custom)
160 }
161}
162
163impl<'en> destream::en::IntoStream<'en> for TxnId {
164 fn into_stream<E: destream::en::Encoder<'en>>(self, e: E) -> Result<E::Ok, E::Error> {
165 self.to_string().into_stream(e)
166 }
167}
168
169impl<'en> destream::en::ToStream<'en> for TxnId {
170 fn to_stream<E: destream::en::Encoder<'en>>(&'en self, e: E) -> Result<E::Ok, E::Error> {
171 self.to_string().into_stream(e)
172 }
173}
174
175impl<D: Digest> Hash<D> for TxnId {
176 fn hash(self) -> Output<D> {
177 Hash::<D>::hash(&self)
178 }
179}
180
181impl<'a, D: Digest> Hash<D> for &'a TxnId {
182 fn hash(self) -> Output<D> {
183 let mut bytes = [0u8; 10];
184 bytes[..8].copy_from_slice(&self.timestamp.to_be_bytes());
185 bytes[8..].copy_from_slice(&self.nonce.to_be_bytes());
186 D::digest(&bytes)
187 }
188}
189
190impl fmt::Display for TxnId {
191 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192 write!(f, "{}-{}", self.timestamp, self.nonce)
193 }
194}