1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
16
17use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
18
19const VERSION_V1: u8 = 1;
20const VERSION_V2: u8 = 2;
21
22const AUTH_KEY_LEN: usize = 256;
23
24#[derive(Debug, Clone)]
25pub struct FullSession {
26 pub dc_id: u8,
27 pub ip: IpAddr,
28 pub port: u16,
29 pub auth_key: [u8; AUTH_KEY_LEN],
30 pub user_id: i64,
31 pub server_salt: i64,
32 pub seq_no: u32,
33 pub layer: u32,
34}
35
36#[derive(Debug, Clone)]
37pub struct Session {
38 pub dc_id: u8,
39 pub ip: IpAddr,
40 pub port: u16,
41 pub auth_key: [u8; AUTH_KEY_LEN],
42 pub user_id: i64,
43}
44
45#[derive(Debug, Clone)]
46pub enum StringSession {
47 V1(FullSession),
48 V2(Session),
49}
50
51#[derive(Debug, thiserror::Error)]
52pub enum StringSessionError {
53 #[error("base64 decode error: {0}")]
54 Base64(#[from] base64::DecodeError),
55 #[error("invalid or truncated session data")]
56 InvalidData,
57 #[error("unsupported version: {0}")]
58 UnsupportedVersion(u8),
59 #[error("unknown ip type byte: {0}")]
60 UnknownIpType(u8),
61}
62
63impl StringSession {
64 pub fn decode(s: &str) -> Result<Self, StringSessionError> {
66 let bytes = URL_SAFE_NO_PAD.decode(s.trim())?;
67
68 if bytes.is_empty() {
69 return Err(StringSessionError::InvalidData);
70 }
71
72 match bytes[0] {
73 VERSION_V1 => decode_v1(&bytes).map(StringSession::V1),
74 VERSION_V2 => decode_v2(&bytes).map(StringSession::V2),
75 v => Err(StringSessionError::UnsupportedVersion(v)),
76 }
77 }
78
79 pub fn encode(&self) -> String {
81 match self {
82 StringSession::V2(s) => encode_v2(s),
83 StringSession::V1(s) => encode_v2(&Session {
84 dc_id: s.dc_id,
85 ip: s.ip,
86 port: s.port,
87 auth_key: s.auth_key,
88 user_id: s.user_id,
89 }),
90 }
91 }
92
93 pub fn encode_v1(&self) -> String {
96 match self {
97 StringSession::V1(s) => encode_v1(s),
98 StringSession::V2(_) => {
99 panic!("cannot encode V2 session as V1: missing server_salt, seq_no, layer")
100 }
101 }
102 }
103
104 pub fn session(&self) -> Session {
108 match self {
109 StringSession::V2(s) => s.clone(),
110 StringSession::V1(s) => Session {
111 dc_id: s.dc_id,
112 ip: s.ip,
113 port: s.port,
114 auth_key: s.auth_key,
115 user_id: s.user_id,
116 },
117 }
118 }
119
120 pub fn full_session(&self) -> Option<&FullSession> {
123 match self {
124 StringSession::V1(s) => Some(s),
125 StringSession::V2(_) => None,
126 }
127 }
128
129 pub fn version(&self) -> u8 {
132 match self {
133 StringSession::V1(_) => VERSION_V1,
134 StringSession::V2(_) => VERSION_V2,
135 }
136 }
137}
138
139impl From<Session> for StringSession {
140 fn from(s: Session) -> Self {
141 StringSession::V2(s)
142 }
143}
144
145impl From<FullSession> for StringSession {
146 fn from(s: FullSession) -> Self {
147 StringSession::V1(s)
148 }
149}
150
151fn encode_v2(s: &Session) -> String {
152 let ip_bytes = ip_to_bytes(s.ip);
153 let ip_type = ip_type_byte(s.ip);
154
155 let mut buf = Vec::with_capacity(1 + 1 + 1 + ip_bytes.len() + 2 + 8 + AUTH_KEY_LEN);
156 buf.push(VERSION_V2);
157 buf.push(s.dc_id);
158 buf.push(ip_type);
159 buf.extend_from_slice(&ip_bytes);
160 buf.extend_from_slice(&s.port.to_be_bytes());
161 buf.extend_from_slice(&s.user_id.to_be_bytes());
162 buf.extend_from_slice(&s.auth_key);
163
164 URL_SAFE_NO_PAD.encode(&buf)
165}
166
167fn encode_v1(s: &FullSession) -> String {
168 let ip_bytes = ip_to_bytes(s.ip);
169 let ip_type = ip_type_byte(s.ip);
170
171 let mut buf = Vec::with_capacity(1 + 1 + 1 + ip_bytes.len() + 2 + 8 + 8 + 4 + 4 + AUTH_KEY_LEN);
172 buf.push(VERSION_V1);
173 buf.push(s.dc_id);
174 buf.push(ip_type);
175 buf.extend_from_slice(&ip_bytes);
176 buf.extend_from_slice(&s.port.to_be_bytes());
177 buf.extend_from_slice(&s.user_id.to_be_bytes());
178 buf.extend_from_slice(&s.server_salt.to_be_bytes());
179 buf.extend_from_slice(&s.seq_no.to_be_bytes());
180 buf.extend_from_slice(&s.layer.to_be_bytes());
181 buf.extend_from_slice(&s.auth_key);
182
183 URL_SAFE_NO_PAD.encode(&buf)
184}
185
186fn decode_v2(bytes: &[u8]) -> Result<Session, StringSessionError> {
187 let mut c = 1usize;
188
189 let dc_id = read_u8(bytes, &mut c)?;
190 let ip = read_ip(bytes, &mut c)?;
191
192 if bytes.len() < c + 2 + 8 + AUTH_KEY_LEN {
193 return Err(StringSessionError::InvalidData);
194 }
195
196 let port = read_u16_be(bytes, &mut c)?;
197 let user_id = read_i64_be(bytes, &mut c)?;
198 let auth_key = read_auth_key(bytes, &mut c)?;
199
200 Ok(Session {
201 dc_id,
202 ip,
203 port,
204 auth_key,
205 user_id,
206 })
207}
208
209fn decode_v1(bytes: &[u8]) -> Result<FullSession, StringSessionError> {
210 let mut c = 1usize;
211
212 let dc_id = read_u8(bytes, &mut c)?;
213 let ip = read_ip(bytes, &mut c)?;
214
215 if bytes.len() < c + 2 + 8 + 8 + 4 + 4 + AUTH_KEY_LEN {
216 return Err(StringSessionError::InvalidData);
217 }
218
219 let port = read_u16_be(bytes, &mut c)?;
220 let user_id = read_i64_be(bytes, &mut c)?;
221 let server_salt = read_i64_be(bytes, &mut c)?;
222 let seq_no = read_u32_be(bytes, &mut c)?;
223 let layer = read_u32_be(bytes, &mut c)?;
224 let auth_key = read_auth_key(bytes, &mut c)?;
225
226 Ok(FullSession {
227 dc_id,
228 ip,
229 port,
230 auth_key,
231 user_id,
232 server_salt,
233 seq_no,
234 layer,
235 })
236}
237
238fn read_u8(bytes: &[u8], c: &mut usize) -> Result<u8, StringSessionError> {
239 if bytes.len() < *c + 1 {
240 return Err(StringSessionError::InvalidData);
241 }
242 let v = bytes[*c];
243 *c += 1;
244 Ok(v)
245}
246
247fn read_u16_be(bytes: &[u8], c: &mut usize) -> Result<u16, StringSessionError> {
248 let v = u16::from_be_bytes(
249 bytes[*c..*c + 2]
250 .try_into()
251 .map_err(|_| StringSessionError::InvalidData)?,
252 );
253 *c += 2;
254 Ok(v)
255}
256
257fn read_u32_be(bytes: &[u8], c: &mut usize) -> Result<u32, StringSessionError> {
258 let v = u32::from_be_bytes(
259 bytes[*c..*c + 4]
260 .try_into()
261 .map_err(|_| StringSessionError::InvalidData)?,
262 );
263 *c += 4;
264 Ok(v)
265}
266
267fn read_i64_be(bytes: &[u8], c: &mut usize) -> Result<i64, StringSessionError> {
268 let v = i64::from_be_bytes(
269 bytes[*c..*c + 8]
270 .try_into()
271 .map_err(|_| StringSessionError::InvalidData)?,
272 );
273 *c += 8;
274 Ok(v)
275}
276
277fn read_auth_key(bytes: &[u8], c: &mut usize) -> Result<[u8; AUTH_KEY_LEN], StringSessionError> {
278 let key: [u8; AUTH_KEY_LEN] = bytes[*c..*c + AUTH_KEY_LEN]
279 .try_into()
280 .map_err(|_| StringSessionError::InvalidData)?;
281 *c += AUTH_KEY_LEN;
282 Ok(key)
283}
284
285fn read_ip(bytes: &[u8], c: &mut usize) -> Result<IpAddr, StringSessionError> {
286 let ip_type = read_u8(bytes, c)?;
287 match ip_type {
288 4 => {
289 if bytes.len() < *c + 4 {
290 return Err(StringSessionError::InvalidData);
291 }
292 let octets: [u8; 4] = bytes[*c..*c + 4]
293 .try_into()
294 .map_err(|_| StringSessionError::InvalidData)?;
295 *c += 4;
296 Ok(IpAddr::V4(Ipv4Addr::from(octets)))
297 }
298 6 => {
299 if bytes.len() < *c + 16 {
300 return Err(StringSessionError::InvalidData);
301 }
302 let octets: [u8; 16] = bytes[*c..*c + 16]
303 .try_into()
304 .map_err(|_| StringSessionError::InvalidData)?;
305 *c += 16;
306 Ok(IpAddr::V6(Ipv6Addr::from(octets)))
307 }
308 other => Err(StringSessionError::UnknownIpType(other)),
309 }
310}
311
312fn ip_to_bytes(ip: IpAddr) -> Vec<u8> {
313 match ip {
314 IpAddr::V4(v4) => v4.octets().to_vec(),
315 IpAddr::V6(v6) => v6.octets().to_vec(),
316 }
317}
318
319fn ip_type_byte(ip: IpAddr) -> u8 {
320 match ip {
321 IpAddr::V4(_) => 4,
322 IpAddr::V6(_) => 6,
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 fn dummy_key() -> [u8; AUTH_KEY_LEN] {
331 let mut k = [0u8; AUTH_KEY_LEN];
332 for (i, b) in k.iter_mut().enumerate() {
333 *b = i as u8;
334 }
335 k
336 }
337
338 fn ipv4() -> IpAddr {
339 IpAddr::V4(Ipv4Addr::new(149, 154, 167, 51))
340 }
341
342 fn ipv6() -> IpAddr {
343 IpAddr::V6(Ipv6Addr::new(0x2001, 0xb28, 0xf23d, 0, 0, 0, 0, 0xa))
344 }
345
346 #[test]
347 fn v2_roundtrip_ipv4() {
348 let s = StringSession::V2(Session {
349 dc_id: 2,
350 ip: ipv4(),
351 port: 443,
352 auth_key: dummy_key(),
353 user_id: 123456789,
354 });
355
356 let encoded = s.encode();
357 let decoded = StringSession::decode(&encoded).unwrap();
358
359 assert_eq!(decoded.version(), 2);
360 let d = decoded.session();
361 assert_eq!(d.dc_id, 2);
362 assert_eq!(d.ip, ipv4());
363 assert_eq!(d.port, 443);
364 assert_eq!(d.user_id, 123456789);
365 assert_eq!(d.auth_key, dummy_key());
366 }
367
368 #[test]
369 fn v2_roundtrip_ipv6() {
370 let s = StringSession::V2(Session {
371 dc_id: 4,
372 ip: ipv6(),
373 port: 443,
374 auth_key: dummy_key(),
375 user_id: -987654321,
376 });
377
378 let encoded = s.encode();
379 let decoded = StringSession::decode(&encoded).unwrap();
380
381 assert_eq!(decoded.version(), 2);
382 let d = decoded.session();
383 assert_eq!(d.ip, ipv6());
384 assert_eq!(d.user_id, -987654321);
385 }
386
387 #[test]
388 fn v1_roundtrip_ipv4() {
389 let s = StringSession::V1(FullSession {
390 dc_id: 1,
391 ip: ipv4(),
392 port: 443,
393 auth_key: dummy_key(),
394 user_id: 111,
395 server_salt: -999,
396 seq_no: 42,
397 layer: 166,
398 });
399
400 let encoded = s.encode_v1();
401 let decoded = StringSession::decode(&encoded).unwrap();
402
403 assert_eq!(decoded.version(), 1);
404 let f = decoded.full_session().unwrap();
405 assert_eq!(f.dc_id, 1);
406 assert_eq!(f.ip, ipv4());
407 assert_eq!(f.port, 443);
408 assert_eq!(f.user_id, 111);
409 assert_eq!(f.server_salt, -999);
410 assert_eq!(f.seq_no, 42);
411 assert_eq!(f.layer, 166);
412 assert_eq!(f.auth_key, dummy_key());
413 }
414
415 #[test]
416 fn v1_roundtrip_ipv6() {
417 let s = StringSession::V1(FullSession {
418 dc_id: 5,
419 ip: ipv6(),
420 port: 443,
421 auth_key: dummy_key(),
422 user_id: 777,
423 server_salt: 12345,
424 seq_no: 10,
425 layer: 166,
426 });
427
428 let encoded = s.encode_v1();
429 let decoded = StringSession::decode(&encoded).unwrap();
430
431 assert_eq!(decoded.version(), 1);
432 let f = decoded.full_session().unwrap();
433 assert_eq!(f.ip, ipv6());
434 assert_eq!(f.layer, 166);
435 }
436
437 #[test]
438 fn v1_encode_produces_v2_when_called_via_encode() {
439 let s = StringSession::V1(FullSession {
440 dc_id: 2,
441 ip: ipv4(),
442 port: 443,
443 auth_key: dummy_key(),
444 user_id: 555,
445 server_salt: 0,
446 seq_no: 0,
447 layer: 166,
448 });
449
450 let encoded = s.encode();
451 let decoded = StringSession::decode(&encoded).unwrap();
452 assert_eq!(decoded.version(), 2);
453 }
454
455 #[test]
456 fn v2_encoded_length_ipv4() {
457 let s = StringSession::V2(Session {
458 dc_id: 1,
459 ip: ipv4(),
460 port: 443,
461 auth_key: dummy_key(),
462 user_id: 1,
463 });
464 assert_eq!(s.encode().len(), 364);
465 }
466
467 #[test]
468 fn rejects_truncated() {
469 assert!(StringSession::decode("Ag").is_err());
470 }
471
472 #[test]
473 fn rejects_unsupported_version() {
474 let bad = URL_SAFE_NO_PAD.encode(&[99u8]);
475 assert!(matches!(
476 StringSession::decode(&bad),
477 Err(StringSessionError::UnsupportedVersion(99))
478 ));
479 }
480
481 #[test]
482 fn full_session_returns_none_for_v2() {
483 let s = StringSession::V2(Session {
484 dc_id: 1,
485 ip: ipv4(),
486 port: 443,
487 auth_key: dummy_key(),
488 user_id: 1,
489 });
490 assert!(s.full_session().is_none());
491 }
492}