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