2021-08-04 00:40:09 +02:00
|
|
|
|
using System;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
using System.Collections.Generic;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
using System.IO;
|
2021-08-06 07:28:54 +02:00
|
|
|
|
using System.Linq;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
using System.Net;
|
2021-08-06 07:28:54 +02:00
|
|
|
|
using System.Security.Cryptography;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
using System.Text.Json;
|
2021-09-28 16:12:20 +02:00
|
|
|
|
using System.Threading;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
|
|
|
|
|
namespace WTelegram
|
|
|
|
|
|
{
|
|
|
|
|
|
internal class Session
|
|
|
|
|
|
{
|
2021-09-18 07:26:06 +02:00
|
|
|
|
public TL.User User;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
public int MainDC;
|
|
|
|
|
|
public Dictionary<int, DCSession> DCSessions = new();
|
|
|
|
|
|
|
|
|
|
|
|
public class DCSession
|
|
|
|
|
|
{
|
2021-09-28 16:12:20 +02:00
|
|
|
|
public long Id;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
public long AuthKeyID;
|
|
|
|
|
|
public byte[] AuthKey; // 2048-bit = 256 bytes
|
2021-09-25 19:43:39 +02:00
|
|
|
|
public long UserId;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
public long Salt;
|
|
|
|
|
|
public int Seqno;
|
|
|
|
|
|
public long ServerTicksOffset;
|
|
|
|
|
|
public long LastSentMsgId;
|
|
|
|
|
|
public TL.DcOption DataCenter;
|
|
|
|
|
|
|
2021-09-28 16:12:20 +02:00
|
|
|
|
internal Client Client;
|
|
|
|
|
|
internal int DcID => DataCenter?.id ?? 0;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
internal IPEndPoint EndPoint => DataCenter == null ? null : new(IPAddress.Parse(DataCenter.ip_address), DataCenter.port);
|
|
|
|
|
|
}
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
|
|
|
|
|
public DateTime SessionStart => _sessionStart;
|
2021-09-28 16:12:20 +02:00
|
|
|
|
public readonly SemaphoreSlim _sem = new(1);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
private readonly DateTime _sessionStart = DateTime.UtcNow;
|
|
|
|
|
|
private string _pathname;
|
2021-08-06 07:28:54 +02:00
|
|
|
|
private byte[] _apiHash; // used as AES key for encryption of session file
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
2021-09-18 07:26:06 +02:00
|
|
|
|
private static readonly JsonSerializerOptions JsonOptions = new(Helpers.JsonOptions)
|
|
|
|
|
|
{
|
|
|
|
|
|
Converters = {
|
|
|
|
|
|
new Helpers.PolymorphicConverter<TL.UserProfilePhotoBase>(),
|
|
|
|
|
|
new Helpers.PolymorphicConverter<TL.UserStatus>()
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-08-06 07:28:54 +02:00
|
|
|
|
internal static Session LoadOrCreate(string pathname, byte[] apiHash)
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (File.Exists(pathname))
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2021-08-06 07:28:54 +02:00
|
|
|
|
var session = Load(pathname, apiHash);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
session._pathname = pathname;
|
2021-08-06 07:28:54 +02:00
|
|
|
|
session._apiHash = apiHash;
|
2021-08-04 10:11:07 +02:00
|
|
|
|
Helpers.Log(2, "Loaded previous session");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
return session;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2021-08-06 07:28:54 +02:00
|
|
|
|
throw new ApplicationException($"Exception while reading session file: {ex.Message}\nDelete the file to start a new session", ex);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-09-28 16:12:20 +02:00
|
|
|
|
return new Session { _pathname = pathname, _apiHash = apiHash };
|
2021-08-06 07:28:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal static Session Load(string pathname, byte[] apiHash)
|
|
|
|
|
|
{
|
|
|
|
|
|
var input = File.ReadAllBytes(pathname);
|
|
|
|
|
|
using var aes = Aes.Create();
|
|
|
|
|
|
using var decryptor = aes.CreateDecryptor(apiHash, input[0..16]);
|
|
|
|
|
|
var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16);
|
2021-08-16 22:30:45 +02:00
|
|
|
|
if (!Encryption.Sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32]))
|
2021-08-06 07:28:54 +02:00
|
|
|
|
throw new ApplicationException("Integrity check failed in session loading");
|
2021-09-18 07:26:06 +02:00
|
|
|
|
return JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), JsonOptions);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal void Save()
|
|
|
|
|
|
{
|
2021-09-18 07:26:06 +02:00
|
|
|
|
var utf8Json = JsonSerializer.SerializeToUtf8Bytes(this, JsonOptions);
|
2021-08-06 07:28:54 +02:00
|
|
|
|
var finalBlock = new byte[16];
|
2021-08-10 08:25:37 +02:00
|
|
|
|
var output = new byte[(16 + 32 + utf8Json.Length + 16) & ~15];
|
2021-08-06 07:28:54 +02:00
|
|
|
|
Encryption.RNG.GetBytes(output, 0, 16);
|
|
|
|
|
|
using var aes = Aes.Create();
|
|
|
|
|
|
using var encryptor = aes.CreateEncryptor(_apiHash, output[0..16]);
|
2021-08-16 22:30:45 +02:00
|
|
|
|
encryptor.TransformBlock(Encryption.Sha256.ComputeHash(utf8Json), 0, 32, output, 16);
|
2021-08-06 07:28:54 +02:00
|
|
|
|
encryptor.TransformBlock(utf8Json, 0, utf8Json.Length & ~15, output, 48);
|
|
|
|
|
|
utf8Json.AsSpan(utf8Json.Length & ~15).CopyTo(finalBlock);
|
|
|
|
|
|
encryptor.TransformFinalBlock(finalBlock, 0, utf8Json.Length & 15).CopyTo(output.AsMemory(48 + utf8Json.Length & ~15));
|
2021-09-27 03:20:58 +02:00
|
|
|
|
if (!File.Exists(_pathname))
|
|
|
|
|
|
File.WriteAllBytes(_pathname, output);
|
2021-09-27 17:08:22 +02:00
|
|
|
|
else lock (this)
|
|
|
|
|
|
{
|
|
|
|
|
|
string tempPathname = _pathname + ".tmp";
|
|
|
|
|
|
File.WriteAllBytes(tempPathname, output);
|
|
|
|
|
|
File.Replace(tempPathname, _pathname, null);
|
|
|
|
|
|
}
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|