layer-mtproto 0.4.6

MTProto 2.0 session management, message framing, DH key exchange and transport abstractions
Documentation
<div align="center">

<img src="https://raw.githubusercontent.com/ankit-chaubey/layer/main/docs/images/crate-mtproto-banner.svg" alt="layer-mtproto" width="100%" />

# πŸ“‘ layer-mtproto

**MTProto 2.0 session management, DH key exchange, and message framing for Rust.**

[![Crates.io](https://img.shields.io/crates/v/layer-mtproto?color=fc8d62)](https://crates.io/crates/layer-mtproto)
[![docs.rs](https://img.shields.io/badge/docs.rs-layer--mtproto-5865F2)](https://docs.rs/layer-mtproto)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)
[![Rust](https://img.shields.io/badge/rust-2024_edition-f74c00)](https://www.rust-lang.org/)
[![TL Layer](https://img.shields.io/badge/TL%20Layer-224-8b5cf6)](https://core.telegram.org/mtproto)

*A complete, from-scratch implementation of Telegram's MTProto 2.0 session layer.*

</div>

---

## πŸ“¦ Installation

```toml
[dependencies]
layer-mtproto  = "0.4.6"
layer-tl-types = { version = "0.4.6", features = ["tl-mtproto"] }
```

---

## ✨ What It Does

`layer-mtproto` implements the full MTProto 2.0 session layer β€” the encrypted tunnel through which all Telegram API calls travel. This is the core protocol machinery that sits between your application code and the TCP socket.

It handles:
- 🀝 **3-step DH key exchange** β€” deriving a shared auth key from scratch
- πŸ” **Encrypted sessions** β€” packing/unpacking MTProto 2.0 messages with AES-IGE
- πŸ“¦ **Message framing** β€” salt, session_id, message_id, sequence numbers
- πŸ—œοΈ **Containers & compression** β€” `msg_container`, `gzip_packed` responses
- πŸ§‚ **Salt management** β€” server salt tracking and auto-correction
- πŸ”„ **Session state** β€” time offset, sequence number, salt rotation
- πŸ” **Error recovery** β€” `bad_msg_notification`, `bad_server_salt`, `msg_resend_req`
- βœ… **Acknowledgements** β€” `MsgsAck` with a pending-ack queue

---

## πŸ—οΈ Architecture

```
Application (layer-client)
       β”‚
       β–Ό
  EncryptedSession         ← encrypt/decrypt, pack/unpack
       β”‚
       β–Ό
  Authentication           ← 3-step DH handshake (step1 β†’ step2 β†’ step3 β†’ finish)
       β”‚
       β–Ό
  layer-crypto             ← AES-IGE, RSA, SHA, Diffie-Hellman
       β”‚
       β–Ό
  TCP Socket
```

---

## πŸ“š Core Types

### `EncryptedSession`

Manages the live MTProto session after a key has been established.

```rust
use layer_mtproto::EncryptedSession;

// Create from a completed DH handshake
let session = EncryptedSession::new(auth_key, first_salt, time_offset);

// Pack a RemoteCall into encrypted wire bytes
let wire_bytes = session.pack(&my_request)?;

// Pack any Serializable directly (bypasses RemoteCall bound)
let wire_bytes = session.pack_serializable(&raw_obj)?;

// Unpack an encrypted response from the server
let msg = session.unpack(&mut raw_bytes)?;
println!("msg_id={}, body_len={}", msg.msg_id, msg.body.len());
```

---

### `authentication` β€” 3-Step DH Key Exchange

The full MTProto DH handshake as specified by Telegram:

```rust
use layer_mtproto::authentication as auth;

// Step 1 β€” req_pq_multi: get the server's PQ
let (req1, state1) = auth::step1()?;
// ... send req1 over the wire, receive res_pq ...

// Step 2 β€” req_DH_params: send our DH parameters
let (req2, state2) = auth::step2(state1, res_pq)?;
// ... send req2, receive server_DH_params ...

// Step 3 β€” set_client_DH_params: send our client DH
let (req3, state3) = auth::step3(state2, dh_params)?;
// ... send req3, receive dh_answer ...

// Finish β€” derive the auth key from the completed handshake
let done = auth::finish(state3, dh_answer)?;

// done.auth_key    β†’ [u8; 256]   β€” the shared secret
// done.first_salt  β†’ i64         β€” first server salt to use
// done.time_offset β†’ i32         β€” clock skew relative to server
```

---

### `Message`

A decoded MTProto message as returned by `session.unpack()`:

```rust
pub struct Message {
    pub msg_id:  i64,
    pub seq_no:  i32,
    pub salt:    i64,
    pub body:    Vec<u8>,   // raw TL bytes of the inner object
}
```

The `body` bytes are deserialized by `layer-client` using `layer-tl-types`'s `Deserializable` trait.

---

### `Session` (plain / unencrypted)

Used only for sending the initial DH handshake messages before an auth key exists:

```rust
use layer_mtproto::Session;

let mut plain = Session::new();
// Pack a plaintext MTProto message
let framed = plain.pack_plain(&my_handshake_request)?;
```

---

## πŸ” What's Inside

### Encryption Details

- AES-IGE encryption/decryption via `layer-crypto`
- `msg_key` = SHA-256 of `(auth_key[88..120] || plaintext)` for client→server
- `msg_key` = SHA-256 of `(auth_key[96..128] || plaintext)` for server→client
- The 256-byte auth key is split into sub-keys for encryption, MAC, and padding

### Message Framing

Every outgoing MTProto message has this structure:

```
server_salt     (8 bytes) β€” current server salt
session_id      (8 bytes) β€” random, stable for this session's lifetime
message_id      (8 bytes) β€” Unix time * 2^32, monotonically increasing
seq_no          (4 bytes) β€” content-related message counter
message_length  (4 bytes) β€” length of payload in bytes
payload         (N bytes) β€” serialized TL object
padding         (M bytes) β€” 12–1024 random bytes so total % 16 == 0
```

Wrapped in a 32-byte `msg_key` prefix after encryption.

### Containers & Compression

- `msg_container` (TL ID `0x73f1f8dc`) β€” wraps multiple logical messages in one TCP frame; both packing and unpacking are supported
- `gzip_packed` (TL ID `0x3072cfa1`) β€” response bodies are decompressed automatically; outgoing large requests are optionally compressed

### Salt & Time Management

- `bad_server_salt` β€” session records the corrected salt and automatically resets
- `future_salts` prefetch β€” salts are requested in advance; session rotates before expiry
- `time_offset` correction β€” all outgoing `msg_id` values are adjusted for clock skew

### Error Recovery

- `bad_msg_notification` β€” messages with wrong `msg_id`, `seq_no`, or `session_id` are resent with corrected framing
- `seq_no` auto-correction for error codes 32 (seq_no too low) and 33 (seq_no too high)
- `msg_resend_req` β€” fulfils resend requests by replaying from a sent-body cache

### Acknowledgements

- Received content-related messages accumulate in a `pending_ack` list
- The `flush_acks()` call bundles them into a single `MsgsAck` message
- `layer-client` flushes pending ACKs on a timer and before each outgoing call

---

## πŸ”— Part of the layer stack

```
layer-client
└── layer-mtproto     ← you are here
    β”œβ”€β”€ layer-tl-types  (tl-mtproto feature)
    └── layer-crypto
```

---

## πŸ“„ License

Licensed under either of, at your option:

- **MIT License** β€” see [LICENSE-MIT]../LICENSE-MIT
- **Apache License, Version 2.0** β€” see [LICENSE-APACHE]../LICENSE-APACHE

---

## πŸ‘€ Author

**Ankit Chaubey**  
[github.com/ankit-chaubey](https://github.com/ankit-chaubey) Β· [ankitchaubey.in](https://ankitchaubey.in) Β· [ankitchaubey.dev@gmail.com](mailto:ankitchaubey.dev@gmail.com)

πŸ“¦ [github.com/ankit-chaubey/layer](https://github.com/ankit-chaubey/layer)