tuwunel_matrix/pdu/count.rs
1#![expect(
2 clippy::cast_possible_wrap,
3 clippy::cast_sign_loss,
4 clippy::as_conversions
5)]
6
7use std::{cmp::Ordering, fmt, fmt::Display, str::FromStr};
8
9use ruma::api::Direction;
10use tuwunel_core::{Error, Result, err};
11
12/// Sequence number locating a PDU in a room timeline.
13///
14/// Valid normal counts range from zero through `i64::MAX`, while valid
15/// backfilled counts range from `i64::MIN` through zero. Ordering compares
16/// their signed representations, with zero shared by both variants.
17#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
18pub enum Count {
19 /// Sequence assigned to an event in the normal timeline.
20 ///
21 /// Normal counts advance forward as new local or federated events are
22 /// appended. Valid values do not exceed `i64::MAX`.
23 Normal(u64),
24
25 /// Sequence assigned to an event in the backfilled timeline.
26 ///
27 /// Valid backfilled counts occupy the nonpositive signed ordering range.
28 Backfilled(i64),
29}
30
31impl Count {
32 /// Encodes the count's integer bits in big-endian order.
33 ///
34 /// Normal values retain their unsigned representation. Backfilled values
35 /// are reinterpreted as unsigned two's-complement bits before encoding.
36 ///
37 /// # Panics
38 ///
39 /// Panics in debug builds if a positive `Backfilled` value was constructed
40 /// directly.
41 #[inline]
42 #[must_use]
43 pub fn to_be_bytes(self) -> [u8; size_of::<u64>()] { self.into_unsigned().to_be_bytes() }
44
45 /// Interprets unsigned integer bits as a signed timeline count.
46 ///
47 /// Values with the high bit set become negative backfilled counts. Other
48 /// positive values become normal counts, while zero becomes backfilled
49 /// zero.
50 #[inline]
51 #[must_use]
52 pub fn from_unsigned(unsigned: u64) -> Self { Self::from_signed(unsigned as i64) }
53
54 /// Classifies a signed integer as a normal or backfilled count.
55 ///
56 /// Positive values become normal counts. Zero and negative values become
57 /// backfilled counts.
58 #[inline]
59 #[must_use]
60 pub fn from_signed(signed: i64) -> Self {
61 match signed {
62 | i64::MIN..=0 => Self::Backfilled(signed),
63 | _ => Self::Normal(signed as u64),
64 }
65 }
66
67 /// Converts the count to its unsigned integer bit representation.
68 ///
69 /// Backfilled values are reinterpreted using two's-complement bits. The
70 /// variant information is not retained in the returned integer.
71 ///
72 /// # Panics
73 ///
74 /// Panics in debug builds if a positive `Backfilled` value was constructed
75 /// directly.
76 #[inline]
77 #[must_use]
78 pub fn into_unsigned(self) -> u64 {
79 self.debug_assert_valid();
80 match self {
81 | Self::Normal(i) => i,
82 | Self::Backfilled(i) => i as u64,
83 }
84 }
85
86 /// Converts the count to the signed representation used for ordering.
87 ///
88 /// Valid normal values cast into the nonnegative signed domain, while valid
89 /// backfilled values retain their signed count. Direct construction outside
90 /// the documented ranges violates the ordering invariant.
91 ///
92 /// # Panics
93 ///
94 /// Panics in debug builds if a positive `Backfilled` value was constructed
95 /// directly.
96 #[inline]
97 #[must_use]
98 pub fn into_signed(self) -> i64 {
99 self.debug_assert_valid();
100 match self {
101 | Self::Normal(i) => i as i64,
102 | Self::Backfilled(i) => i,
103 }
104 }
105
106 /// Converts a count to a normal-timeline position.
107 ///
108 /// Existing normal counts are preserved. Any backfilled count maps to the
109 /// beginning of the normal timeline at zero.
110 ///
111 /// # Panics
112 ///
113 /// Panics in debug builds if a positive `Backfilled` value was constructed
114 /// directly.
115 #[inline]
116 #[must_use]
117 pub fn into_normal(self) -> Self {
118 self.debug_assert_valid();
119 match self {
120 | Self::Normal(i) => Self::Normal(i),
121 | Self::Backfilled(_) => Self::Normal(0),
122 }
123 }
124
125 /// Advances or retreats the count by one without integer overflow.
126 ///
127 /// Forward movement adds one and backward movement subtracts one. The
128 /// original variant is preserved, so callers must avoid crossing that
129 /// variant's valid timeline range.
130 #[inline]
131 pub fn checked_inc(self, dir: Direction) -> Result<Self, Error> {
132 match dir {
133 | Direction::Forward => self.checked_add(1),
134 | Direction::Backward => self.checked_sub(1),
135 }
136 }
137
138 /// Adds an unsigned offset without integer overflow.
139 ///
140 /// Backfilled offsets are cast to `i64` and must not exceed `i64::MAX`.
141 /// Callers must keep the resulting variant within its documented range;
142 /// integer overflow returns an arithmetic error.
143 #[inline]
144 pub fn checked_add(self, add: u64) -> Result<Self, Error> {
145 Ok(match self {
146 | Self::Normal(i) => Self::Normal(
147 i.checked_add(add)
148 .ok_or_else(|| err!(Arithmetic("Count::Normal overflow")))?,
149 ),
150 | Self::Backfilled(i) => Self::Backfilled(
151 i.checked_add(add as i64)
152 .ok_or_else(|| err!(Arithmetic("Count::Backfilled overflow")))?,
153 ),
154 })
155 }
156
157 /// Subtracts an unsigned offset without integer underflow.
158 ///
159 /// Backfilled offsets are cast to `i64` and must not exceed `i64::MAX`.
160 /// Callers must keep the resulting variant within its documented range;
161 /// integer underflow returns an arithmetic error.
162 #[inline]
163 pub fn checked_sub(self, sub: u64) -> Result<Self, Error> {
164 Ok(match self {
165 | Self::Normal(i) => Self::Normal(
166 i.checked_sub(sub)
167 .ok_or_else(|| err!(Arithmetic("Count::Normal underflow")))?,
168 ),
169 | Self::Backfilled(i) => Self::Backfilled(
170 i.checked_sub(sub as i64)
171 .ok_or_else(|| err!(Arithmetic("Count::Backfilled underflow")))?,
172 ),
173 })
174 }
175
176 /// Advances or retreats the count by one with saturation.
177 ///
178 /// Forward movement adds one and backward movement subtracts one.
179 /// Saturation uses the integer boundary of the existing variant and does
180 /// not prevent a backfilled value from crossing zero.
181 #[inline]
182 #[must_use]
183 pub fn saturating_inc(self, dir: Direction) -> Self {
184 match dir {
185 | Direction::Forward => self.saturating_add(1),
186 | Direction::Backward => self.saturating_sub(1),
187 }
188 }
189
190 /// Adds an unsigned offset with saturation at the integer boundary.
191 ///
192 /// Backfilled offsets are cast to `i64` and must not exceed `i64::MAX`.
193 /// Saturation uses the underlying integer boundary and does not repair a
194 /// result outside the variant's documented range.
195 #[inline]
196 #[must_use]
197 pub fn saturating_add(self, add: u64) -> Self {
198 match self {
199 | Self::Normal(i) => Self::Normal(i.saturating_add(add)),
200 | Self::Backfilled(i) => Self::Backfilled(i.saturating_add(add as i64)),
201 }
202 }
203
204 /// Subtracts an unsigned offset with saturation at the integer boundary.
205 ///
206 /// Backfilled offsets are cast to `i64` and must not exceed `i64::MAX`.
207 /// Saturation uses the underlying integer boundary and does not repair a
208 /// result outside the variant's documented range.
209 #[inline]
210 #[must_use]
211 pub fn saturating_sub(self, sub: u64) -> Self {
212 match self {
213 | Self::Normal(i) => Self::Normal(i.saturating_sub(sub)),
214 | Self::Backfilled(i) => Self::Backfilled(i.saturating_sub(sub as i64)),
215 }
216 }
217
218 /// Returns the earliest valid timeline count.
219 ///
220 /// The minimum is a backfilled count at `i64::MIN`. It sorts before every
221 /// other valid count.
222 #[inline]
223 #[must_use]
224 pub const fn min() -> Self { Self::Backfilled(i64::MIN) }
225
226 /// Returns the latest valid timeline count.
227 ///
228 /// The maximum is a normal count at `i64::MAX`. This keeps the value within
229 /// the signed domain used by ordering.
230 #[inline]
231 #[must_use]
232 pub const fn max() -> Self { Self::Normal(i64::MAX as u64) }
233
234 #[inline]
235 pub(crate) fn debug_assert_valid(&self) {
236 if let Self::Backfilled(i) = self {
237 debug_assert!(*i <= 0, "Backfilled sequence must be negative");
238 }
239 }
240}
241
242impl Display for Count {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
244 self.debug_assert_valid();
245 match self {
246 | Self::Normal(i) => write!(f, "{i}"),
247 | Self::Backfilled(i) => write!(f, "{i}"),
248 }
249 }
250}
251
252impl From<i64> for Count {
253 #[inline]
254 fn from(signed: i64) -> Self { Self::from_signed(signed) }
255}
256
257impl From<u64> for Count {
258 #[inline]
259 fn from(unsigned: u64) -> Self { Self::from_unsigned(unsigned) }
260}
261
262impl FromStr for Count {
263 type Err = Error;
264
265 fn from_str(token: &str) -> Result<Self, Self::Err> { Ok(Self::from_signed(token.parse()?)) }
266}
267
268impl PartialOrd for Count {
269 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
270}
271
272impl Ord for Count {
273 fn cmp(&self, other: &Self) -> Ordering { self.into_signed().cmp(&other.into_signed()) }
274}
275
276impl Default for Count {
277 fn default() -> Self { Self::Normal(0) }
278}