WTelegramClient/src/Session.cs

122 lines
4.1 KiB
C#
Raw Normal View History

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;
using System.Linq;
2021-09-23 09:27:52 +02:00
using System.Net;
using System.Security.Cryptography;
2021-08-04 00:40:09 +02:00
using System.Text.Json;
namespace WTelegram
{
internal class Session
{
public TL.User User;
2021-09-23 09:27:52 +02:00
public int MainDC;
public Dictionary<int, DCSession> DCSessions = new();
public long Id;
public class DCSession
{
public long AuthKeyID;
public byte[] AuthKey; // 2048-bit = 256 bytes
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;
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-23 09:27:52 +02:00
internal DCSession CurrentDCSession;
2021-08-04 00:40:09 +02:00
private readonly DateTime _sessionStart = DateTime.UtcNow;
private string _pathname;
private byte[] _apiHash; // used as AES key for encryption of session file
2021-08-04 00:40:09 +02:00
private static readonly JsonSerializerOptions JsonOptions = new(Helpers.JsonOptions)
{
Converters = {
new Helpers.PolymorphicConverter<TL.UserProfilePhotoBase>(),
new Helpers.PolymorphicConverter<TL.UserStatus>()
}
};
internal static Session LoadOrCreate(string pathname, byte[] apiHash)
2021-08-04 00:40:09 +02:00
{
if (File.Exists(pathname))
{
try
{
var session = Load(pathname, apiHash);
2021-08-04 00:40:09 +02:00
session._pathname = pathname;
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)
{
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
}
}
return new Session { _pathname = pathname, _apiHash = apiHash, Id = Helpers.RandomLong() };
}
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);
if (!Encryption.Sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32]))
throw new ApplicationException("Integrity check failed in session loading");
return JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), JsonOptions);
2021-08-04 00:40:09 +02:00
}
internal void Save()
{
var utf8Json = JsonSerializer.SerializeToUtf8Bytes(this, JsonOptions);
var finalBlock = new byte[16];
2021-08-10 08:25:37 +02:00
var output = new byte[(16 + 32 + utf8Json.Length + 16) & ~15];
Encryption.RNG.GetBytes(output, 0, 16);
using var aes = Aes.Create();
using var encryptor = aes.CreateEncryptor(_apiHash, output[0..16]);
encryptor.TransformBlock(Encryption.Sha256.ComputeHash(utf8Json), 0, 32, output, 16);
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
}
internal (long msgId, int seqno) NewMsg(bool isContent)
{
int seqno;
2021-09-23 09:27:52 +02:00
long msgId = DateTime.UtcNow.Ticks + CurrentDCSession.ServerTicksOffset - 621355968000000000L;
2021-08-04 00:40:09 +02:00
msgId = msgId * 428 + (msgId >> 24) * 25110956; // approximately unixtime*2^32 and divisible by 4
lock (this)
{
2021-09-23 09:27:52 +02:00
if (msgId <= CurrentDCSession.LastSentMsgId) msgId = CurrentDCSession.LastSentMsgId += 4; else CurrentDCSession.LastSentMsgId = msgId;
seqno = isContent ? CurrentDCSession.Seqno++ * 2 + 1 : CurrentDCSession.Seqno * 2;
Save();
}
2021-08-04 00:40:09 +02:00
return (msgId, seqno);
}
internal DateTime MsgIdToStamp(long serverMsgId)
2021-09-23 09:27:52 +02:00
=> new((serverMsgId >> 32) * 10000000 - CurrentDCSession.ServerTicksOffset + 621355968000000000L, DateTimeKind.Utc);
2021-09-23 09:27:52 +02:00
internal void ChangeDC(int dc)
{
2021-09-23 09:27:52 +02:00
if (dc == 0) dc = MainDC;
CurrentDCSession = dc != 0 ? DCSessions[dc] : new();
}
2021-08-04 00:40:09 +02:00
}
}