use std::net::{IpAddr, SocketAddr};
use base64::Engine;
use base64::engine::general_purpose::{
URL_SAFE, URL_SAFE_NO_PAD, URL_SAFE_NO_PAD_INDIFFERENT, URL_SAFE_PAD_INDIFFERENT,
};
use crate::crypto::AuthKey;
use crate::session::dc;
use crate::tdata::account::Account;
use crate::tdata::mtp_config::{Endpoint, Environment, MtpConfig};
use crate::{ConfigFailure, Error, Result};
fn stored_config(account: &Account) -> Option<MtpConfig> {
account.config.as_ref().map(|_| account.mtp_config())
}
fn endpoint_addr(endpoint: &Endpoint) -> Result<SocketAddr> {
let ip: IpAddr = endpoint.ip.parse().map_err(|_| Error::BadConfigData {
context: ConfigFailure::BadEndpointIp,
})?;
let port = u16::try_from(endpoint.port).map_err(|_| Error::BadConfigData {
context: ConfigFailure::PortOutOfRange {
port: i64::from(endpoint.port),
},
})?;
Ok(SocketAddr::new(ip, port))
}
fn account_endpoint(account: &Account, ipv6: bool) -> Result<(SocketAddr, bool, &AuthKey)> {
let (addr, _, test, key) = account_target(account, ipv6)?;
Ok((addr, test, key))
}
pub(super) fn account_target(
account: &Account,
ipv6: bool,
) -> Result<(SocketAddr, String, bool, &AuthKey)> {
let not_found = Error::BadConfigData {
context: ConfigFailure::DcNotFound {
dc_id: account.main_dc,
},
};
let (addr, ip_text, test) = if let Some(config) = stored_config(account) {
let endpoints = config.endpoints(account.main_dc).ok_or(not_found)?;
let endpoint = endpoints.select(ipv6).ok_or(Error::BadConfigData {
context: ConfigFailure::NoTcpEndpoint,
})?;
(
endpoint_addr(endpoint)?,
endpoint.ip.clone(),
config.environment == Environment::Test,
)
} else {
let addr = dc::production(account.main_dc, ipv6).ok_or(not_found)?;
(addr, table_text(addr.ip()), false)
};
let key = account.auth_key().ok_or(Error::AuthKeyNotFound)?;
Ok((addr, ip_text, test, key))
}
fn table_text(ip: IpAddr) -> String {
match ip {
IpAddr::V4(ip) => ip.to_string(),
IpAddr::V6(ip) => ip
.segments()
.iter()
.map(|group| format!("{group:04x}"))
.collect::<Vec<_>>()
.join(":"),
}
}
fn dc_byte(dc_id: i32) -> Result<u8> {
u8::try_from(dc_id).map_err(|_| Error::BadConfigData {
context: ConfigFailure::DcIdOutOfRange {
dc_id: i64::from(dc_id),
},
})
}
struct Cursor<'a> {
rest: &'a [u8],
}
impl<'a> Cursor<'a> {
const fn new(data: &'a [u8]) -> Self {
Self { rest: data }
}
fn take(&mut self, len: usize) -> Result<&'a [u8]> {
if self.rest.len() < len {
return Err(Error::QDataStreamFailed { context: None });
}
let (head, tail) = self.rest.split_at(len);
self.rest = tail;
Ok(head)
}
fn array<const N: usize>(&mut self) -> Result<[u8; N]> {
let mut out = [0; N];
out.copy_from_slice(self.take(N)?);
Ok(out)
}
fn u8(&mut self) -> Result<u8> {
Ok(self.array::<1>()?[0])
}
fn finish(&self) -> Result<()> {
if self.rest.is_empty() {
Ok(())
} else {
Err(Error::QDataStreamFailed { context: None })
}
}
}
pub mod telethon {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use super::{Cursor, Engine, URL_SAFE, URL_SAFE_PAD_INDIFFERENT, account_endpoint, dc_byte};
use crate::crypto::AuthKey;
use crate::tdata::account::Account;
use crate::{Error, Result};
pub const VERSION: char = '1';
pub const IPV4_LEN: usize = 1 + 4 + 2 + AuthKey::SIZE;
pub const IPV6_LEN: usize = 1 + 16 + 2 + AuthKey::SIZE;
#[derive(Debug, Clone)]
pub struct StringSession {
pub dc_id: u8,
pub addr: SocketAddr,
pub auth_key: AuthKey,
}
impl StringSession {
pub fn from_account(account: &Account, ipv6: bool) -> Result<Self> {
let (addr, _, key) = account_endpoint(account, ipv6)?;
Ok(Self {
dc_id: dc_byte(account.main_dc)?,
addr,
auth_key: key.clone(),
})
}
#[must_use]
pub fn encode(&self) -> String {
let mut body = Vec::with_capacity(IPV6_LEN);
body.push(self.dc_id);
match self.addr.ip() {
IpAddr::V4(ip) => body.extend_from_slice(&ip.octets()),
IpAddr::V6(ip) => body.extend_from_slice(&ip.octets()),
}
body.extend_from_slice(&self.addr.port().to_be_bytes());
body.extend_from_slice(self.auth_key.as_bytes());
let mut out = String::from(VERSION);
out.push_str(&URL_SAFE.encode(body));
out
}
pub fn decode(string: &str) -> Result<Self> {
let body = string
.strip_prefix(VERSION)
.ok_or(Error::QDataStreamFailed { context: None })?;
let bytes = URL_SAFE_PAD_INDIFFERENT
.decode(body)
.map_err(|_| Error::QDataStreamFailed { context: None })?;
let ipv6 = match bytes.len() {
IPV4_LEN => false,
IPV6_LEN => true,
actual => {
return Err(Error::BadLength {
what: "Telethon string session",
expected: if actual > IPV4_LEN {
IPV6_LEN
} else {
IPV4_LEN
},
actual,
});
}
};
let mut cursor = Cursor::new(&bytes);
let dc_id = cursor.u8()?;
let ip = if ipv6 {
IpAddr::V6(Ipv6Addr::from(cursor.array::<16>()?))
} else {
IpAddr::V4(Ipv4Addr::from(cursor.array::<4>()?))
};
let port = u16::from_be_bytes(cursor.array()?);
let auth_key = AuthKey::new(cursor.array()?);
cursor.finish()?;
Ok(Self {
dc_id,
addr: SocketAddr::new(ip, port),
auth_key,
})
}
}
}
pub mod mtcute {
use std::net::SocketAddr;
use super::{
Cursor, Engine, URL_SAFE_NO_PAD, URL_SAFE_NO_PAD_INDIFFERENT, account_endpoint, dc_byte,
};
use crate::crypto::AuthKey;
use crate::tdata::account::Account;
use crate::{Error, Result};
pub const VERSION: u8 = 3;
pub const FLAG_SELF: i32 = 1;
pub const FLAG_TEST_MODE_OLD: i32 = 2;
pub const FLAG_MEDIA_DC: i32 = 4;
pub const DC_OPTION_VERSION: u8 = 2;
pub const DC_IPV6: u8 = 1;
pub const DC_MEDIA_ONLY: u8 = 2;
pub const DC_TEST_MODE: u8 = 4;
pub const BOOL_TRUE: u32 = 0x9972_75B5;
pub const BOOL_FALSE: u32 = 0xBC79_9737;
pub const MAX_USER_ID: u64 = (1 << 53) - 1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DcOption {
pub id: u8,
pub ip: String,
pub port: u16,
pub ipv6: bool,
pub media_only: bool,
}
impl DcOption {
#[must_use]
pub fn new(id: u8, addr: SocketAddr) -> Self {
Self {
id,
ip: addr.ip().to_string(),
port: addr.port(),
ipv6: addr.is_ipv6(),
media_only: false,
}
}
fn serialize(&self, test_mode: bool) -> Vec<u8> {
let mut flags = 0;
for (set, bit) in [
(self.ipv6, DC_IPV6),
(self.media_only, DC_MEDIA_ONLY),
(test_mode, DC_TEST_MODE),
] {
if set {
flags |= bit;
}
}
let mut out = vec![DC_OPTION_VERSION, self.id, flags];
write_bytes(&mut out, self.ip.as_bytes());
out.extend_from_slice(&i32::from(self.port).to_le_bytes());
out
}
fn parse(blob: &[u8]) -> Result<(Self, bool)> {
let mut cursor = Cursor::new(blob);
let [version, id, flags] = cursor.array()?;
if !matches!(version, 1 | DC_OPTION_VERSION)
|| flags & !(DC_IPV6 | DC_MEDIA_ONLY | DC_TEST_MODE) != 0
{
return Err(Error::QDataStreamFailed { context: None });
}
let ip = String::from_utf8(read_bytes(&mut cursor)?.to_vec())
.map_err(|_| Error::QDataStreamFailed { context: None })?;
let port = u16::try_from(i32::from_le_bytes(cursor.array()?))
.map_err(|_| Error::QDataStreamFailed { context: None })?;
cursor.finish()?;
let option = Self {
id,
ip,
port,
ipv6: flags & DC_IPV6 != 0,
media_only: flags & DC_MEDIA_ONLY != 0,
};
Ok((
option,
version == DC_OPTION_VERSION && flags & DC_TEST_MODE != 0,
))
}
}
#[derive(Debug, Clone)]
pub struct StringSession {
pub primary_dc: DcOption,
pub media_dc: Option<DcOption>,
pub self_user: Option<(u64, bool)>,
pub test_mode: bool,
pub auth_key: AuthKey,
}
impl StringSession {
pub fn from_account(account: &Account, ipv6: bool) -> Result<Self> {
let (addr, test_mode, key) = account_endpoint(account, ipv6)?;
Ok(Self {
primary_dc: DcOption::new(dc_byte(account.main_dc)?, addr),
media_dc: None,
self_user: Some((account.user_id, false)),
test_mode,
auth_key: key.clone(),
})
}
#[must_use]
pub fn encode(&self) -> String {
let mut flags = 0;
if self.self_user.is_some() {
flags |= FLAG_SELF;
}
if self.media_dc.is_some() {
flags |= FLAG_MEDIA_DC;
}
let mut out = vec![VERSION];
out.extend_from_slice(&flags.to_le_bytes());
write_bytes(&mut out, &self.primary_dc.serialize(self.test_mode));
if let Some(media) = &self.media_dc {
write_bytes(&mut out, &media.serialize(self.test_mode));
}
if let Some((user_id, is_bot)) = self.self_user {
out.extend_from_slice(&user_id.to_le_bytes());
let flag = if is_bot { BOOL_TRUE } else { BOOL_FALSE };
out.extend_from_slice(&flag.to_le_bytes());
}
write_bytes(&mut out, self.auth_key.as_bytes());
URL_SAFE_NO_PAD.encode(out)
}
pub fn decode(string: &str) -> Result<Self> {
let bytes = URL_SAFE_NO_PAD_INDIFFERENT
.decode(string)
.map_err(|_| Error::QDataStreamFailed { context: None })?;
let mut cursor = Cursor::new(&bytes);
if cursor.u8()? != VERSION {
return Err(Error::QDataStreamFailed { context: None });
}
let flags = i32::from_le_bytes(cursor.array()?);
if flags & !(FLAG_SELF | FLAG_TEST_MODE_OLD | FLAG_MEDIA_DC) != 0 {
return Err(Error::QDataStreamFailed { context: None });
}
let (primary_dc, primary_test) = DcOption::parse(read_bytes(&mut cursor)?)?;
let media = if flags & FLAG_MEDIA_DC == 0 {
None
} else {
Some(DcOption::parse(read_bytes(&mut cursor)?)?)
};
let test_mode = if flags & FLAG_TEST_MODE_OLD != 0 {
true
} else {
let media_test = media.as_ref().map_or(primary_test, |(_, test)| *test);
if media_test != primary_test {
return Err(Error::QDataStreamFailed { context: None });
}
primary_test
};
let self_user = if flags & FLAG_SELF == 0 {
None
} else {
let user_id = u64::from_le_bytes(cursor.array()?);
if user_id > MAX_USER_ID {
return Err(Error::QDataStreamFailed { context: None });
}
let is_bot = match u32::from_le_bytes(cursor.array()?) {
BOOL_TRUE => true,
BOOL_FALSE => false,
_ => return Err(Error::QDataStreamFailed { context: None }),
};
Some((user_id, is_bot))
};
let auth_key = AuthKey::from_slice(read_bytes(&mut cursor)?)?;
cursor.finish()?;
Ok(Self {
primary_dc,
media_dc: media.map(|(option, _)| option),
self_user,
test_mode,
auth_key,
})
}
}
const fn padding(len: usize) -> usize {
let used = if len <= 253 { len + 1 } else { len };
(4 - used % 4) % 4
}
fn write_bytes(out: &mut Vec<u8>, data: &[u8]) {
match u8::try_from(data.len()) {
Ok(len) if len <= 253 => out.push(len),
_ => {
out.push(254);
let len = u32::try_from(data.len()).unwrap_or(u32::MAX).to_le_bytes();
out.extend_from_slice(&len[..3]);
}
}
out.extend_from_slice(data);
out.resize(out.len() + padding(data.len()), 0);
}
fn read_bytes<'a>(cursor: &mut Cursor<'a>) -> Result<&'a [u8]> {
let len = match cursor.u8()? {
254 => {
let [a, b, c] = cursor.array()?;
let len = usize::from(a) | usize::from(b) << 8 | usize::from(c) << 16;
if len <= 253 {
return Err(Error::QDataStreamFailed { context: None });
}
len
}
255 => return Err(Error::QDataStreamFailed { context: None }),
len => usize::from(len),
};
let data = cursor.take(len)?;
if cursor.take(padding(len))?.iter().any(|&b| b != 0) {
return Err(Error::QDataStreamFailed { context: None });
}
Ok(data)
}
}
pub mod grammers {
use std::collections::HashMap;
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
use super::{endpoint_addr, stored_config};
use crate::crypto::AuthKey;
use crate::session::dc;
use crate::tdata::account::Account;
use crate::tdata::mtp_config::MtpConfig;
use crate::{ConfigFailure, Error, Result};
fn built_in_options() -> Result<HashMap<i32, (SocketAddrV4, SocketAddrV6)>> {
dc::PRODUCTION_DC_IDS
.iter()
.map(
|&id| match (dc::production(id, false), dc::production(id, true)) {
(Some(SocketAddr::V4(ipv4)), Some(SocketAddr::V6(ipv6))) => {
Ok((id, (ipv4, ipv6)))
}
_ => Err(Error::BadConfigData {
context: ConfigFailure::DcNotFound { dc_id: id },
}),
},
)
.collect()
}
fn config_options(config: &MtpConfig) -> HashMap<i32, (SocketAddrV4, SocketAddrV6)> {
config
.dc_options
.dcs()
.filter_map(|(id, _)| {
let endpoints = config.endpoints(id)?;
let ipv4 = endpoint_addr(endpoints.select(false)?).ok()?;
let ipv6 = endpoint_addr(endpoints.select(true)?).ok()?;
match (ipv4, ipv6) {
(SocketAddr::V4(ipv4), SocketAddr::V6(ipv6)) => Some((id, (ipv4, ipv6))),
_ => None,
}
})
.collect()
}
#[derive(Debug, Clone)]
pub struct DcOption {
pub id: i32,
pub ipv4: SocketAddrV4,
pub ipv6: SocketAddrV6,
pub auth_key: Option<AuthKey>,
}
#[derive(Debug, Clone)]
pub struct SessionData {
pub home_dc: i32,
pub dc_options: HashMap<i32, DcOption>,
}
impl SessionData {
pub fn from_account(account: &Account) -> Result<Self> {
let options = match stored_config(account) {
Some(config) => config_options(&config),
None => built_in_options()?,
};
if !options.contains_key(&account.main_dc) {
return Err(Error::BadConfigData {
context: ConfigFailure::DcNotFound {
dc_id: account.main_dc,
},
});
}
let key = account.auth_key().ok_or(Error::AuthKeyNotFound)?;
let dc_options = options
.into_iter()
.map(|(id, (ipv4, ipv6))| {
let option = DcOption {
id,
ipv4,
ipv6,
auth_key: (id == account.main_dc).then(|| key.clone()),
};
(id, option)
})
.collect();
Ok(Self {
home_dc: account.main_dc,
dc_options,
})
}
}
}