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(&self, output: &mut dyn SignatureOutput) -> Result<(), CryptError> {
56 output.update(&self.sender.as_u128().to_be_bytes())?;
58
59 output.update(&self.session_id.as_u128().to_be_bytes())?;
61
62 output.update(&self.index.to_be_bytes())?;
64
65 Ok(())
66 }
67
68 #[must_use]
70 pub fn is_descendant_of(&self, other: &SignedMessageLink) -> bool {
71 self.index > other.index
72 && self.sender == other.sender
73 && self.session_id == other.session_id
74 }
75
76 #[must_use]
78 pub const fn advance(&self) -> Option<Self> {
79 if self.index == i32::MAX {
80 None
81 } else {
82 Some(Self {
83 index: self.index + 1,
84 sender: self.sender,
85 session_id: self.session_id,
86 })
87 }
88 }
89}
90
91#[derive(Clone, Debug)]
95pub struct SignedMessageBody {
96 pub content: String,
98 pub time_stamp: SystemTime,
100 pub salt: i64,
102 pub last_seen: LastSeen,
104}
105
106impl SignedMessageBody {
107 #[must_use]
109 pub const fn new(
110 content: String,
111 time_stamp: SystemTime,
112 salt: i64,
113 last_seen: LastSeen,
114 ) -> Self {
115 Self {
116 content,
117 time_stamp,
118 salt,
119 last_seen,
120 }
121 }
122
123 #[must_use]
125 pub fn unsigned(content: String) -> Self {
126 Self {
127 content,
128 time_stamp: SystemTime::now(),
129 salt: 0,
130 last_seen: LastSeen::default(),
131 }
132 }
133
134 pub fn update_signature(&self, output: &mut dyn SignatureOutput) -> Result<(), CryptError> {
136 output.update(&self.salt.to_be_bytes())?;
138
139 let epoch_seconds = self
141 .time_stamp
142 .duration_since(UNIX_EPOCH)
143 .unwrap_or_default()
144 .as_secs() as i64;
145 output.update(&epoch_seconds.to_be_bytes())?;
146
147 let content_bytes = self.content.as_bytes();
149 output.update(&(content_bytes.len() as i32).to_be_bytes())?;
150
151 output.update(content_bytes)?;
153
154 update_last_seen_signature(&self.last_seen, output)?;
156
157 Ok(())
158 }
159}
160
161fn update_last_seen_signature(
163 last_seen: &LastSeen,
164 output: &mut dyn SignatureOutput,
165) -> Result<(), CryptError> {
166 output.update(&(last_seen.len() as i32).to_be_bytes())?;
168
169 for signature in last_seen.as_slice() {
171 output.update(signature)?;
172 }
173
174 Ok(())
175}
176
177#[derive(Debug, Error)]
179pub enum ChainError {
180 #[error("Missing profile key")]
182 MissingProfileKey,
183
184 #[error("Chain is broken")]
186 ChainBroken,
187
188 #[error("Profile key has expired")]
190 ExpiredProfileKey,
191
192 #[error("Invalid signature")]
194 InvalidSignature,
195
196 #[error("Message out of order")]
198 OutOfOrderChat,
199
200 #[error("Message has expired")]
202 MessageExpired,
203
204 #[error("Cryptographic error: {0}")]
206 CryptoError(#[from] CryptError),
207}
208
209#[derive(Debug)]
213pub struct SignedMessageChain {
214 next_link: Option<SignedMessageLink>,
216 last_timestamp: SystemTime,
218}
219
220impl SignedMessageChain {
221 #[must_use]
223 pub const fn new(sender: Uuid, session_id: Uuid) -> Self {
224 Self {
225 next_link: Some(SignedMessageLink::root(sender, session_id)),
226 last_timestamp: UNIX_EPOCH,
227 }
228 }
229
230 #[must_use]
232 pub const fn next_link(&self) -> Option<&SignedMessageLink> {
233 self.next_link.as_ref()
234 }
235
236 #[must_use]
238 pub const fn is_broken(&self) -> bool {
239 self.next_link.is_none()
240 }
241
242 pub const fn break_chain(&mut self) {
244 self.next_link = None;
245 }
246
247 pub fn validate_and_advance(
254 &mut self,
255 body: &SignedMessageBody,
256 ) -> Result<SignedMessageLink, ChainError> {
257 let link = self.next_link.clone().ok_or(ChainError::ChainBroken)?;
259
260 if body.time_stamp < self.last_timestamp {
262 self.break_chain();
263 return Err(ChainError::OutOfOrderChat);
264 }
265
266 self.last_timestamp = body.time_stamp;
268 self.next_link = link.advance();
269
270 Ok(link)
271 }
272
273 pub const fn reset(&mut self, sender: Uuid, session_id: Uuid) {
275 self.next_link = Some(SignedMessageLink::root(sender, session_id));
276 self.last_timestamp = UNIX_EPOCH;
277 }
278}
279
280pub struct MessageSignatureUpdater<'a> {
282 link: &'a SignedMessageLink,
283 body: &'a SignedMessageBody,
284}
285
286impl<'a> MessageSignatureUpdater<'a> {
287 #[must_use]
289 pub const fn new(link: &'a SignedMessageLink, body: &'a SignedMessageBody) -> Self {
290 Self { link, body }
291 }
292}
293
294impl SignatureUpdater for MessageSignatureUpdater<'_> {
295 fn update(&self, output: &mut dyn SignatureOutput) -> Result<(), CryptError> {
296 output.update(&1i32.to_be_bytes())?;
298
299 self.link.update_signature(output)?;
301
302 self.body.update_signature(output)?;
304
305 Ok(())
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_link_advance() {
315 let link = SignedMessageLink::root(Uuid::new_v4(), Uuid::new_v4());
316 assert_eq!(link.index, 0);
317
318 let next = link.advance().expect("Should advance from index 0");
319 assert_eq!(next.index, 1);
320 assert_eq!(next.sender, link.sender);
321 assert_eq!(next.session_id, link.session_id);
322 }
323
324 #[test]
325 fn test_link_advance_at_max() {
326 let link = SignedMessageLink::new(i32::MAX, Uuid::new_v4(), Uuid::new_v4());
327 assert!(link.advance().is_none());
328 }
329
330 #[test]
331 fn test_link_descendant() {
332 let link1 = SignedMessageLink::root(Uuid::new_v4(), Uuid::new_v4());
333 let link2 = link1.advance().expect("Should advance from root");
334 let link3 = link2.advance().expect("Should advance from index 1");
335
336 assert!(link2.is_descendant_of(&link1));
337 assert!(link3.is_descendant_of(&link2));
338 assert!(link3.is_descendant_of(&link1));
339 assert!(!link1.is_descendant_of(&link2));
340 }
341
342 #[test]
343 fn test_chain_validation() {
344 let sender = Uuid::new_v4();
345 let session = Uuid::new_v4();
346 let mut chain = SignedMessageChain::new(sender, session);
347
348 let body = SignedMessageBody::unsigned("Hello".to_string());
349 let link = chain
350 .validate_and_advance(&body)
351 .expect("First message should validate");
352 assert_eq!(link.index, 0);
353
354 let body2 = SignedMessageBody::unsigned("World".to_string());
355 let link2 = chain
356 .validate_and_advance(&body2)
357 .expect("Second message should validate");
358 assert_eq!(link2.index, 1);
359 }
360
361 #[test]
362 fn test_chain_out_of_order() {
363 let sender = Uuid::new_v4();
364 let session = Uuid::new_v4();
365 let mut chain = SignedMessageChain::new(sender, session);
366
367 let body1 = SignedMessageBody::unsigned("First".to_string());
368 chain
369 .validate_and_advance(&body1)
370 .expect("First message should validate");
371
372 let body2 =
374 SignedMessageBody::new("Second".to_string(), UNIX_EPOCH, 0, LastSeen::default());
375
376 let result = chain.validate_and_advance(&body2);
377 assert!(matches!(result, Err(ChainError::OutOfOrderChat)));
378 assert!(chain.is_broken());
379 }
380}