1use std::net::{IpAddr, Ipv4Addr, SocketAddr};
4
5use crate::{AUTH_KEY_SIZE, Result};
6
7const DC_ADDRESSES: [(i32, Ipv4Addr, u16); 5] = [
9 (1, Ipv4Addr::new(149, 154, 175, 53), 443),
10 (2, Ipv4Addr::new(149, 154, 167, 51), 443),
11 (3, Ipv4Addr::new(149, 154, 175, 100), 443),
12 (4, Ipv4Addr::new(149, 154, 167, 91), 443),
13 (5, Ipv4Addr::new(91, 108, 56, 130), 443),
14];
15
16fn get_dc_addr(dc_id: i32) -> SocketAddr {
18 DC_ADDRESSES.iter().find(|(id, _, _)| *id == dc_id).map_or_else(
19 || SocketAddr::new(IpAddr::V4(Ipv4Addr::new(149, 154, 167, 51)), 443),
20 |(_, ip, port)| SocketAddr::new(IpAddr::V4(*ip), *port),
21 )
22}
23
24#[derive(Debug)]
26pub struct Account {
27 index: i32,
29 dc_id: i32,
31 user_id: i64,
33 auth_key: [u8; AUTH_KEY_SIZE],
35}
36
37impl Account {
38 pub(crate) const fn new(
40 index: i32,
41 dc_id: i32,
42 user_id: i64,
43 auth_key: [u8; AUTH_KEY_SIZE],
44 ) -> Self {
45 Self { index, dc_id, user_id, auth_key }
46 }
47
48 #[must_use]
50 pub const fn index(&self) -> i32 {
51 self.index
52 }
53
54 #[must_use]
56 pub const fn dc_id(&self) -> i32 {
57 self.dc_id
58 }
59
60 #[must_use]
62 pub const fn user_id(&self) -> i64 {
63 self.user_id
64 }
65
66 #[must_use]
68 pub const fn auth_key_bytes(&self) -> &[u8; AUTH_KEY_SIZE] {
69 &self.auth_key
70 }
71
72 pub fn to_grammers_session(&self) -> Result<grammers_session::Session> {
76 use grammers_session::Session;
77
78 let session = Session::new();
79
80 let addr = get_dc_addr(self.dc_id);
82 session.insert_dc(self.dc_id, addr, self.auth_key);
83
84 if self.user_id != 0 {
86 session.set_user(self.user_id, self.dc_id, false);
87 }
88
89 Ok(session)
90 }
91
92 pub fn to_session_string(&self) -> Result<String> {
94 let session = self.to_grammers_session()?;
95
96 let data = session.save();
98 Ok(base64_encode(&data))
99 }
100}
101
102fn base64_encode(data: &[u8]) -> String {
104 const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
105
106 let mut result = String::new();
107 let mut i = 0;
108
109 while i < data.len() {
110 let b0 = data[i] as usize;
111 let b1 = if i + 1 < data.len() { data[i + 1] as usize } else { 0 };
112 let b2 = if i + 2 < data.len() { data[i + 2] as usize } else { 0 };
113
114 result.push(ALPHABET[b0 >> 2] as char);
115 result.push(ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)] as char);
116
117 if i + 1 < data.len() {
118 result.push(ALPHABET[((b1 & 0x0f) << 2) | (b2 >> 6)] as char);
119 } else {
120 result.push('=');
121 }
122
123 if i + 2 < data.len() {
124 result.push(ALPHABET[b2 & 0x3f] as char);
125 } else {
126 result.push('=');
127 }
128
129 i += 3;
130 }
131
132 result
133}
134
135#[cfg(test)]
136#[allow(
137 clippy::unwrap_used,
138 clippy::expect_used,
139 clippy::indexing_slicing,
140 clippy::unreadable_literal,
141 reason = "test assertions with controlled inputs"
142)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_account_creation() {
148 let auth_key = [0xAB; AUTH_KEY_SIZE];
149 let account = Account::new(0, 2, 12345678, auth_key);
150
151 assert_eq!(account.index(), 0);
152 assert_eq!(account.dc_id(), 2);
153 assert_eq!(account.user_id(), 12345678);
154 assert_eq!(account.auth_key_bytes(), &auth_key);
155 }
156
157 #[test]
158 fn test_base64_encode() {
159 assert_eq!(base64_encode(b""), "");
160 assert_eq!(base64_encode(b"f"), "Zg==");
161 assert_eq!(base64_encode(b"fo"), "Zm8=");
162 assert_eq!(base64_encode(b"foo"), "Zm9v");
163 assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
164 assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
165 assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
166 }
167}