Skip to Content
ReferenceRustopentele2-core

opentele2-core

The pure core of opentele2: every Telegram Desktop tdata format, session format, device catalog, API profile, fingerprint rule and the MTProto client crypto, shared by the Python and Node bindings.

docs.rs  · crates.io  · MIT

Cargo.toml
[dependencies] opentele2-core = "2.0.0" # opentele2-core = { version = "2.0.0", features = ["telethon-sqlite"] }
  • Pure: it transforms the bytes and values it is given and walks no filesystem beyond the paths passed in.
  • No async: every function is synchronous; there is no runtime such as tokio.
  • No network: it opens no sockets and speaks no MTProto transport.
  • No FFI: it exports a plain Rust API and forbids unsafe code.

Features

FeatureDefaultEffect
parallelyesRuns the batch entry points on a dedicated rayon pool; without it they run sequentially with identical results.
telethon-sqlitenoWrites and reads Telethon .session files (session::telethon_file::write_session_file, read_session_file) with a bundled SQLite. Off by default so the default core links no C code.

Modules

ModuleHolds
tdataTData, a whole folder; account, key_data, map and mtp_config for its files; DrawSource and draw_plan for its random draws
sessiontelethon::StringSession, mtcute::StringSession, grammers::SessionData, telethon_file and the built-in DC table dc
apiClient, Versions, ApiProfile, profile, generate, generate_desktop, LAYER, session_json
devicesPlatform, Device, the weighted catalogs, pick and seed_hash
fingerprinttz_offset, validate_init_params, check_versions, FingerprintConfig, StrictMode, Issue, msg id helpers
authQR login URL, srp_check, post-login steps, session rows and tables, ConsistencyExpectations and the consistency report, FlowFailure
cryptoAuthKey, AES-256-IGE and -CTR, the tdata local encryption, SHA-1/256, HMAC-SHA256, PBKDF2, PQ factorisation, random_fill
kdfcreate_local_key, create_legacy_local_key, argon2id, scrypt and their caps
batchload_many and map_many over independent folders
compressgzip, gunzip
qt, tdfThe Qt stream encoding and the TDF$ container

tdata::TData

src/tdata/mod.rs
pub const APP_VERSION: u32 = 7_003_001; pub const NEW_FOLDER_ACTIVE_INDEX: i32 = -1; pub struct TData { pub app_version: u32, pub key_file: String, pub key_data: KeyData, pub local_key: AuthKey, pub passcode_key: AuthKey, pub accounts: Vec<Account>, pub active_index: i32, pub performance_mode: bool, // ... } impl TData { pub const MAX_ACCOUNTS: usize = 6; pub fn load(dir: impl AsRef<Path>, passcode: &[u8], key_file: Option<&str>) -> Result<Self>; pub fn new(accounts: Vec<Account>, passcode: &[u8], performance_mode: bool, rng: &mut dyn FnMut(&mut [u8])) -> Result<Self>; pub fn new_with_draws(accounts: Vec<Account>, passcode: &[u8], performance_mode: bool, active_index: i32, draws: DrawSource) -> Result<Self>; pub fn save(&self, dir: impl AsRef<Path>, rng: &mut dyn FnMut(&mut [u8])) -> Result<()>; pub fn save_random(&self, dir: impl AsRef<Path>) -> Result<()>; pub fn save_with_draws(&self, dir: impl AsRef<Path>, performance_mode: Option<bool>, draws: DrawSource) -> Result<()>; pub fn migrate(&mut self, rng: &mut dyn FnMut(&mut [u8])) -> Result<bool>; pub fn migrate_with_draws(&mut self, draws: DrawSource) -> Result<bool>; pub fn set_accounts(&mut self, accounts: Vec<Account>) -> Result<()>; pub fn add_account(&mut self, user_id: u64, main_dc: i32, auth_key: AuthKey) -> Result<i32>; pub fn set_key_file(&mut self, name: &str) -> Result<()>; pub fn main_account(&self) -> Option<&Account>; }
ItemDescription
TData::loadLoads a folder; an empty passcode for none, None for the default key file data.
TData::new, new_with_drawsA folder for accounts the way SaveTData creates one, drawing from rng or a DrawSource.
save, save_random, save_with_drawsWrite the folder; save_random draws its padding from the OS random source.
migrate, migrate_with_drawsInstall 7.3 key data in place of a pre-7.3 key file; true when they did.
set_accountsReplaces the accounts later saves write, keeping the local key.
add_accountAdds an account at the lowest free index; Error::MaxAccountLimit for a full folder.
set_key_fileRenames the key file and account files of later saves.
main_accountThe account at the active index, else the first.
DrawSource::os(), DrawSource::replay(draws)The OS random source, or recorded draws replayed in order; draw_plan lists the sizes an operation draws.

Debug of TData and Account prints key ids only, never key material or the passcode.

tdata::account::Account

src/tdata/account.rs
pub struct Account { pub index: i32, pub user_id: u64, pub main_dc: i32, pub keys: Vec<(i32, AuthKey)>, pub keys_to_destroy: Vec<(i32, AuthKey)>, pub map: MapData, pub config: Option<Vec<u8>>, } impl Account { pub fn new(index: i32, user_id: u64, main_dc: i32, keys: Vec<(i32, AuthKey)>) -> Result<Self>; pub fn auth_key(&self) -> Option<&AuthKey>; pub fn mtp_config(&self) -> MtpConfig; }

Account::new builds an account as it is before saving; Error::AuthKeyNotFound when no key belongs to main_dc. auth_key is the key of the main DC. mtp_config parses the stored config, or gives the production default.

Sessions

src/session/codecs.rs
pub mod telethon { 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>; pub fn encode(&self) -> String; pub fn decode(string: &str) -> Result<Self>; } } pub mod mtcute { 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>; pub fn encode(&self) -> String; pub fn decode(string: &str) -> Result<Self>; } } pub mod grammers { pub struct SessionData { pub home_dc: i32, pub dc_options: HashMap<i32, DcOption>, /* ... */ } impl SessionData { pub fn from_account(account: &Account) -> Result<Self>; } }
src/session/telethon_file.rs
pub const VERSION: i64 = 8; pub struct SessionRows { pub dc_id: i32, pub server_address: String, pub port: u16, pub auth_key: AuthKey, pub takeout_id: Option<i64>, } impl SessionRows { pub fn from_account(account: &Account, ipv6: bool) -> Result<Self>; } pub fn session_path(path: impl AsRef<Path>) -> PathBuf; pub fn pair_paths(base: impl AsRef<Path>) -> (PathBuf, PathBuf); pub fn companion_json(profile: &ApiProfile, extra: Option<&Map<String, Value>>) -> Value; pub fn companion_text(profile: &ApiProfile, extra: Option<&Map<String, Value>>) -> String; pub fn read_companion(text: &str) -> Result<(ApiProfile, Map<String, Value>)>; // feature "telethon-sqlite" pub fn write_session_file(path: impl AsRef<Path>, rows: &SessionRows) -> Result<PathBuf>; pub fn read_session_file(path: impl AsRef<Path>) -> Result<SessionRows>;

Every from_account takes the endpoints of the account’s stored MTP config when it has config bytes, and the built-in production table of session::dc otherwise (port 443, DCs 1 to 5). The types hold their key as an AuthKey, whose Debug prints the key id only. The grammers struct here has no grammers dependency; opentele2-grammers gives grammers’ own SessionData.

api

src/api.rs
pub const LAYER: i32 = 230; pub enum Client { Desktop, Android, AndroidX, Ios, MacOS, WebA, WebK, Webogram } pub struct Versions { /* the PlatformVersions fields */ } impl Versions { pub fn offline() -> Self; pub fn set_chrome_version(&mut self, version: impl Into<String>); } pub struct ApiProfile { pub api_id: i32, pub api_hash: String, pub device_model: String, pub system_version: String, pub app_version: String, pub lang_code: String, pub system_lang_code: String, pub lang_pack: String, } impl ApiProfile { pub fn from_session_json(data: &Value) -> Result<Self>; pub fn to_session_json(&self, extra: Option<&Map<String, Value>>) -> Value; } pub fn profile(client: Client, versions: &Versions) -> ApiProfile; pub fn generate(client: Client, seed: Option<&str>, versions: &Versions) -> Result<ApiProfile>; pub fn generate_desktop(system: Option<&str>, seed: Option<&str>, versions: &Versions) -> Result<ApiProfile>; pub fn format_app_version(version: u32) -> String;
ItemDescription
ClientThe eight official clients; Client::from_name("TelegramDesktop") and name() map the binding names.
Versions::offline()The built-in versions, used when fetching is off.
profile(client, versions)The client’s default profile with the app versions of versions.
generate(client, seed, versions)The default profile with a device picked for seed; Client::Desktop is generate_desktop without a system.
generate_desktop(system, seed, versions)The desktop profile for "windows", "macos" or "linux" (chosen from the seed when absent); the app version gets x64 on Windows and arm64 on macOS.
ApiProfileEquality and hashing use api_id, api_hash, device_model, system_version and app_version only.
session_jsonThe session JSON field rules (FIELDS, DEFAULT_EXTRA_FIELDS, read, write) untyped, for bindings.

devices and fingerprint

src/devices.rs
pub enum Platform { Windows, Linux, MacOS { swift: bool }, Android, Ios, Web { client: WebClient } } pub struct Device { pub model: String, pub system_version: String } pub fn pick(platform: Platform, seed: Option<&str>) -> Result<Device>; pub fn catalog(platform: Platform) -> Result<&'static Catalog>; pub fn catalog_entries(platform: Platform) -> Result<Vec<CatalogEntry>>; pub fn seed_hash(seed: &str) -> u64;
src/fingerprint.rs
pub fn tz_offset(utc_offset_seconds: i32) -> f64; pub fn validate_init_params(profile: &ApiProfile, versions: &Versions, mode: StrictMode) -> Vec<Issue>; pub fn check_versions(profile: &ApiProfile, versions: &Versions) -> Vec<Issue>; pub fn issues_message(issues: &[Issue]) -> String; pub fn layer_mismatch_warning(library_layer: i32) -> Option<String>; pub fn msg_id_offset(rng: &mut dyn FnMut(&mut [u8])) -> u16; pub fn is_valid_msg_id(msg_id: i64, from_client: bool, now_unix: i64) -> bool; pub enum StrictMode { Off, Warn, Strict } pub struct FingerprintConfig { /* strict_mode, auto_validate, preferred_transport, ... */ } impl FingerprintConfig { pub fn from_os_rng() -> Result<Self>; pub fn effective_layer(&self) -> i32; pub fn effective_layer_for(&self, library_layer: i32) -> (i32, Option<String>); pub fn validate(&self, profile: &ApiProfile, versions: &Versions) -> Vec<Issue>; }

pick indexes the expanded weighted catalog with a hash of the seed, so a seed always gives the same device; without a seed the pick is random. tz_offset takes the local UTC offset from the caller, so results do not depend on the machine’s clock or zone.

auth

src/auth.rs
pub fn qr_login_url(token: &[u8]) -> String; pub fn srp_inputs(srp_id: Option<i64>, srp_b: Option<&[u8]>) -> (i64, Vec<u8>); pub fn srp_check(params: &SrpParams<'_>, password: &str, rng: &mut dyn FnMut(&mut [u8])) -> Result<SrpCheck, SrpError>; pub fn srp_check_random(params: &SrpParams<'_>, password: &str) -> Result<SrpCheck, SrpError>; pub fn post_login_steps(lang_pack: &str) -> Vec<PostLoginStep>; pub fn format_sessions(rows: &[SessionRow]) -> String; pub fn is_official_api_id(api_id: i32) -> bool; pub fn consistency_report(outcomes: [CheckOutcome; 7]) -> ConsistencyReport; pub fn consistency_summary(results: &[CheckResult]) -> String; pub const OFFICIAL_API_IDS: [i32; 6] = [2040, 6, 21724, 10840, 2834, 2496]; pub const QR_LOGIN_ATTEMPTS: u32 = 5; pub const LAYER_TOLERANCE: u32 = 15;

The client-independent parts of login, session admin and the consistency checks; the flows run on each ecosystem’s own client. ConsistencyExpectations::new(profile, versions) says what each of the seven checks (Check::ALL) expects, and FlowFailure gives each flow failure its code and Python exception class.

crypto, kdf and batch

src/crypto.rs
pub struct AuthKey(/* 256 bytes */); impl AuthKey { pub const SIZE: usize = 256; pub const fn new(bytes: [u8; Self::SIZE]) -> Self; pub fn from_slice(bytes: &[u8]) -> Result<Self>; pub const fn as_bytes(&self) -> &[u8; Self::SIZE]; pub fn key_id(&self) -> u64; } pub fn aes_ige_encrypt(buf: &mut [u8], key: &[u8], iv: &[u8]) -> Result<()>; pub fn aes_ige_decrypt(buf: &mut [u8], key: &[u8], iv: &[u8]) -> Result<()>; pub fn decrypt_local(encrypted: &[u8], local_key: &AuthKey) -> Result<Vec<u8>>; pub fn encrypt_local_random(data: &[u8], local_key: &AuthKey) -> Result<Vec<u8>>; pub fn sha1(data: &[u8]) -> [u8; 20]; pub fn sha256(data: &[u8]) -> [u8; 32]; pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32]; pub fn factorize_pq(pq: &[u8]) -> Result<(Vec<u8>, Vec<u8>)>; pub fn random_fill(buf: &mut [u8]) -> Result<()>; pub struct AesCtr(/* ... */); impl AesCtr { pub fn new(key: &[u8], iv: &[u8]) -> Result<Self>; pub fn apply(&mut self, buf: &mut [u8]); }
src/kdf.rs
pub fn create_local_key(passcode: &[u8], salt: &[u8]) -> AuthKey; pub fn create_legacy_local_key(passcode: &[u8], salt: &[u8]) -> AuthKey; pub fn scrypt(passcode: &[u8], salt: &[u8], n: u32, r: u32, p: u32) -> Result<[u8; AuthKey::SIZE]>;
src/batch.rs
pub struct LoadRequest { /* dir, passcode, key_file */ } impl LoadRequest { pub fn new(dir: impl Into<PathBuf>, passcode: impl Into<Vec<u8>>, key_file: Option<String>) -> Self; } pub struct BatchOptions { pub threads: Option<NonZeroUsize>, /* ... */ } pub fn load_many(requests: &[LoadRequest], options: &BatchOptions) -> Vec<Result<TData>>;

load_many returns results in input order; with parallel each call builds its own rayon pool.

Errors

src/error.rs
pub type Result<T> = std::result::Result<T, Error>; pub enum Error { FileNotFound { file: String }, InvalidMagic { context: MagicContext }, InvalidCheckSum { file: String, computed: [u8; 16] }, BadDecryptKey, WrongPasscode, BadEncryptedDataSize { size: usize }, BadDecryptedDataSize { encrypted_size: usize, full_len: usize, decrypted_size: usize }, BadConfigData { context: ConfigFailure }, QDataStreamFailed { context: Option<&'static str> }, ReadMapDataFailed { context: MapReadFailure }, ReadMapDataIncorrectPasscode, AuthKeyNotFound, MaxAccountLimit, SaveFailed, KeyDataInvalid { context: KeyDataFailure }, BadLength { what: &'static str, expected: usize, actual: usize }, InvalidInput { what: &'static str, reason: String }, Io(std::io::Error), } impl Error { pub fn code(&self) -> &'static str; pub fn detail_code(&self) -> Option<&'static str>; pub fn legacy_message(&self) -> String; pub fn legacy_class(&self) -> &'static str; }

code() is the stable code the bindings map (TDATA_WRONG_PASSCODE, BAD_LENGTH, …): Node prefixes it with OT_, Python raises the class legacy_class() names. The codes are listed on the Node errors page.

src/main.rs
use opentele2_core::api::{self, Client, Versions}; use opentele2_core::session::telethon; use opentele2_core::tdata::TData; fn main() -> opentele2_core::Result<()> { let tdata = TData::load("tdata", b"", None)?; let account = tdata.main_account().expect("folder has an account"); let session = telethon::StringSession::from_account(account, false)?; println!("dc {} at {}", session.dc_id, session.addr); let device = api::generate(Client::Desktop, Some("account-42"), &Versions::offline())?; println!("{} {}", device.device_model, device.app_version); Ok(()) }
Last updated on