use std::collections::{HashSet, VecDeque};
use std::time::{SystemTime, UNIX_EPOCH};
use ferogram_crypto::{AuthKey, DequeBuffer, decrypt_data_v2, encrypt_data_v2};
use ferogram_tl_types::RemoteCall;
const SEEN_MSG_IDS_MAX: usize = 500;
#[derive(Debug)]
pub enum DecryptError {
Crypto(ferogram_crypto::DecryptError),
FrameTooShort,
SessionMismatch,
MsgIdTimeWindow,
DuplicateMsgId,
InvalidMsgId,
}
impl std::fmt::Display for DecryptError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Crypto(e) => write!(f, "crypto: {e}"),
Self::FrameTooShort => write!(f, "inner plaintext too short"),
Self::SessionMismatch => write!(f, "session_id mismatch"),
Self::MsgIdTimeWindow => write!(f, "server msg_id outside -300s/+30s time window"),
Self::DuplicateMsgId => write!(f, "duplicate server msg_id (replay)"),
Self::InvalidMsgId => write!(f, "server msg_id has even parity (must be odd)"),
}
}
}
impl std::error::Error for DecryptError {}
pub struct DecryptedMessage {
pub salt: i64,
pub session_id: i64,
pub msg_id: i64,
pub seq_no: i32,
pub body: Vec<u8>,
}
pub type SeenMsgIds = std::sync::Arc<std::sync::Mutex<(VecDeque<i64>, HashSet<i64>)>>;
pub fn new_seen_msg_ids() -> SeenMsgIds {
std::sync::Arc::new(std::sync::Mutex::new((
VecDeque::with_capacity(SEEN_MSG_IDS_MAX),
HashSet::with_capacity(SEEN_MSG_IDS_MAX),
)))
}
pub struct EncryptedSession {
auth_key: AuthKey,
session_id: i64,
sequence: i32,
last_msg_id: i64,
pub salt: i64,
pub time_offset: i32,
seen_msg_ids: SeenMsgIds,
}
impl EncryptedSession {
pub fn new(auth_key: [u8; 256], first_salt: i64, time_offset: i32) -> Self {
Self::with_seen(auth_key, first_salt, time_offset, new_seen_msg_ids())
}
pub fn with_seen(
auth_key: [u8; 256],
first_salt: i64,
time_offset: i32,
seen_msg_ids: SeenMsgIds,
) -> Self {
let mut rnd = [0u8; 8];
getrandom::getrandom(&mut rnd).expect("getrandom");
Self {
auth_key: AuthKey::from_bytes(auth_key),
session_id: i64::from_le_bytes(rnd),
sequence: 0,
last_msg_id: 0,
salt: first_salt,
time_offset,
seen_msg_ids,
}
}
pub fn seen_msg_ids(&self) -> SeenMsgIds {
std::sync::Arc::clone(&self.seen_msg_ids)
}
fn next_msg_id(&mut self) -> i64 {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let secs = now.as_secs().wrapping_add(self.time_offset as i64 as u64);
let nanos = now.subsec_nanos() as u64;
let mut id = ((secs << 32) | (nanos << 2)) as i64;
if self.last_msg_id >= id {
id = self.last_msg_id + 4;
}
self.last_msg_id = id;
id
}
fn next_seq_no(&mut self) -> i32 {
let n = self.sequence * 2 + 1;
self.sequence += 1;
n
}
pub fn next_seq_no_ncr(&self) -> i32 {
self.sequence * 2
}
pub fn correct_seq_no(&mut self, code: u32) {
match code {
32 => {
self.sequence += 64;
log::debug!(
"[ferogram] seq_no correction: code 32, bumped seq to {}",
self.sequence
);
}
33 => {
self.sequence = self.sequence.saturating_sub(16).max(1);
log::debug!(
"[ferogram] seq_no correction: code 33, lowered seq to {}",
self.sequence
);
}
_ => {}
}
}
pub fn undo_seq_no(&mut self) {
self.sequence = self.sequence.saturating_sub(1);
}
pub fn correct_time_offset(&mut self, server_msg_id: i64) {
let server_time = (server_msg_id >> 32) as i32;
let local_now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i32;
let new_offset = server_time.wrapping_sub(local_now);
log::debug!(
"[ferogram] time_offset correction: {} → {} (server_time={server_time})",
self.time_offset,
new_offset
);
self.time_offset = new_offset;
self.last_msg_id = (server_msg_id & !0x3i64).max(self.last_msg_id);
}
pub fn alloc_msg_seqno(&mut self, content_related: bool) -> (i64, i32) {
let msg_id = self.next_msg_id();
let seqno = if content_related {
self.next_seq_no()
} else {
self.next_seq_no_ncr()
};
(msg_id, seqno)
}
pub fn pack_body_with_msg_id(&mut self, body: &[u8], content_related: bool) -> (Vec<u8>, i64) {
let msg_id = self.next_msg_id();
let seq_no = if content_related {
self.next_seq_no()
} else {
self.next_seq_no_ncr()
};
let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
let mut buf = DequeBuffer::with_capacity(inner_len, 32);
buf.extend(self.salt.to_le_bytes());
buf.extend(self.session_id.to_le_bytes());
buf.extend(msg_id.to_le_bytes());
buf.extend(seq_no.to_le_bytes());
buf.extend((body.len() as u32).to_le_bytes());
buf.extend(body.iter().copied());
encrypt_data_v2(&mut buf, &self.auth_key);
(buf.as_ref().to_vec(), msg_id)
}
pub fn pack_container(&mut self, container_body: &[u8]) -> (Vec<u8>, i64) {
self.pack_body_with_msg_id(container_body, false)
}
pub fn pack_body_at_msg_id(&mut self, body: &[u8], msg_id: i64) -> Vec<u8> {
let seq_no = self.next_seq_no();
let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
let mut buf = DequeBuffer::with_capacity(inner_len, 32);
buf.extend(self.salt.to_le_bytes());
buf.extend(self.session_id.to_le_bytes());
buf.extend(msg_id.to_le_bytes());
buf.extend(seq_no.to_le_bytes());
buf.extend((body.len() as u32).to_le_bytes());
buf.extend(body.iter().copied());
encrypt_data_v2(&mut buf, &self.auth_key);
buf.as_ref().to_vec()
}
pub fn pack_serializable<S: ferogram_tl_types::Serializable>(&mut self, call: &S) -> Vec<u8> {
let body = call.to_bytes();
let msg_id = self.next_msg_id();
let seq_no = self.next_seq_no();
let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
let mut buf = DequeBuffer::with_capacity(inner_len, 32);
buf.extend(self.salt.to_le_bytes());
buf.extend(self.session_id.to_le_bytes());
buf.extend(msg_id.to_le_bytes());
buf.extend(seq_no.to_le_bytes());
buf.extend((body.len() as u32).to_le_bytes());
buf.extend(body.iter().copied());
encrypt_data_v2(&mut buf, &self.auth_key);
buf.as_ref().to_vec()
}
pub fn pack_serializable_with_msg_id<S: ferogram_tl_types::Serializable>(
&mut self,
call: &S,
) -> (Vec<u8>, i64) {
let body = call.to_bytes();
let msg_id = self.next_msg_id();
let seq_no = self.next_seq_no();
let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
let mut buf = DequeBuffer::with_capacity(inner_len, 32);
buf.extend(self.salt.to_le_bytes());
buf.extend(self.session_id.to_le_bytes());
buf.extend(msg_id.to_le_bytes());
buf.extend(seq_no.to_le_bytes());
buf.extend((body.len() as u32).to_le_bytes());
buf.extend(body.iter().copied());
encrypt_data_v2(&mut buf, &self.auth_key);
(buf.as_ref().to_vec(), msg_id)
}
pub fn pack_with_msg_id<R: RemoteCall>(&mut self, call: &R) -> (Vec<u8>, i64) {
let body = call.to_bytes();
let msg_id = self.next_msg_id();
let seq_no = self.next_seq_no();
let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
let mut buf = DequeBuffer::with_capacity(inner_len, 32);
buf.extend(self.salt.to_le_bytes());
buf.extend(self.session_id.to_le_bytes());
buf.extend(msg_id.to_le_bytes());
buf.extend(seq_no.to_le_bytes());
buf.extend((body.len() as u32).to_le_bytes());
buf.extend(body.iter().copied());
encrypt_data_v2(&mut buf, &self.auth_key);
(buf.as_ref().to_vec(), msg_id)
}
pub fn pack<R: RemoteCall>(&mut self, call: &R) -> Vec<u8> {
let body = call.to_bytes();
let msg_id = self.next_msg_id();
let seq_no = self.next_seq_no();
let inner_len = 8 + 8 + 8 + 4 + 4 + body.len();
let mut buf = DequeBuffer::with_capacity(inner_len, 32);
buf.extend(self.salt.to_le_bytes());
buf.extend(self.session_id.to_le_bytes());
buf.extend(msg_id.to_le_bytes());
buf.extend(seq_no.to_le_bytes());
buf.extend((body.len() as u32).to_le_bytes());
buf.extend(body.iter().copied());
encrypt_data_v2(&mut buf, &self.auth_key);
buf.as_ref().to_vec()
}
pub fn unpack(&self, frame: &mut [u8]) -> Result<DecryptedMessage, DecryptError> {
let plaintext = decrypt_data_v2(frame, &self.auth_key).map_err(DecryptError::Crypto)?;
if plaintext.len() < 32 {
return Err(DecryptError::FrameTooShort);
}
let salt = i64::from_le_bytes(plaintext[..8].try_into().unwrap());
let session_id = i64::from_le_bytes(plaintext[8..16].try_into().unwrap());
let msg_id = i64::from_le_bytes(plaintext[16..24].try_into().unwrap());
let seq_no = i32::from_le_bytes(plaintext[24..28].try_into().unwrap());
let body_len = u32::from_le_bytes(plaintext[28..32].try_into().unwrap()) as usize;
if session_id != self.session_id {
return Err(DecryptError::SessionMismatch);
}
if msg_id & 1 == 0 {
return Err(DecryptError::InvalidMsgId);
}
let server_secs = (msg_id as u64 >> 32) as i64;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let corrected = now + self.time_offset as i64;
let skew = server_secs - corrected;
if !(-300..=30).contains(&skew) {
return Err(DecryptError::MsgIdTimeWindow);
}
{
let mut seen = self.seen_msg_ids.lock().unwrap();
if seen.1.contains(&msg_id) {
return Err(DecryptError::DuplicateMsgId);
}
seen.0.push_back(msg_id);
seen.1.insert(msg_id);
if seen.0.len() > SEEN_MSG_IDS_MAX
&& let Some(old_id) = seen.0.pop_front()
{
seen.1.remove(&old_id);
}
}
if body_len > 16 * 1024 * 1024 {
return Err(DecryptError::FrameTooShort);
}
if 32 + body_len > plaintext.len() {
return Err(DecryptError::FrameTooShort);
}
if !body_len.is_multiple_of(4) {
return Err(DecryptError::FrameTooShort);
}
let padding = plaintext.len() - 32 - body_len;
if padding < 12 {
return Err(DecryptError::FrameTooShort);
}
let body = plaintext[32..32 + body_len].to_vec();
Ok(DecryptedMessage {
salt,
session_id,
msg_id,
seq_no,
body,
})
}
pub fn auth_key_bytes(&self) -> [u8; 256] {
self.auth_key.to_bytes()
}
pub fn session_id(&self) -> i64 {
self.session_id
}
pub fn reset_session(&mut self) {
let mut rnd = [0u8; 8];
getrandom::getrandom(&mut rnd).expect("getrandom");
let old_session = self.session_id;
self.session_id = i64::from_le_bytes(rnd);
self.sequence = 0;
self.last_msg_id = 0;
log::debug!(
"[ferogram] session reset: {:#018x} → {:#018x}",
old_session,
self.session_id
);
}
}
impl EncryptedSession {
pub fn decrypt_frame_dedup(
auth_key: &[u8; 256],
session_id: i64,
frame: &mut [u8],
seen: &SeenMsgIds,
) -> Result<DecryptedMessage, DecryptError> {
let msg = Self::decrypt_frame_with_offset(auth_key, session_id, frame, 0)?;
{
let mut s = seen.lock().unwrap();
if s.1.contains(&msg.msg_id) {
return Err(DecryptError::DuplicateMsgId);
}
s.0.push_back(msg.msg_id);
s.1.insert(msg.msg_id);
if s.0.len() > SEEN_MSG_IDS_MAX
&& let Some(old_id) = s.0.pop_front()
{
s.1.remove(&old_id);
}
}
Ok(msg)
}
pub fn decrypt_frame(
auth_key: &[u8; 256],
session_id: i64,
frame: &mut [u8],
) -> Result<DecryptedMessage, DecryptError> {
Self::decrypt_frame_with_offset(auth_key, session_id, frame, 0)
}
pub fn decrypt_frame_with_offset(
auth_key: &[u8; 256],
session_id: i64,
frame: &mut [u8],
time_offset: i32,
) -> Result<DecryptedMessage, DecryptError> {
let key = AuthKey::from_bytes(*auth_key);
let plaintext = decrypt_data_v2(frame, &key).map_err(DecryptError::Crypto)?;
if plaintext.len() < 32 {
return Err(DecryptError::FrameTooShort);
}
let salt = i64::from_le_bytes(plaintext[..8].try_into().unwrap());
let sid = i64::from_le_bytes(plaintext[8..16].try_into().unwrap());
let msg_id = i64::from_le_bytes(plaintext[16..24].try_into().unwrap());
let seq_no = i32::from_le_bytes(plaintext[24..28].try_into().unwrap());
let body_len = u32::from_le_bytes(plaintext[28..32].try_into().unwrap()) as usize;
if sid != session_id {
return Err(DecryptError::SessionMismatch);
}
if msg_id & 1 == 0 {
return Err(DecryptError::InvalidMsgId);
}
let server_secs = (msg_id as u64 >> 32) as i64;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let corrected = now + time_offset as i64;
let skew = server_secs - corrected;
if !(-300..=30).contains(&skew) {
return Err(DecryptError::MsgIdTimeWindow);
}
if body_len > 16 * 1024 * 1024 {
return Err(DecryptError::FrameTooShort);
}
if 32 + body_len > plaintext.len() {
return Err(DecryptError::FrameTooShort);
}
if !body_len.is_multiple_of(4) {
return Err(DecryptError::FrameTooShort);
}
let padding = plaintext.len() - 32 - body_len;
if padding < 12 {
return Err(DecryptError::FrameTooShort);
}
let body = plaintext[32..32 + body_len].to_vec();
Ok(DecryptedMessage {
salt,
session_id: sid,
msg_id,
seq_no,
body,
})
}
}