Skip to main content

ferogram_mtproto/
encrypted.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3//
4// ferogram: async Telegram MTProto client in Rust
5// https://github.com/ankit-chaubey/ferogram
6//
7// If you use or modify this code, keep this notice at the top of your file
8// and include the LICENSE-MIT or LICENSE-APACHE file from this repository:
9// https://github.com/ankit-chaubey/ferogram
10
11use std::collections::{HashSet, VecDeque};
12use std::time::{SystemTime, UNIX_EPOCH};
13
14use ferogram_crypto::{AuthKey, DequeBuffer, decrypt_data_v2, encrypt_data_v2};
15use ferogram_tl_types::RemoteCall;
16
17/// Rolling deduplication buffer for server msg_ids.
18const SEEN_MSG_IDS_MAX: usize = 500;
19
20/// Errors that can occur when decrypting a server message.
21#[derive(Debug)]
22pub enum DecryptError {
23    /// The underlying crypto layer rejected the message.
24    Crypto(ferogram_crypto::DecryptError),
25    /// The decrypted inner message was too short to contain a valid header.
26    FrameTooShort,
27    /// Session-ID mismatch (possible replay or wrong connection).
28    SessionMismatch,
29    /// Server msg_id is outside the allowed time window (-300s / +30s).
30    MsgIdTimeWindow,
31    /// This msg_id was already seen in the rolling 500-entry buffer.
32    DuplicateMsgId,
33    /// Server msg_id has even parity; server messages must have odd msg_id.
34    InvalidMsgId,
35}
36
37impl std::fmt::Display for DecryptError {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Self::Crypto(e) => write!(f, "crypto: {e}"),
41            Self::FrameTooShort => write!(f, "inner plaintext too short"),
42            Self::SessionMismatch => write!(f, "session_id mismatch"),
43            Self::MsgIdTimeWindow => write!(f, "server msg_id outside -300s/+30s time window"),
44            Self::DuplicateMsgId => write!(f, "duplicate server msg_id (replay)"),
45            Self::InvalidMsgId => write!(f, "server msg_id has even parity (must be odd)"),
46        }
47    }
48}
49impl std::error::Error for DecryptError {}
50
51/// The inner payload extracted from a successfully decrypted server frame.
52pub struct DecryptedMessage {
53    /// `salt` sent by the server.
54    pub salt: i64,
55    /// The `session_id` from the frame.
56    pub session_id: i64,
57    /// The `msg_id` of the inner message.
58    pub msg_id: i64,
59    /// `seq_no` of the inner message.
60    pub seq_no: i32,
61    /// TL-serialized body of the inner message.
62    pub body: Vec<u8>,
63}
64
65/// Shared, persistent dedup ring for server msg_ids.
66///
67/// `VecDeque` provides O(1) push/pop for eviction order; `HashSet` provides
68/// O(1) membership checks, replacing the previous O(n) `VecDeque::contains`
69/// scan that became a serialisation bottleneck under 12 concurrent workers.
70///
71/// Outlives individual `EncryptedSession` objects so that replayed frames
72/// from a prior connection cycle are still rejected after reconnect.
73pub type SeenMsgIds = std::sync::Arc<std::sync::Mutex<(VecDeque<i64>, HashSet<i64>)>>;
74
75/// Allocate a fresh seen-msg_id ring.
76pub fn new_seen_msg_ids() -> SeenMsgIds {
77    std::sync::Arc::new(std::sync::Mutex::new((
78        VecDeque::with_capacity(SEEN_MSG_IDS_MAX),
79        HashSet::with_capacity(SEEN_MSG_IDS_MAX),
80    )))
81}
82
83/// MTProto 2.0 encrypted session state.
84pub struct EncryptedSession {
85    auth_key: AuthKey,
86    session_id: i64,
87    sequence: i32,
88    last_msg_id: i64,
89    /// Current server salt to include in outgoing messages.
90    pub salt: i64,
91    /// Clock skew in seconds vs. server.
92    pub time_offset: i32,
93    /// Rolling 500-entry dedup buffer of seen server msg_ids.
94    /// Shared with the owning DcConnection so it survives reconnects.
95    seen_msg_ids: SeenMsgIds,
96}
97
98impl EncryptedSession {
99    /// Create a new encrypted session from the output of `authentication::finish`.
100    ///
101    /// `seen_msg_ids` should be the persistent ring owned by the `DcConnection`
102    /// (or any other owner that outlives individual sessions).  Pass
103    /// `new_seen_msg_ids()` for the very first connection on a slot.
104    pub fn new(auth_key: [u8; 256], first_salt: i64, time_offset: i32) -> Self {
105        Self::with_seen(auth_key, first_salt, time_offset, new_seen_msg_ids())
106    }
107
108    /// Like `new` but reuses an existing seen-msg_id ring (reconnect path).
109    pub fn with_seen(
110        auth_key: [u8; 256],
111        first_salt: i64,
112        time_offset: i32,
113        seen_msg_ids: SeenMsgIds,
114    ) -> Self {
115        let mut rnd = [0u8; 8];
116        getrandom::getrandom(&mut rnd).expect("getrandom");
117        Self {
118            auth_key: AuthKey::from_bytes(auth_key),
119            session_id: i64::from_le_bytes(rnd),
120            sequence: 0,
121            last_msg_id: 0,
122            salt: first_salt,
123            time_offset,
124            seen_msg_ids,
125        }
126    }
127
128    /// Return a clone of the shared seen-msg_id ring for passing to a
129    /// replacement session on reconnect.
130    pub fn seen_msg_ids(&self) -> SeenMsgIds {
131        std::sync::Arc::clone(&self.seen_msg_ids)
132    }
133
134    /// Compute the next message ID (based on corrected server time).
135    fn next_msg_id(&mut self) -> i64 {
136        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
137        // Keep arithmetic in u64: seconds since epoch with time_offset applied.
138        let secs = now.as_secs().wrapping_add(self.time_offset as i64 as u64);
139        let nanos = now.subsec_nanos() as u64;
140        let mut id = ((secs << 32) | (nanos << 2)) as i64;
141        if self.last_msg_id >= id {
142            id = self.last_msg_id + 4;
143        }
144        self.last_msg_id = id;
145        id
146    }
147
148    /// Next content-related seq_no (odd) and advance the counter.
149    /// Used for all regular RPC requests.
150    fn next_seq_no(&mut self) -> i32 {
151        let n = self.sequence * 2 + 1;
152        self.sequence += 1;
153        n
154    }
155
156    /// Return the current even seq_no WITHOUT advancing the counter.
157    ///
158    /// Service messages (MsgsAck, containers, etc.) MUST use an even seqno
159    /// per the MTProto spec so the server does not expect a reply.
160    pub fn next_seq_no_ncr(&self) -> i32 {
161        self.sequence * 2
162    }
163
164    /// Correct the outgoing sequence counter when the server reports a
165    /// `bad_msg_notification` with error codes 32 (seq_no too low) or
166    /// 33 (seq_no too high).
167    ///
168    pub fn correct_seq_no(&mut self, code: u32) {
169        match code {
170            32 => {
171                // seq_no too low: jump forward so next send is well above server expectation
172                self.sequence += 64;
173                log::debug!(
174                    "[ferogram] seq_no correction: code 32, bumped seq to {}",
175                    self.sequence
176                );
177            }
178            33 => {
179                // seq_no too high: step back, but never below 1 to avoid
180                // re-using seq_no=1 which was already sent this session.
181                // Zeroing would make the next content message get seq_no=1,
182                // which the server already saw and will reject again with code 32.
183                self.sequence = self.sequence.saturating_sub(16).max(1);
184                log::debug!(
185                    "[ferogram] seq_no correction: code 33, lowered seq to {}",
186                    self.sequence
187                );
188            }
189            _ => {}
190        }
191    }
192
193    /// Undo the last `next_seq_no` increment.
194    ///
195    /// Called before retrying a request after `bad_server_salt` so the resent
196    /// message uses the same seq_no slot rather than advancing the counter a
197    /// second time (which would produce seq_no too high → bad_msg_notification
198    /// code 33 → server closes TCP → early eof).
199    pub fn undo_seq_no(&mut self) {
200        self.sequence = self.sequence.saturating_sub(1);
201    }
202
203    /// Re-derive the clock skew from a server-provided `msg_id`.
204    ///
205    /// Called on `bad_msg_notification` error codes 16 (msg_id too low) and
206    /// 17 (msg_id too high) so clock drift is corrected at any point in the
207    /// session, not only at connect time.
208    ///
209    pub fn correct_time_offset(&mut self, server_msg_id: i64) {
210        // Upper 32 bits of msg_id = Unix seconds on the server
211        let server_time = (server_msg_id >> 32) as i32;
212        let local_now = SystemTime::now()
213            .duration_since(UNIX_EPOCH)
214            .unwrap()
215            .as_secs() as i32;
216        let new_offset = server_time.wrapping_sub(local_now);
217        log::debug!(
218            "[ferogram] time_offset correction: {} → {} (server_time={server_time})",
219            self.time_offset,
220            new_offset
221        );
222        self.time_offset = new_offset;
223        // Seed last_msg_id from the server's msg_id (bits 1-0 cleared to 0b00)
224        // so the next next_msg_id() call produces a strictly larger value.
225        self.last_msg_id = (server_msg_id & !0x3i64).max(self.last_msg_id);
226    }
227
228    /// Allocate a fresh `(msg_id, seqno)` pair for an inner container message
229    /// WITHOUT encrypting anything.
230    ///
231    /// `content_related = true`  → odd seqno, advances counter  (regular RPCs)
232    /// `content_related = false` → even seqno, no advance       (MsgsAck, container)
233    ///
234    pub fn alloc_msg_seqno(&mut self, content_related: bool) -> (i64, i32) {
235        let msg_id = self.next_msg_id();
236        let seqno = if content_related {
237            self.next_seq_no()
238        } else {
239            self.next_seq_no_ncr()
240        };
241        (msg_id, seqno)
242    }
243
244    /// Encrypt a pre-serialized TL body into a wire-ready MTProto frame.
245    ///
246    /// `content_related` controls whether the seqno is odd (content, advances
247    /// the counter) or even (service, no advance).
248    ///
249    /// Returns `(encrypted_wire_bytes, msg_id)`.
250    /// Used for (bad_msg re-send) and (container inner messages).
251    pub fn pack_body_with_msg_id(&mut self, body: &[u8], content_related: bool) -> (Vec<u8>, i64) {
252        let msg_id = self.next_msg_id();
253        let seq_no = if content_related {
254            self.next_seq_no()
255        } else {
256            self.next_seq_no_ncr()
257        };
258
259        let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
260        let mut buf = DequeBuffer::with_capacity(inner_len, 32);
261        buf.extend(self.salt.to_le_bytes());
262        buf.extend(self.session_id.to_le_bytes());
263        buf.extend(msg_id.to_le_bytes());
264        buf.extend(seq_no.to_le_bytes());
265        buf.extend((body.len() as u32).to_le_bytes());
266        buf.extend(body.iter().copied());
267
268        encrypt_data_v2(&mut buf, &self.auth_key);
269        (buf.as_ref().to_vec(), msg_id)
270    }
271
272    /// Encrypt a pre-built `msg_container` body (the container itself is
273    /// a non-content-related message with an even seqno).
274    ///
275    /// Returns `(encrypted_wire_bytes, container_msg_id)`.
276    /// The container_msg_id is needed so callers can map it back to inner
277    /// requests when a bad_msg_notification or bad_server_salt arrives for
278    /// the container rather than the individual inner message.
279    ///
280    pub fn pack_container(&mut self, container_body: &[u8]) -> (Vec<u8>, i64) {
281        self.pack_body_with_msg_id(container_body, false)
282    }
283
284    /// Encrypt `body` using a **caller-supplied** `msg_id` instead of generating one.
285    ///
286    /// Required by `auth.bindTempAuthKey`, which must use the same `msg_id`
287    /// in both the outer MTProto envelope and the inner `bind_auth_key_inner`.
288    pub fn pack_body_at_msg_id(&mut self, body: &[u8], msg_id: i64) -> Vec<u8> {
289        let seq_no = self.next_seq_no();
290        let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
291        let mut buf = DequeBuffer::with_capacity(inner_len, 32);
292        buf.extend(self.salt.to_le_bytes());
293        buf.extend(self.session_id.to_le_bytes());
294        buf.extend(msg_id.to_le_bytes());
295        buf.extend(seq_no.to_le_bytes());
296        buf.extend((body.len() as u32).to_le_bytes());
297        buf.extend(body.iter().copied());
298        encrypt_data_v2(&mut buf, &self.auth_key);
299        buf.as_ref().to_vec()
300    }
301
302    /// Serialize and encrypt a TL function into a wire-ready byte vector.
303    pub fn pack_serializable<S: ferogram_tl_types::Serializable>(&mut self, call: &S) -> Vec<u8> {
304        let body = call.to_bytes();
305        let msg_id = self.next_msg_id();
306        let seq_no = self.next_seq_no();
307
308        let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
309        let mut buf = DequeBuffer::with_capacity(inner_len, 32);
310        buf.extend(self.salt.to_le_bytes());
311        buf.extend(self.session_id.to_le_bytes());
312        buf.extend(msg_id.to_le_bytes());
313        buf.extend(seq_no.to_le_bytes());
314        buf.extend((body.len() as u32).to_le_bytes());
315        buf.extend(body.iter().copied());
316
317        encrypt_data_v2(&mut buf, &self.auth_key);
318        buf.as_ref().to_vec()
319    }
320
321    /// Like `pack_serializable` but also returns the `msg_id`.
322    pub fn pack_serializable_with_msg_id<S: ferogram_tl_types::Serializable>(
323        &mut self,
324        call: &S,
325    ) -> (Vec<u8>, i64) {
326        let body = call.to_bytes();
327        let msg_id = self.next_msg_id();
328        let seq_no = self.next_seq_no();
329        let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
330        let mut buf = DequeBuffer::with_capacity(inner_len, 32);
331        buf.extend(self.salt.to_le_bytes());
332        buf.extend(self.session_id.to_le_bytes());
333        buf.extend(msg_id.to_le_bytes());
334        buf.extend(seq_no.to_le_bytes());
335        buf.extend((body.len() as u32).to_le_bytes());
336        buf.extend(body.iter().copied());
337        encrypt_data_v2(&mut buf, &self.auth_key);
338        (buf.as_ref().to_vec(), msg_id)
339    }
340
341    /// Like [`pack`] but also returns the `msg_id` allocated for this message.
342    pub fn pack_with_msg_id<R: RemoteCall>(&mut self, call: &R) -> (Vec<u8>, i64) {
343        let body = call.to_bytes();
344        let msg_id = self.next_msg_id();
345        let seq_no = self.next_seq_no();
346        let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
347        let mut buf = DequeBuffer::with_capacity(inner_len, 32);
348        buf.extend(self.salt.to_le_bytes());
349        buf.extend(self.session_id.to_le_bytes());
350        buf.extend(msg_id.to_le_bytes());
351        buf.extend(seq_no.to_le_bytes());
352        buf.extend((body.len() as u32).to_le_bytes());
353        buf.extend(body.iter().copied());
354        encrypt_data_v2(&mut buf, &self.auth_key);
355        (buf.as_ref().to_vec(), msg_id)
356    }
357
358    /// Encrypt and frame a [`RemoteCall`] into a ready-to-send MTProto message.
359    pub fn pack<R: RemoteCall>(&mut self, call: &R) -> Vec<u8> {
360        let body = call.to_bytes();
361        let msg_id = self.next_msg_id();
362        let seq_no = self.next_seq_no();
363
364        let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
365        let mut buf = DequeBuffer::with_capacity(inner_len, 32);
366        buf.extend(self.salt.to_le_bytes());
367        buf.extend(self.session_id.to_le_bytes());
368        buf.extend(msg_id.to_le_bytes());
369        buf.extend(seq_no.to_le_bytes());
370        buf.extend((body.len() as u32).to_le_bytes());
371        buf.extend(body.iter().copied());
372
373        encrypt_data_v2(&mut buf, &self.auth_key);
374        buf.as_ref().to_vec()
375    }
376
377    /// Decrypt an encrypted server frame.
378    pub fn unpack(&self, frame: &mut [u8]) -> Result<DecryptedMessage, DecryptError> {
379        let plaintext = decrypt_data_v2(frame, &self.auth_key).map_err(DecryptError::Crypto)?;
380
381        if plaintext.len() < 32 {
382            return Err(DecryptError::FrameTooShort);
383        }
384
385        let salt = i64::from_le_bytes(plaintext[..8].try_into().unwrap());
386        let session_id = i64::from_le_bytes(plaintext[8..16].try_into().unwrap());
387        let msg_id = i64::from_le_bytes(plaintext[16..24].try_into().unwrap());
388        let seq_no = i32::from_le_bytes(plaintext[24..28].try_into().unwrap());
389        let body_len = u32::from_le_bytes(plaintext[28..32].try_into().unwrap()) as usize;
390
391        if session_id != self.session_id {
392            return Err(DecryptError::SessionMismatch);
393        }
394
395        // MTProto: server msg_id must be odd.
396        if msg_id & 1 == 0 {
397            return Err(DecryptError::InvalidMsgId);
398        }
399
400        // Time window is intentionally asymmetric: -300s past, +30s future.
401        let server_secs = (msg_id as u64 >> 32) as i64;
402        let now = SystemTime::now()
403            .duration_since(UNIX_EPOCH)
404            .unwrap()
405            .as_secs() as i64;
406        let corrected = now + self.time_offset as i64;
407        let skew = server_secs - corrected;
408        if !(-300..=30).contains(&skew) {
409            return Err(DecryptError::MsgIdTimeWindow);
410        }
411
412        // Rolling 500-entry dedup.
413        {
414            let mut seen = self.seen_msg_ids.lock().unwrap();
415            if seen.1.contains(&msg_id) {
416                return Err(DecryptError::DuplicateMsgId);
417            }
418            seen.0.push_back(msg_id);
419            seen.1.insert(msg_id);
420            if seen.0.len() > SEEN_MSG_IDS_MAX
421                && let Some(old_id) = seen.0.pop_front()
422            {
423                seen.1.remove(&old_id);
424            }
425        }
426
427        // Maximum body length: 16 MB.
428        if body_len > 16 * 1024 * 1024 {
429            return Err(DecryptError::FrameTooShort);
430        }
431        if 32 + body_len > plaintext.len() {
432            return Err(DecryptError::FrameTooShort);
433        }
434        // TL payload must be 4-byte aligned.
435        if !body_len.is_multiple_of(4) {
436            return Err(DecryptError::FrameTooShort);
437        }
438        // MTProto 2.0: minimum padding is 12 bytes; no upper bound.
439        let padding = plaintext.len() - 32 - body_len;
440        if padding < 12 {
441            return Err(DecryptError::FrameTooShort);
442        }
443        let body = plaintext[32..32 + body_len].to_vec();
444
445        Ok(DecryptedMessage {
446            salt,
447            session_id,
448            msg_id,
449            seq_no,
450            body,
451        })
452    }
453
454    /// Return the auth_key bytes (for persistence).
455    pub fn auth_key_bytes(&self) -> [u8; 256] {
456        self.auth_key.to_bytes()
457    }
458
459    /// Return the current session_id.
460    pub fn session_id(&self) -> i64 {
461        self.session_id
462    }
463
464    /// Reset session state: new random session_id, zeroed seq_no and last_msg_id,
465    /// cleared dedup buffer.
466    ///
467    /// Called on `bad_msg_notification` error codes 32/33 (seq_no mismatch).
468    /// Creates a new session_id and resets seq_no to avoid persistent desync.
469    pub fn reset_session(&mut self) {
470        let mut rnd = [0u8; 8];
471        getrandom::getrandom(&mut rnd).expect("getrandom");
472        let old_session = self.session_id;
473        self.session_id = i64::from_le_bytes(rnd);
474        self.sequence = 0;
475        self.last_msg_id = 0;
476        // Do not clear seen_msg_ids: the ring is shared with the owning
477        // DcConnection and must survive session resets to reject replayed frames.
478        log::debug!(
479            "[ferogram] session reset: {:#018x} → {:#018x}",
480            old_session,
481            self.session_id
482        );
483    }
484}
485
486impl EncryptedSession {
487    /// Like [`decrypt_frame`] but also performs seen-msg_id deduplication using the
488    /// supplied ring.  Pass `&self.inner.seen_msg_ids` from the client.
489    pub fn decrypt_frame_dedup(
490        auth_key: &[u8; 256],
491        session_id: i64,
492        frame: &mut [u8],
493        seen: &SeenMsgIds,
494    ) -> Result<DecryptedMessage, DecryptError> {
495        let msg = Self::decrypt_frame_with_offset(auth_key, session_id, frame, 0)?;
496        {
497            let mut s = seen.lock().unwrap();
498            if s.1.contains(&msg.msg_id) {
499                return Err(DecryptError::DuplicateMsgId);
500            }
501            s.0.push_back(msg.msg_id);
502            s.1.insert(msg.msg_id);
503            if s.0.len() > SEEN_MSG_IDS_MAX
504                && let Some(old_id) = s.0.pop_front()
505            {
506                s.1.remove(&old_id);
507            }
508        }
509        Ok(msg)
510    }
511
512    /// Decrypt a frame using explicit key + session_id: no mutable state needed.
513    /// Used by the split-reader task so it can decrypt without locking the writer.
514    /// `time_offset` is the session's current clock skew (seconds); pass 0 if unknown.
515    pub fn decrypt_frame(
516        auth_key: &[u8; 256],
517        session_id: i64,
518        frame: &mut [u8],
519    ) -> Result<DecryptedMessage, DecryptError> {
520        Self::decrypt_frame_with_offset(auth_key, session_id, frame, 0)
521    }
522
523    /// Like [`decrypt_frame`] but applies the time-window check with the given
524    /// `time_offset` (seconds, server_time − local_time).
525    pub fn decrypt_frame_with_offset(
526        auth_key: &[u8; 256],
527        session_id: i64,
528        frame: &mut [u8],
529        time_offset: i32,
530    ) -> Result<DecryptedMessage, DecryptError> {
531        let key = AuthKey::from_bytes(*auth_key);
532        let plaintext = decrypt_data_v2(frame, &key).map_err(DecryptError::Crypto)?;
533        if plaintext.len() < 32 {
534            return Err(DecryptError::FrameTooShort);
535        }
536        let salt = i64::from_le_bytes(plaintext[..8].try_into().unwrap());
537        let sid = i64::from_le_bytes(plaintext[8..16].try_into().unwrap());
538        let msg_id = i64::from_le_bytes(plaintext[16..24].try_into().unwrap());
539        let seq_no = i32::from_le_bytes(plaintext[24..28].try_into().unwrap());
540        let body_len = u32::from_le_bytes(plaintext[28..32].try_into().unwrap()) as usize;
541        if sid != session_id {
542            return Err(DecryptError::SessionMismatch);
543        }
544        // MTProto: server msg_id must be odd.
545        if msg_id & 1 == 0 {
546            return Err(DecryptError::InvalidMsgId);
547        }
548        // Time window is intentionally asymmetric: -300s past, +30s future.
549        let server_secs = (msg_id as u64 >> 32) as i64;
550        let now = SystemTime::now()
551            .duration_since(UNIX_EPOCH)
552            .unwrap()
553            .as_secs() as i64;
554        let corrected = now + time_offset as i64;
555        let skew = server_secs - corrected;
556        if !(-300..=30).contains(&skew) {
557            return Err(DecryptError::MsgIdTimeWindow);
558        }
559        // Maximum body length: 16 MB.
560        if body_len > 16 * 1024 * 1024 {
561            return Err(DecryptError::FrameTooShort);
562        }
563        if 32 + body_len > plaintext.len() {
564            return Err(DecryptError::FrameTooShort);
565        }
566        // TL payload must be 4-byte aligned.
567        if !body_len.is_multiple_of(4) {
568            return Err(DecryptError::FrameTooShort);
569        }
570        // MTProto 2.0: minimum padding is 12 bytes; no upper bound.
571        let padding = plaintext.len() - 32 - body_len;
572        if padding < 12 {
573            return Err(DecryptError::FrameTooShort);
574        }
575        let body = plaintext[32..32 + body_len].to_vec();
576        Ok(DecryptedMessage {
577            salt,
578            session_id: sid,
579            msg_id,
580            seq_no,
581            body,
582        })
583    }
584}