1use std::time::{SystemTime, UNIX_EPOCH};
7
8use steel_crypto::{CryptError, SignatureUpdater, signature::SignatureOutput};
9use thiserror::Error;
10use uuid::Uuid;
11
12use super::signature_cache::LastSeen;
13
14#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct SignedMessageLink {
19 pub index: i32,
21 pub sender: Uuid,
23 pub session_id: Uuid,
25}
26
27impl SignedMessageLink {
28 #[must_use]
30 pub const fn new(index: i32, sender: Uuid, session_id: Uuid) -> Self {
31 Self {
32 index,
33 sender,
34 session_id,
35 }
36 }
37
38 #[must_use]
40 pub const fn unsigned(sender: Uuid) -> Self {
41 Self::root(sender, Uuid::from_u128(0))
42 }
43
44 #[must_use]
46 pub const fn root(sender: Uuid, session_id: Uuid) -> Self {
47 Self {
48 index: 0,
49 sender,
50 session_id,
51 }
52 }
53
54 pub fn update_signature(
56 &self,
57 output: &mut (impl SignatureOutput + ?Sized),
58 ) -> Result<(), CryptError> {
59 output.update(&self.sender.as_u128().to_be_bytes())?;
61
62 output.update(&self.session_id.as_u128().to_be_bytes())?;
64
65 output.update(&self.index.to_be_bytes())?;
67
68 Ok(())
69 }
70
71 #[must_use]
73 pub fn is_descendant_of(&self, other: &SignedMessageLink) -> bool {
74 self.index > other.index
75 && self.sender == other.sender
76 && self.session_id == other.session_id
77 }
78
79 #[must_use]
81 pub const fn advance(&self) -> Option<Self> {
82 if self.index == i32::MAX {
83 None
84 } else {
85 Some(Self {
86 index: self.index + 1,
87 sender: self.sender,
88 session_id: self.session_id,
89 })
90 }
91 }
92}
93
94#[derive(Clone, Debug)]
98pub struct SignedMessageBody {
99 pub content: String,
101 pub time_stamp: SystemTime,
103 pub salt: i64,
105 pub last_seen: LastSeen,
107}
108
109impl SignedMessageBody {
110 #[must_use]
112 pub const fn new(
113 content: String,
114 time_stamp: SystemTime,
115 salt: i64,
116 last_seen: LastSeen,
117 ) -> Self {
118 Self {
119 content,
120 time_stamp,
121 salt,
122 last_seen,
123 }
124 }
125
126 #[must_use]
128 pub fn unsigned(content: String) -> Self {
129 Self {
130 content,
131 time_stamp: SystemTime::now(),
132 salt: 0,
133 last_seen: LastSeen::default(),
134 }
135 }
136
137 pub fn update_signature(
139 &self,
140 output: &mut (impl SignatureOutput + ?Sized),
141 ) -> Result<(), CryptError> {
142 output.update(&self.salt.to_be_bytes())?;
144
145 let epoch_seconds = self
147 .time_stamp
148 .duration_since(UNIX_EPOCH)
149 .unwrap_or_default()
150 .as_secs() as i64;
151 output.update(&epoch_seconds.to_be_bytes())?;
152
153 let content_bytes = self.content.as_bytes();
155 output.update(&(content_bytes.len() as i32).to_be_bytes())?;
156
157 output.update(content_bytes)?;
159
160 update_last_seen_signature(&self.last_seen, output)?;
162
163 Ok(())
164 }
165}
166
167fn update_last_seen_signature(
169 last_seen: &LastSeen,
170 output: &mut (impl SignatureOutput + ?Sized),
171) -> Result<(), CryptError> {
172 output.update(&(last_seen.len() as i32).to_be_bytes())?;
174
175 for signature in last_seen.as_slice() {
177 output.update(signature)?;
178 }
179
180 Ok(())
181}
182
183#[derive(Debug, Error)]
185pub enum ChainError {
186 #[error("Missing profile key")]
188 MissingProfileKey,
189
190 #[error("Chain is broken")]
192 ChainBroken,
193
194 #[error("Profile key has expired")]
196 ExpiredProfileKey,
197
198 #[error("Invalid signature")]
200 InvalidSignature,
201
202 #[error("Message out of order")]
204 OutOfOrderChat,
205
206 #[error("Message has expired")]
208 MessageExpired,
209
210 #[error("Cryptographic error: {0}")]
212 CryptoError(#[from] CryptError),
213}
214
215#[derive(Debug)]
219pub struct SignedMessageChain {
220 next_link: Option<SignedMessageLink>,
222 last_timestamp: SystemTime,
224}
225
226impl SignedMessageChain {
227 #[must_use]
229 pub const fn new(sender: Uuid, session_id: Uuid) -> Self {
230 Self {
231 next_link: Some(SignedMessageLink::root(sender, session_id)),
232 last_timestamp: UNIX_EPOCH,
233 }
234 }
235
236 #[must_use]
238 pub const fn next_link(&self) -> Option<&SignedMessageLink> {
239 self.next_link.as_ref()
240 }
241
242 #[must_use]
244 pub const fn is_broken(&self) -> bool {
245 self.next_link.is_none()
246 }
247
248 pub const fn break_chain(&mut self) {
250 self.next_link = None;
251 }
252
253 pub fn validate_and_advance(
260 &mut self,
261 body: &SignedMessageBody,
262 ) -> Result<SignedMessageLink, ChainError> {
263 let link = self.next_link.clone().ok_or(ChainError::ChainBroken)?;
265
266 if body.time_stamp < self.last_timestamp {
268 self.break_chain();
269 return Err(ChainError::OutOfOrderChat);
270 }
271
272 self.last_timestamp = body.time_stamp;
274 self.next_link = link.advance();
275
276 Ok(link)
277 }
278
279 pub const fn reset(&mut self, sender: Uuid, session_id: Uuid) {
281 self.next_link = Some(SignedMessageLink::root(sender, session_id));
282 self.last_timestamp = UNIX_EPOCH;
283 }
284}
285
286pub struct MessageSignatureUpdater<'a> {
288 link: &'a SignedMessageLink,
289 body: &'a SignedMessageBody,
290}
291
292impl<'a> MessageSignatureUpdater<'a> {
293 #[must_use]
295 pub const fn new(link: &'a SignedMessageLink, body: &'a SignedMessageBody) -> Self {
296 Self { link, body }
297 }
298}
299
300impl SignatureUpdater for MessageSignatureUpdater<'_> {
301 fn update(&self, output: &mut dyn SignatureOutput) -> Result<(), CryptError> {
302 output.update(&1i32.to_be_bytes())?;
304
305 self.link.update_signature(output)?;
307
308 self.body.update_signature(output)?;
310
311 Ok(())
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn test_link_advance() {
321 let link = SignedMessageLink::root(Uuid::new_v4(), Uuid::new_v4());
322 assert_eq!(link.index, 0);
323
324 let next = link.advance().expect("Should advance from index 0");
325 assert_eq!(next.index, 1);
326 assert_eq!(next.sender, link.sender);
327 assert_eq!(next.session_id, link.session_id);
328 }
329
330 #[test]
331 fn test_link_advance_at_max() {
332 let link = SignedMessageLink::new(i32::MAX, Uuid::new_v4(), Uuid::new_v4());
333 assert!(link.advance().is_none());
334 }
335
336 #[test]
337 fn test_link_descendant() {
338 let link1 = SignedMessageLink::root(Uuid::new_v4(), Uuid::new_v4());
339 let link2 = link1.advance().expect("Should advance from root");
340 let link3 = link2.advance().expect("Should advance from index 1");
341
342 assert!(link2.is_descendant_of(&link1));
343 assert!(link3.is_descendant_of(&link2));
344 assert!(link3.is_descendant_of(&link1));
345 assert!(!link1.is_descendant_of(&link2));
346 }
347
348 #[test]
349 fn test_chain_validation() {
350 let sender = Uuid::new_v4();
351 let session = Uuid::new_v4();
352 let mut chain = SignedMessageChain::new(sender, session);
353
354 let body = SignedMessageBody::unsigned("Hello".to_string());
355 let link = chain
356 .validate_and_advance(&body)
357 .expect("First message should validate");
358 assert_eq!(link.index, 0);
359
360 let body2 = SignedMessageBody::unsigned("World".to_string());
361 let link2 = chain
362 .validate_and_advance(&body2)
363 .expect("Second message should validate");
364 assert_eq!(link2.index, 1);
365 }
366
367 #[test]
368 fn test_chain_out_of_order() {
369 let sender = Uuid::new_v4();
370 let session = Uuid::new_v4();
371 let mut chain = SignedMessageChain::new(sender, session);
372
373 let body1 = SignedMessageBody::unsigned("First".to_string());
374 chain
375 .validate_and_advance(&body1)
376 .expect("First message should validate");
377
378 let body2 =
380 SignedMessageBody::new("Second".to_string(), UNIX_EPOCH, 0, LastSeen::default());
381
382 let result = chain.validate_and_advance(&body2);
383 assert!(matches!(result, Err(ChainError::OutOfOrderChat)));
384 assert!(chain.is_broken());
385 }
386}