Skip to main content

layer_client/
lib.rs

1//! High-level Telegram client — design mirrors grammers exactly.
2//!
3//! # Full flow
4//!
5//! ```rust,no_run
6//! use layer_client::{Client, SignInError};
7//!
8//! // Reuse session if it exists, otherwise fresh DH + initConnection(GetConfig)
9//! let mut client = Client::load_or_connect("session.bin", API_ID, API_HASH)?;
10//!
11//! if !client.is_authorized()? {
12//!     let token = client.request_login_code(PHONE)?;
13//!     let code  = prompt("Enter code: ");
14//!
15//!     match client.sign_in(&token, &code) {
16//!         Ok(name)                                  => println!("Welcome, {name}!"),
17//!         Err(SignInError::PasswordRequired(token)) => {
18//!             let pw = prompt("Enter 2FA password: ");
19//!             client.check_password(token, pw.trim())?;
20//!         }
21//!         Err(SignInError::InvalidCode)             => eprintln!("Wrong code"),
22//!         Err(SignInError::SignUpRequired)           => eprintln!("Sign up via official app first"),
23//!         Err(SignInError::Other(e))                => return Err(e.into()),
24//!     }
25//!     client.save_session("session.bin")?;
26//! }
27//!
28//! client.send_message("me", "Hello from layer!")?;
29//! ```
30
31use std::collections::HashMap;
32use std::fs;
33use std::io::{self, Read, Write};
34use std::net::TcpStream;
35use std::path::Path;
36use std::time::Duration;
37
38use layer_mtproto::transport::{AbridgedTransport, Transport};
39use layer_mtproto::{EncryptedSession, Session, authentication as auth};
40use layer_tl_types::{Cursor, Deserializable, RemoteCall};
41
42pub use error::Error;
43pub use sign_in_error::{SignInError, PasswordToken};
44pub use login::LoginToken;
45
46// ─── DC bootstrap addresses ───────────────────────────────────────────────────
47
48const DC_ADDRESSES: &[(i32, &str)] = &[
49    (1, "149.154.175.53:443"),
50    (2, "149.154.167.51:443"),
51    (3, "149.154.175.100:443"),
52    (4, "149.154.167.91:443"),
53    (5, "91.108.56.130:443"),
54];
55
56// ─── MTProto constructor IDs ──────────────────────────────────────────────────
57
58const ID_RPC_RESULT:      u32 = 0xf35c6d01;
59const ID_RPC_ERROR:       u32 = 0x2144ca19;
60const ID_MSG_CONTAINER:   u32 = 0x73f1f8dc;
61const ID_GZIP_PACKED:     u32 = 0x3072cfa1;
62const ID_PONG:            u32 = 0x347773c5;
63const ID_MSGS_ACK:        u32 = 0x62d6b459;
64const ID_BAD_SERVER_SALT: u32 = 0xedab447b;
65const ID_NEW_SESSION:     u32 = 0x9ec20908;
66const ID_BAD_MSG_NOTIFY:  u32 = 0xa7eff811;
67
68// ─── Error ────────────────────────────────────────────────────────────────────
69
70mod error {
71    use std::io;
72
73    #[derive(Debug)]
74    pub enum Error {
75        Io(io::Error),
76        Auth(layer_mtproto::authentication::Error),
77        Decrypt(layer_mtproto::encrypted::DecryptError),
78        Tl(layer_tl_types::deserialize::Error),
79        /// Telegram returned an RPC error (e.g. PHONE_CODE_INVALID, code 420 FLOOD_WAIT_X).
80        Rpc { code: i32, message: String },
81        Proto(&'static str),
82    }
83
84    impl std::fmt::Display for Error {
85        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86            match self {
87                Self::Io(e)                 => write!(f, "IO: {e}"),
88                Self::Auth(e)               => write!(f, "DH: {e}"),
89                Self::Decrypt(e)            => write!(f, "Decrypt: {e}"),
90                Self::Tl(e)                 => write!(f, "TL: {e}"),
91                Self::Rpc { code, message } => write!(f, "RPC {code}: {message}"),
92                Self::Proto(s)              => write!(f, "Protocol: {s}"),
93            }
94        }
95    }
96    impl std::error::Error for Error {}
97
98    impl From<io::Error>                              for Error { fn from(e: io::Error) -> Self { Self::Io(e) } }
99    impl From<layer_mtproto::authentication::Error>   for Error { fn from(e: layer_mtproto::authentication::Error) -> Self { Self::Auth(e) } }
100    impl From<layer_mtproto::encrypted::DecryptError> for Error { fn from(e: layer_mtproto::encrypted::DecryptError) -> Self { Self::Decrypt(e) } }
101    impl From<layer_tl_types::deserialize::Error>     for Error { fn from(e: layer_tl_types::deserialize::Error) -> Self { Self::Tl(e) } }
102}
103
104// ─── SignInError — mirrors grammers exactly ───────────────────────────────────
105
106mod sign_in_error {
107    use super::Error;
108
109    /// Holds the server's 2FA challenge. Pass to [`super::Client::check_password`].
110    pub struct PasswordToken {
111        pub(crate) password: layer_tl_types::types::account::Password,
112    }
113
114    impl PasswordToken {
115        /// The password hint set by the user, if any.
116        pub fn hint(&self) -> Option<&str> {
117            self.password.hint.as_deref()
118        }
119    }
120
121    /// Errors that can occur during [`super::Client::sign_in`].
122    ///
123    /// Mirrors `grammers_client::SignInError`.
124    #[derive(Debug)]
125    pub enum SignInError {
126        /// New number — must sign up via official app first.
127        SignUpRequired,
128        /// 2FA is enabled; pass the token to [`super::Client::check_password`].
129        PasswordRequired(PasswordToken),
130        /// The code was wrong or expired.
131        InvalidCode,
132        /// Generic error.
133        Other(Error),
134    }
135
136    impl std::fmt::Display for SignInError {
137        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138            match self {
139                Self::SignUpRequired          => write!(f, "sign up required — use official app"),
140                Self::PasswordRequired(_)     => write!(f, "2FA password required"),
141                Self::InvalidCode             => write!(f, "invalid or expired code"),
142                Self::Other(e)               => write!(f, "{e}"),
143            }
144        }
145    }
146    impl std::error::Error for SignInError {}
147    impl From<Error> for SignInError { fn from(e: Error) -> Self { Self::Other(e) } }
148
149    impl std::fmt::Debug for PasswordToken {
150        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151            write!(f, "PasswordToken {{ hint: {:?} }}", self.hint())
152        }
153    }
154}
155
156// ─── LoginToken ───────────────────────────────────────────────────────────────
157
158mod login {
159    /// Opaque token from [`super::Client::request_login_code`].
160    /// Pass to [`super::Client::sign_in`].
161    pub struct LoginToken {
162        pub(crate) phone:           String,
163        pub(crate) phone_code_hash: String,
164    }
165}
166
167// ─── 2FA (SRP) — ported from grammers-crypto/two_factor_auth.rs ──────────────
168
169mod two_factor_auth {
170    use hmac::Hmac;
171    use num_bigint::{BigInt, Sign};
172    use num_traits::ops::euclid::Euclid;
173    use sha2::{Digest, Sha256, Sha512};
174
175    fn sha256(parts: &[&[u8]]) -> [u8; 32] {
176        let mut h = Sha256::new();
177        for p in parts { h.update(p); }
178        h.finalize().into()
179    }
180
181    fn sh(data: &[u8], salt: &[u8]) -> [u8; 32] {
182        sha256(&[salt, data, salt])
183    }
184
185    fn ph1(password: &[u8], salt1: &[u8], salt2: &[u8]) -> [u8; 32] {
186        sh(&sh(password, salt1), salt2)
187    }
188
189    fn ph2(password: &[u8], salt1: &[u8], salt2: &[u8]) -> [u8; 32] {
190        let hash1 = ph1(password, salt1, salt2);
191        let mut dk = [0u8; 64];
192        pbkdf2::pbkdf2::<Hmac<Sha512>>(&hash1, salt1, 100_000, &mut dk).unwrap();
193        sh(&dk, salt2)
194    }
195
196    fn pad256(data: &[u8]) -> [u8; 256] {
197        let mut out = [0u8; 256];
198        let start = 256usize.saturating_sub(data.len());
199        out[start..].copy_from_slice(&data[data.len().saturating_sub(256)..]);
200        out
201    }
202
203    fn xor32(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
204        let mut out = [0u8; 32];
205        for i in 0..32 { out[i] = a[i] ^ b[i]; }
206        out
207    }
208
209    /// Compute SRP `(M1, g_a)` for Telegram 2FA.
210    /// Ported exactly from grammers `calculate_2fa`.
211    pub fn calculate_2fa(
212        salt1:    &[u8],
213        salt2:    &[u8],
214        p:        &[u8],
215        g:        i32,
216        g_b:      &[u8],
217        a:        &[u8],
218        password: impl AsRef<[u8]>,
219    ) -> ([u8; 32], [u8; 256]) {
220        let big_p  = BigInt::from_bytes_be(Sign::Plus, p);
221        let g_b    = pad256(g_b);
222        let a      = pad256(a);
223        let g_hash = pad256(&[g as u8]);
224
225        let big_g_b = BigInt::from_bytes_be(Sign::Plus, &g_b);
226        let big_g   = BigInt::from(g as u32);
227        let big_a   = BigInt::from_bytes_be(Sign::Plus, &a);
228
229        // k = H(p | pad(g))
230        let k    = sha256(&[p, &g_hash]);
231        let big_k = BigInt::from_bytes_be(Sign::Plus, &k);
232
233        // g_a = g^a mod p
234        let g_a = big_g.modpow(&big_a, &big_p);
235        let g_a = pad256(&g_a.to_bytes_be().1);
236
237        // u = H(g_a | g_b)
238        let u     = sha256(&[&g_a, &g_b]);
239        let big_u = BigInt::from_bytes_be(Sign::Plus, &u);
240
241        // x = PH2(password, salt1, salt2)
242        let x     = ph2(password.as_ref(), salt1, salt2);
243        let big_x = BigInt::from_bytes_be(Sign::Plus, &x);
244
245        // v = g^x mod p,  k_v = k*v mod p
246        let big_v  = big_g.modpow(&big_x, &big_p);
247        let big_kv = (big_k * big_v) % &big_p;
248
249        // t = (g_b - k_v) mod p  (positive)
250        let big_t  = (big_g_b - big_kv).rem_euclid(&big_p);
251
252        // s_a = t^(a + u*x) mod p
253        let exp    = big_a + big_u * big_x;
254        let big_sa = big_t.modpow(&exp, &big_p);
255
256        // k_a = H(s_a)
257        let k_a = sha256(&[&pad256(&big_sa.to_bytes_be().1)]);
258
259        // M1 = H(H(p)^H(g) | H(salt1) | H(salt2) | g_a | g_b | k_a)
260        let h_p   = sha256(&[p]);
261        let h_g   = sha256(&[&g_hash]);
262        let p_xg  = xor32(&h_p, &h_g);
263        let m1    = sha256(&[&p_xg, &sha256(&[salt1]), &sha256(&[salt2]), &g_a, &g_b, &k_a]);
264
265        (m1, g_a)
266    }
267}
268
269// ─── DC option ────────────────────────────────────────────────────────────────
270
271#[derive(Clone)]
272struct DcOption {
273    addr:     String,
274    auth_key: Option<[u8; 256]>,
275}
276
277// ─── Session persistence ──────────────────────────────────────────────────────
278
279struct PersistedSession { home_dc_id: i32, dcs: Vec<PersistedDc> }
280struct PersistedDc { dc_id: i32, auth_key: Option<[u8;256]>, first_salt: i64, time_offset: i32, addr: String }
281
282impl PersistedSession {
283    fn save(&self, path: &Path) -> io::Result<()> {
284        let mut b = Vec::new();
285        b.extend_from_slice(&self.home_dc_id.to_le_bytes());
286        b.push(self.dcs.len() as u8);
287        for d in &self.dcs {
288            b.extend_from_slice(&d.dc_id.to_le_bytes());
289            if let Some(k) = &d.auth_key { b.push(1); b.extend_from_slice(k); } else { b.push(0); }
290            b.extend_from_slice(&d.first_salt.to_le_bytes());
291            b.extend_from_slice(&d.time_offset.to_le_bytes());
292            let ab = d.addr.as_bytes(); b.push(ab.len() as u8); b.extend_from_slice(ab);
293        }
294        fs::write(path, b)
295    }
296
297    fn load(path: &Path) -> io::Result<Self> {
298        let buf = fs::read(path)?;
299        let mut p = 0usize;
300        macro_rules! r { ($n:expr) => {{ if p+$n > buf.len() { return Err(io::Error::new(io::ErrorKind::InvalidData,"truncated")); } let s=&buf[p..p+$n]; p+=$n; s }}; }
301        let home_dc_id = i32::from_le_bytes(r!(4).try_into().unwrap());
302        let dc_count   = r!(1)[0] as usize;
303        let mut dcs    = Vec::with_capacity(dc_count);
304        for _ in 0..dc_count {
305            let dc_id      = i32::from_le_bytes(r!(4).try_into().unwrap());
306            let has_key    = r!(1)[0];
307            let auth_key   = if has_key==1 { let mut k=[0u8;256]; k.copy_from_slice(r!(256)); Some(k) } else { None };
308            let first_salt  = i64::from_le_bytes(r!(8).try_into().unwrap());
309            let time_offset = i32::from_le_bytes(r!(4).try_into().unwrap());
310            let al = r!(1)[0] as usize;
311            let addr = String::from_utf8_lossy(r!(al)).into_owned();
312            dcs.push(PersistedDc { dc_id, auth_key, first_salt, time_offset, addr });
313        }
314        Ok(Self { home_dc_id, dcs })
315    }
316}
317
318// ─── TCP transport ────────────────────────────────────────────────────────────
319
320struct Tcp(TcpStream);
321impl Tcp {
322    fn connect(addr: &str) -> io::Result<Self> {
323        let s = TcpStream::connect(addr)?;
324        s.set_read_timeout(Some(Duration::from_secs(90)))?;
325        s.set_write_timeout(Some(Duration::from_secs(10)))?;
326        Ok(Self(s))
327    }
328}
329impl Transport for Tcp {
330    type Error = io::Error;
331    fn send(&mut self, data: &[u8]) -> io::Result<()> { self.0.write_all(data) }
332    fn recv(&mut self) -> io::Result<Vec<u8>> {
333        let mut f=[0u8;1]; self.0.read_exact(&mut f)?;
334        let words = if f[0]<0x7f { f[0] as usize }
335            else { let mut b=[0u8;3]; self.0.read_exact(&mut b)?; b[0] as usize|(b[1] as usize)<<8|(b[2] as usize)<<16 };
336        let mut buf=vec![0u8;words*4]; self.0.read_exact(&mut buf)?; Ok(buf)
337    }
338}
339
340// ─── Connection (single DC) ───────────────────────────────────────────────────
341
342struct Connection { transport: AbridgedTransport<Tcp>, enc: EncryptedSession }
343
344impl Connection {
345    /// Fresh connection — runs DH key exchange.
346    fn connect_raw(addr: &str) -> Result<Self, Error> {
347        let tcp = Tcp::connect(addr)?;
348        let mut tr = AbridgedTransport::new(tcp);
349        let mut plain = Session::new();
350
351        let (req1, s1) = auth::step1()?;
352        tr.send_message(&plain.pack(&req1).to_plaintext_bytes())?;
353        let res_pq: layer_tl_types::enums::ResPq = recv_plain(&mut tr)?;
354
355        let (req2, s2) = auth::step2(s1, res_pq)?;
356        tr.send_message(&plain.pack(&req2).to_plaintext_bytes())?;
357        let dh: layer_tl_types::enums::ServerDhParams = recv_plain(&mut tr)?;
358
359        let (req3, s3) = auth::step3(s2, dh)?;
360        tr.send_message(&plain.pack(&req3).to_plaintext_bytes())?;
361        let ans: layer_tl_types::enums::SetClientDhParamsAnswer = recv_plain(&mut tr)?;
362
363        let done = auth::finish(s3, ans)?;
364        Ok(Self { transport: tr, enc: EncryptedSession::new(done.auth_key, done.first_salt, done.time_offset) })
365    }
366
367    /// Reuse saved auth key — no DH needed.
368    /// Mirrors grammers `connect_with_auth`.
369    fn connect_with_key(addr: &str, auth_key: [u8;256], first_salt: i64, time_offset: i32) -> Result<Self, Error> {
370        let tcp = Tcp::connect(addr)?;
371        Ok(Self { transport: AbridgedTransport::new(tcp), enc: EncryptedSession::new(auth_key, first_salt, time_offset) })
372    }
373
374    fn auth_key_bytes(&self) -> [u8;256] { self.enc.auth_key_bytes() }
375    fn first_salt(&self)     -> i64       { self.enc.salt }
376    fn time_offset(&self)    -> i32       { self.enc.time_offset }
377
378    fn rpc_call<R: RemoteCall>(&mut self, req: &R) -> Result<Vec<u8>, Error> {
379        let wire = self.enc.pack(req);
380        self.transport.send_message(&wire)?;
381        self.recv_rpc()
382    }
383
384    fn recv_rpc(&mut self) -> Result<Vec<u8>, Error> {
385        loop {
386            let mut raw = match self.transport.recv_message() {
387                Ok(r) => r,
388                Err(e) if e.kind()==io::ErrorKind::WouldBlock || e.kind()==io::ErrorKind::TimedOut => continue,
389                Err(e) if e.kind()==io::ErrorKind::UnexpectedEof
390                       || e.kind()==io::ErrorKind::ConnectionReset
391                       || e.kind()==io::ErrorKind::ConnectionAborted => {
392                    return Err(Error::Proto("server closed the connection"));
393                }
394                Err(e) => return Err(e.into()),
395            };
396            let msg = self.enc.unpack(&mut raw)?;
397            if msg.salt != 0 { self.enc.salt = msg.salt; }
398            match unwrap_envelope(msg.body)? {
399                Some(p) => return Ok(p),
400                None    => continue,
401            }
402        }
403    }
404}
405
406// ─── Client ───────────────────────────────────────────────────────────────────
407
408pub struct Client {
409    conn:       Connection,
410    home_dc_id: i32,
411    dc_options: HashMap<i32, DcOption>,
412    api_id:     i32,
413    api_hash:   String,
414}
415
416impl Client {
417    // ── Constructors ─────────────────────────────────────────────────────────
418
419    /// Connect fresh: DH + `invokeWithLayer(initConnection(GetConfig))`.
420    ///
421    /// `initConnection` wraps `GetConfig` exactly like grammers' `SenderPoolRunner::connect_sender`.
422    /// The Config response populates our DC address table for future migrations.
423    pub fn connect(dc_addr: &str, api_id: i32, api_hash: &str) -> Result<Self, Error> {
424        eprintln!("[layer] Connecting to {dc_addr} …");
425        let conn = Connection::connect_raw(dc_addr)?;
426        eprintln!("[layer] DH complete ✓");
427        let mut client = Self {
428            conn, home_dc_id: 2,
429            dc_options: bootstrap_dc_options(),
430            api_id, api_hash: api_hash.to_string(),
431        };
432        client.init_and_get_config()?;
433        Ok(client)
434    }
435
436    /// Load saved session or connect fresh.
437    ///
438    /// On a saved session, reuses the auth key — mirrors grammers `connect_with_auth`.
439    /// On no session file, defaults to DC2 (same as Telegram's recommended bootstrap DC).
440    pub fn load_or_connect(session_path: impl AsRef<Path>, api_id: i32, api_hash: &str) -> Result<Self, Error> {
441        let path = session_path.as_ref();
442        if path.exists() {
443            match PersistedSession::load(path) {
444                Ok(s) => {
445                    if let Some(dc) = s.dcs.iter().find(|d| d.dc_id == s.home_dc_id) {
446                        if let Some(key) = dc.auth_key {
447                            eprintln!("[layer] Loading session (DC{}) …", s.home_dc_id);
448                            let conn = Connection::connect_with_key(&dc.addr, key, dc.first_salt, dc.time_offset)?;
449                            let mut dc_options = bootstrap_dc_options();
450                            for d in &s.dcs {
451                                dc_options.insert(d.dc_id, DcOption { addr: d.addr.clone(), auth_key: d.auth_key });
452                            }
453                            let mut client = Self { conn, home_dc_id: s.home_dc_id, dc_options, api_id, api_hash: api_hash.to_string() };
454                            client.init_and_get_config()?;
455                            eprintln!("[layer] Session restored ✓");
456                            return Ok(client);
457                        }
458                    }
459                    eprintln!("[layer] Session incomplete — connecting fresh …");
460                }
461                Err(e) => eprintln!("[layer] Session load failed ({e}) — connecting fresh …"),
462            }
463        }
464        Self::connect("149.154.167.51:443", api_id, api_hash)
465    }
466
467    // ── Session ───────────────────────────────────────────────────────────────
468
469    /// Persist auth key + DC table. Call after successful sign-in.
470    pub fn save_session(&self, path: impl AsRef<Path>) -> Result<(), Error> {
471        let dcs = self.dc_options.iter().map(|(&dc_id, opt)| PersistedDc {
472            dc_id,
473            auth_key:    opt.auth_key,
474            first_salt:  if dc_id==self.home_dc_id { self.conn.first_salt()  } else { 0 },
475            time_offset: if dc_id==self.home_dc_id { self.conn.time_offset() } else { 0 },
476            addr:        opt.addr.clone(),
477        }).collect();
478        PersistedSession { home_dc_id: self.home_dc_id, dcs }.save(path.as_ref())?;
479        eprintln!("[layer] Session saved ✓");
480        Ok(())
481    }
482
483    // ── Auth ──────────────────────────────────────────────────────────────────
484
485    /// Returns `true` if already logged in.
486    /// Probes with `updates.getState` — same as grammers.
487    pub fn is_authorized(&mut self) -> Result<bool, Error> {
488        match self.conn.rpc_call(&layer_tl_types::functions::updates::GetState {}) {
489            Ok(_)                             => Ok(true),
490            Err(Error::Rpc { code: 401, .. }) => Ok(false),
491            Err(e)                            => Err(e),
492        }
493    }
494
495    /// Send login code. Handles `PHONE_MIGRATE_X` like grammers:
496    /// disconnect, reconnect to correct DC, retry.
497    pub fn request_login_code(&mut self, phone: &str) -> Result<LoginToken, Error> {
498        use layer_tl_types::enums::auth::SentCode;
499        let req  = self.make_send_code_req(phone);
500        let body = match self.conn.rpc_call(&req) {
501            Ok(b) => b,
502            Err(Error::Rpc { code: 303, message }) => {
503                let dc = parse_migrate_dc(&message)
504                    .ok_or_else(|| Error::Rpc { code: 303, message: message.clone() })?;
505                eprintln!("[layer] PHONE_MIGRATE_{dc} — reconnecting …");
506                self.migrate_to(dc)?;
507                self.conn.rpc_call(&req)?
508            }
509            Err(e) => return Err(e),
510        };
511        let mut cur = Cursor::from_slice(&body);
512        let (hash, kind) = match layer_tl_types::enums::auth::SentCode::deserialize(&mut cur)? {
513            SentCode::SentCode(c)        => (c.phone_code_hash, sent_code_type_name(&c.r#type)),
514            SentCode::Success(_)         => return Err(Error::Proto("unexpected SentCode::Success")),
515            SentCode::PaymentRequired(_) => return Err(Error::Proto("payment required")),
516        };
517        eprintln!("[layer] Code sent via {kind}");
518        Ok(LoginToken { phone: phone.to_string(), phone_code_hash: hash })
519    }
520
521    /// Complete sign-in with the received code.
522    ///
523    /// Returns the display name on success.
524    /// Returns `Err(SignInError::PasswordRequired(token))` if 2FA is enabled —
525    /// pass the token to [`check_password`].
526    ///
527    /// Handles `USER_MIGRATE_X` exactly like grammers.
528    pub fn sign_in(&mut self, token: &LoginToken, code: &str) -> Result<String, SignInError> {
529        let req = layer_tl_types::functions::auth::SignIn {
530            phone_number:       token.phone.clone(),
531            phone_code_hash:    token.phone_code_hash.clone(),
532            phone_code:         Some(code.trim().to_string()),
533            email_verification: None,
534        };
535
536        let body = match self.conn.rpc_call(&req) {
537            Ok(b) => b,
538            // DC migration
539            Err(Error::Rpc { code: 303, message }) => {
540                let dc = parse_migrate_dc(&message)
541                    .ok_or_else(|| Error::Rpc { code: 303, message: message.clone() })?;
542                eprintln!("[layer] USER_MIGRATE_{dc} — reconnecting …");
543                self.migrate_to(dc).map_err(SignInError::Other)?;
544                self.conn.rpc_call(&req).map_err(SignInError::Other)?
545            }
546            // 2FA required — fetch password info and return PasswordRequired
547            Err(Error::Rpc { message, .. }) if message.contains("SESSION_PASSWORD_NEEDED") => {
548                let pw_token = self.get_password_info().map_err(SignInError::Other)?;
549                return Err(SignInError::PasswordRequired(pw_token));
550            }
551            // Wrong/expired code
552            Err(Error::Rpc { message, .. }) if message.starts_with("PHONE_CODE") => {
553                return Err(SignInError::InvalidCode);
554            }
555            Err(e) => return Err(SignInError::Other(e)),
556        };
557
558        let mut cur = Cursor::from_slice(&body);
559        match layer_tl_types::enums::auth::Authorization::deserialize(&mut cur).map_err(|e| SignInError::Other(e.into()))? {
560            layer_tl_types::enums::auth::Authorization::Authorization(a) => {
561                let name = extract_user_name(&a.user);
562                eprintln!("[layer] Signed in ✓  Welcome, {name}!");
563                Ok(name)
564            }
565            layer_tl_types::enums::auth::Authorization::SignUpRequired(_) =>
566                Err(SignInError::SignUpRequired),
567        }
568    }
569
570    /// Complete 2FA login with the user's password.
571    ///
572    /// `password_token` comes from `Err(SignInError::PasswordRequired(token))`.
573    /// Mirrors grammers `check_password`.
574    pub fn check_password(&mut self, password_token: PasswordToken, password: impl AsRef<[u8]>) -> Result<String, Error> {
575        let pw   = password_token.password;
576        let algo = pw.current_algo.ok_or(Error::Proto("no current_algo in Password"))?;
577
578        let (salt1, salt2, p, g) = extract_password_params(&algo)?;
579
580        let g_b        = pw.srp_b.ok_or(Error::Proto("no srp_b in Password"))?;
581        let a          = pw.secure_random; // secure_random is always present (not optional)
582        let srp_id     = pw.srp_id.ok_or(Error::Proto("no srp_id in Password"))?;
583
584        let (m1, g_a) = two_factor_auth::calculate_2fa(salt1, salt2, p, g, &g_b, &a, password.as_ref());
585
586        let req = layer_tl_types::functions::auth::CheckPassword {
587            password: layer_tl_types::enums::InputCheckPasswordSrp::InputCheckPasswordSrp(
588                layer_tl_types::types::InputCheckPasswordSrp {
589                    srp_id,
590                    a: g_a.to_vec(),
591                    m1: m1.to_vec(),
592                },
593            ),
594        };
595
596        let body    = self.conn.rpc_call(&req)?;
597        let mut cur = Cursor::from_slice(&body);
598        match layer_tl_types::enums::auth::Authorization::deserialize(&mut cur)? {
599            layer_tl_types::enums::auth::Authorization::Authorization(a) => {
600                let name = extract_user_name(&a.user);
601                eprintln!("[layer] 2FA ✓  Welcome, {name}!");
602                Ok(name)
603            }
604            layer_tl_types::enums::auth::Authorization::SignUpRequired(_) =>
605                Err(Error::Proto("unexpected SignUpRequired after 2FA")),
606        }
607    }
608
609    // ── Messaging ─────────────────────────────────────────────────────────────
610
611    /// Send a text message to `peer`. Use `"me"` for Saved Messages.
612    pub fn send_message(&mut self, peer: &str, text: &str) -> Result<(), Error> {
613        let input_peer = match peer {
614            "me" | "self" => layer_tl_types::enums::InputPeer::PeerSelf,
615            _ => return Err(Error::Proto("only \"me\" supported — resolve peer first")),
616        };
617        let req = layer_tl_types::functions::messages::SendMessage {
618            no_webpage: false, silent: false, background: false, clear_draft: false,
619            noforwards: false, update_stickersets_order: false, invert_media: false,
620            allow_paid_floodskip: false, peer: input_peer, reply_to: None,
621            message: text.to_string(), random_id: random_i64(),
622            reply_markup: None, entities: None, schedule_date: None,
623            schedule_repeat_period: None, send_as: None, quick_reply_shortcut: None,
624            effect: None, allow_paid_stars: None, suggested_post: None,
625        };
626        eprintln!("[layer] Sending message to {peer} …");
627        self.conn.rpc_call(&req)?;
628        eprintln!("[layer] Message sent ✓");
629        Ok(())
630    }
631
632    // ── Raw invoke ────────────────────────────────────────────────────────────
633
634    /// Invoke any TL function and return the deserialized response.
635    pub fn invoke<R: RemoteCall>(&mut self, req: &R) -> Result<R::Return, Error> {
636        let body = self.conn.rpc_call(req)?;
637        let mut cur = Cursor::from_slice(&body);
638        Ok(R::Return::deserialize(&mut cur)?)
639    }
640
641    // ── Private ───────────────────────────────────────────────────────────────
642
643    /// `invokeWithLayer(initConnection(GetConfig {}))` — exactly like grammers.
644    ///
645    /// Wraps `GetConfig` so we receive the full DC list.
646    /// Manually packs to avoid the `Deserializable` bound issue on generic wrappers.
647    fn init_and_get_config(&mut self) -> Result<(), Error> {
648        use layer_tl_types::functions::{InvokeWithLayer, InitConnection, help::GetConfig};
649        let req = InvokeWithLayer {
650            layer: layer_tl_types::LAYER,
651            query: InitConnection {
652                api_id: self.api_id,
653                device_model:     "Linux".to_string(),
654                system_version:   "1.0".to_string(),
655                app_version:      "0.1.0".to_string(),
656                system_lang_code: "en".to_string(),
657                lang_pack:        "".to_string(),
658                lang_code:        "en".to_string(),
659                proxy:            None,
660                params:           None,
661                query:            GetConfig {},
662            },
663        };
664        let wire = self.conn.enc.pack_serializable(&req);
665        self.conn.transport.send_message(&wire)?;
666        let body = self.conn.recv_rpc()?;
667        let mut cur = Cursor::from_slice(&body);
668        if let Ok(layer_tl_types::enums::Config::Config(cfg)) =
669            layer_tl_types::enums::Config::deserialize(&mut cur)
670        {
671            self.update_dc_options(&cfg.dc_options);
672            eprintln!("[layer] initConnection ✓  ({} DCs known)", self.dc_options.len());
673        } else {
674            eprintln!("[layer] initConnection ✓");
675        }
676        Ok(())
677    }
678
679    /// Parse DC options from Config and update table.
680    /// Same filter as grammers `SenderPoolRunner::update_config`:
681    /// skip media-only, CDN, tcpo-only, and IPv6.
682    fn update_dc_options(&mut self, options: &[layer_tl_types::enums::DcOption]) {
683        for opt in options {
684            let layer_tl_types::enums::DcOption::DcOption(o) = opt;
685            if o.media_only || o.cdn || o.tcpo_only || o.ipv6 { continue; }
686            let addr = format!("{}:{}", o.ip_address, o.port);
687            self.dc_options.entry(o.id)
688                .or_insert_with(|| DcOption { addr: addr.clone(), auth_key: None })
689                .addr = addr.clone();
690        }
691    }
692
693    /// Disconnect from current DC and connect to `new_dc_id`.
694    /// Mirrors grammers `disconnect_from_dc` + `set_home_dc_id` + `connect_sender`.
695    fn migrate_to(&mut self, new_dc_id: i32) -> Result<(), Error> {
696        let addr = self.dc_options.get(&new_dc_id)
697            .map(|o| o.addr.clone())
698            .or_else(|| DC_ADDRESSES.iter().find(|(id,_)| *id==new_dc_id).map(|(_,a)| a.to_string()))
699            .unwrap_or_else(|| "149.154.167.51:443".to_string());
700
701        eprintln!("[layer] Migrating to DC{new_dc_id} ({addr}) …");
702
703        let saved_key = self.dc_options.get(&new_dc_id).and_then(|o| o.auth_key);
704        let conn = if let Some(key) = saved_key {
705            Connection::connect_with_key(&addr, key, 0, 0)?
706        } else {
707            Connection::connect_raw(&addr)?
708        };
709
710        // Save auth key for this DC
711        let new_key = conn.auth_key_bytes();
712        self.dc_options.entry(new_dc_id)
713            .or_insert_with(|| DcOption { addr: addr.clone(), auth_key: None })
714            .auth_key = Some(new_key);
715
716        self.conn       = conn;
717        self.home_dc_id = new_dc_id;
718        self.init_and_get_config()?;
719        eprintln!("[layer] Now on DC{new_dc_id} ✓");
720        Ok(())
721    }
722
723    /// Fetch `account.getPassword` to get SRP challenge.
724    /// Mirrors grammers `get_password_information`.
725    fn get_password_info(&mut self) -> Result<PasswordToken, Error> {
726        let body    = self.conn.rpc_call(&layer_tl_types::functions::account::GetPassword {})?;
727        let mut cur = Cursor::from_slice(&body);
728        let pw: layer_tl_types::types::account::Password =
729            match layer_tl_types::enums::account::Password::deserialize(&mut cur)? {
730                layer_tl_types::enums::account::Password::Password(p) => p,
731            };
732        Ok(PasswordToken { password: pw })
733    }
734
735    fn make_send_code_req(&self, phone: &str) -> layer_tl_types::functions::auth::SendCode {
736        layer_tl_types::functions::auth::SendCode {
737            phone_number: phone.to_string(),
738            api_id:       self.api_id,
739            api_hash:     self.api_hash.clone(),
740            settings:     layer_tl_types::enums::CodeSettings::CodeSettings(
741                layer_tl_types::types::CodeSettings {
742                    allow_flashcall: false, current_number: false, allow_app_hash: false,
743                    allow_missed_call: false, allow_firebase: false, unknown_number: false,
744                    logout_tokens: None, token: None, app_sandbox: None,
745                },
746            ),
747        }
748    }
749}
750
751// ─── MTProto envelope unwrapper ───────────────────────────────────────────────
752
753fn unwrap_envelope(body: Vec<u8>) -> Result<Option<Vec<u8>>, Error> {
754    if body.len() < 4 { return Err(Error::Proto("body < 4 bytes")); }
755    let cid = u32::from_le_bytes(body[..4].try_into().unwrap());
756    match cid {
757        ID_RPC_RESULT => {
758            if body.len() < 12 { return Err(Error::Proto("rpc_result too short")); }
759            unwrap_envelope(body[12..].to_vec())
760        }
761        ID_RPC_ERROR => {
762            if body.len() < 8 { return Err(Error::Proto("rpc_error too short")); }
763            let code    = i32::from_le_bytes(body[4..8].try_into().unwrap());
764            let message = tl_read_string(&body[8..])?;
765            Err(Error::Rpc { code, message })
766        }
767        ID_MSG_CONTAINER => {
768            if body.len() < 8 { return Err(Error::Proto("container too short")); }
769            let count   = u32::from_le_bytes(body[4..8].try_into().unwrap()) as usize;
770            let mut pos = 8usize;
771            let mut found: Option<Vec<u8>> = None;
772            for _ in 0..count {
773                if pos+16 > body.len() { break; }
774                let inner_len = u32::from_le_bytes(body[pos+12..pos+16].try_into().unwrap()) as usize;
775                pos += 16;
776                if pos+inner_len > body.len() { break; }
777                let inner = body[pos..pos+inner_len].to_vec();
778                pos += inner_len;
779                if let Some(p) = unwrap_envelope(inner)? { found = Some(p); }
780            }
781            Ok(found)
782        }
783        ID_GZIP_PACKED => {
784            let bytes = tl_read_bytes(&body[4..])?;
785            unwrap_envelope(gz_inflate(&bytes)?)
786        }
787        ID_PONG | ID_MSGS_ACK | ID_NEW_SESSION | ID_BAD_SERVER_SALT | ID_BAD_MSG_NOTIFY => Ok(None),
788        _ => Ok(Some(body)),
789    }
790}
791
792// ─── Plaintext helper (DH only) ───────────────────────────────────────────────
793
794fn recv_plain<T: Deserializable>(tr: &mut AbridgedTransport<Tcp>) -> Result<T, Error> {
795    let raw = tr.recv_message()?;
796    if raw.len() < 20 { return Err(Error::Proto("plaintext frame too short")); }
797    if u64::from_le_bytes(raw[..8].try_into().unwrap()) != 0 {
798        return Err(Error::Proto("expected auth_key_id=0 in plaintext frame"));
799    }
800    let body_len = u32::from_le_bytes(raw[16..20].try_into().unwrap()) as usize;
801    let mut cur  = Cursor::from_slice(&raw[20..20+body_len]);
802    Ok(T::deserialize(&mut cur)?)
803}
804
805// ─── Helpers ──────────────────────────────────────────────────────────────────
806
807fn bootstrap_dc_options() -> HashMap<i32, DcOption> {
808    DC_ADDRESSES.iter().map(|(id,addr)| (*id, DcOption { addr: addr.to_string(), auth_key: None })).collect()
809}
810
811/// Parse "PHONE_MIGRATE_5" → Some(5). Mirrors grammers `err.value`.
812fn parse_migrate_dc(msg: &str) -> Option<i32> {
813    msg.rsplit('_').next()?.parse().ok()
814}
815
816fn extract_password_params(algo: &layer_tl_types::enums::PasswordKdfAlgo)
817    -> Result<(&[u8], &[u8], &[u8], i32), Error>
818{
819    match algo {
820        layer_tl_types::enums::PasswordKdfAlgo::Sha256Sha256Pbkdf2Hmacsha512iter100000Sha256ModPow(a) => {
821            Ok((&a.salt1, &a.salt2, &a.p, a.g))
822        }
823        _ => Err(Error::Proto("unsupported password KDF algorithm")),
824    }
825}
826
827fn random_i64() -> i64 {
828    let mut b = [0u8;8]; getrandom::getrandom(&mut b).expect("getrandom"); i64::from_le_bytes(b)
829}
830
831fn tl_read_bytes(data: &[u8]) -> Result<Vec<u8>, Error> {
832    if data.is_empty() { return Ok(vec![]); }
833    let (len, start) = if data[0]<254 { (data[0] as usize, 1) }
834    else if data.len()>=4 { (data[1] as usize|(data[2] as usize)<<8|(data[3] as usize)<<16, 4) }
835    else { return Err(Error::Proto("TL bytes header truncated")); };
836    if data.len()<start+len { return Err(Error::Proto("TL bytes body truncated")); }
837    Ok(data[start..start+len].to_vec())
838}
839
840fn tl_read_string(data: &[u8]) -> Result<String, Error> {
841    tl_read_bytes(data).map(|b| String::from_utf8_lossy(&b).into_owned())
842}
843
844fn gz_inflate(data: &[u8]) -> Result<Vec<u8>, Error> {
845    use std::io::Read;
846    let mut out = Vec::new();
847    if flate2::read::GzDecoder::new(data).read_to_end(&mut out).is_ok() && !out.is_empty() { return Ok(out); }
848    out.clear();
849    flate2::read::ZlibDecoder::new(data).read_to_end(&mut out).map_err(|_| Error::Proto("gzip failed"))?;
850    Ok(out)
851}
852
853fn extract_user_name(user: &layer_tl_types::enums::User) -> String {
854    match user {
855        layer_tl_types::enums::User::User(u) =>
856            format!("{} {}", u.first_name.as_deref().unwrap_or(""), u.last_name.as_deref().unwrap_or("")).trim().to_string(),
857        layer_tl_types::enums::User::Empty(_) => "(unknown)".into(),
858    }
859}
860
861fn sent_code_type_name(t: &layer_tl_types::enums::auth::SentCodeType) -> &'static str {
862    use layer_tl_types::enums::auth::SentCodeType::*;
863    match t {
864        App(_) => "Telegram app", Sms(_) => "SMS", Call(_) => "phone call",
865        FlashCall(_) => "flash call", MissedCall(_) => "missed call",
866        FragmentSms(_) => "Fragment SMS", FirebaseSms(_) => "Firebase SMS",
867        EmailCode(_) => "email code", SetUpEmailRequired(_) => "email setup required",
868        SmsWord(_) => "SMS word", SmsPhrase(_) => "SMS phrase",
869    }
870}