Store less stuff in session data and reduce save frequency for better performance.

This commit is contained in:
Wizou 2025-04-06 19:48:43 +02:00
parent f495f59bc8
commit 6d238dc528
4 changed files with 67 additions and 70 deletions

View file

@ -62,10 +62,8 @@ namespace WTelegramClientTest
{ {
private readonly NpgsqlConnection _sql; private readonly NpgsqlConnection _sql;
private readonly string _sessionName; private readonly string _sessionName;
private byte[] _data; private readonly byte[] _data;
private int _dataLen; private readonly int _dataLen;
private DateTime _lastWrite;
private Task _delayedWrite;
/// <param name="databaseUrl">Heroku DB URL of the form "postgres://user:password@host:port/database"</param> /// <param name="databaseUrl">Heroku DB URL of the form "postgres://user:password@host:port/database"</param>
/// <param name="sessionName">Entry name for the session data in the WTelegram_sessions table (default: "Heroku")</param> /// <param name="sessionName">Entry name for the session data in the WTelegram_sessions table (default: "Heroku")</param>
@ -85,7 +83,6 @@ namespace WTelegramClientTest
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
_delayedWrite?.Wait();
_sql.Dispose(); _sql.Dispose();
} }
@ -97,18 +94,9 @@ namespace WTelegramClientTest
public override void Write(byte[] buffer, int offset, int count) // Write call and buffer modifications are done within a lock() public override void Write(byte[] buffer, int offset, int count) // Write call and buffer modifications are done within a lock()
{ {
_data = buffer; _dataLen = count; using var cmd = new NpgsqlCommand($"INSERT INTO WTelegram_sessions (name, data) VALUES ('{_sessionName}', @data) ON CONFLICT (name) DO UPDATE SET data = EXCLUDED.data", _sql);
if (_delayedWrite != null) return; cmd.Parameters.AddWithValue("data", count == buffer.Length ? buffer : buffer[offset..(offset + count)]);
var left = 1000 - (int)(DateTime.UtcNow - _lastWrite).TotalMilliseconds; cmd.ExecuteNonQuery();
if (left < 0)
{
using var cmd = new NpgsqlCommand($"INSERT INTO WTelegram_sessions (name, data) VALUES ('{_sessionName}', @data) ON CONFLICT (name) DO UPDATE SET data = EXCLUDED.data", _sql);
cmd.Parameters.AddWithValue("data", count == buffer.Length ? buffer : buffer[offset..(offset + count)]);
cmd.ExecuteNonQuery();
_lastWrite = DateTime.UtcNow;
}
else // delay writings for a full second
_delayedWrite = Task.Delay(left).ContinueWith(t => { lock (this) { _delayedWrite = null; Write(_data, 0, _dataLen); } });
} }
public override long Length => _dataLen; public override long Length => _dataLen;

View file

@ -115,7 +115,7 @@ namespace WTelegram
_session = Session.LoadOrCreate(sessionStore, Convert.FromHexString(session_key)); _session = Session.LoadOrCreate(sessionStore, Convert.FromHexString(session_key));
if (_session.ApiId == 0) _session.ApiId = int.Parse(Config("api_id")); if (_session.ApiId == 0) _session.ApiId = int.Parse(Config("api_id"));
if (_session.MainDC != 0) _session.DCSessions.TryGetValue(_session.MainDC, out _dcSession); if (_session.MainDC != 0) _session.DCSessions.TryGetValue(_session.MainDC, out _dcSession);
_dcSession ??= new() { Id = Helpers.RandomLong() }; _dcSession ??= new();
_dcSession.Client = this; _dcSession.Client = this;
var version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion; var version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
Helpers.Log(1, $"WTelegramClient {version} running under {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}"); Helpers.Log(1, $"WTelegramClient {version} running under {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");
@ -272,8 +272,8 @@ namespace WTelegram
// we have already negociated an AuthKey with this DC // we have already negociated an AuthKey with this DC
if (dcSession.DataCenter.flags == flags && _session.DCSessions.Remove(-dcId)) if (dcSession.DataCenter.flags == flags && _session.DCSessions.Remove(-dcId))
return _session.DCSessions[dcId] = dcSession; // we found a misclassed DC, change its sign return _session.DCSessions[dcId] = dcSession; // we found a misclassed DC, change its sign
dcSession = new Session.DCSession { Id = Helpers.RandomLong(), // clone AuthKey for a session on the matching media_only DC dcSession = new Session.DCSession { // clone AuthKey for a session on the matching media_only DC
AuthKeyID = dcSession.AuthKeyID, AuthKey = dcSession.AuthKey, UserId = dcSession.UserId }; authKeyID = dcSession.authKeyID, AuthKey = dcSession.AuthKey, UserId = dcSession.UserId };
} }
// try to find the most appropriate DcOption for this DC // try to find the most appropriate DcOption for this DC
if (dcSession?.AuthKey == null) // we'll need to negociate an AuthKey => can't use media_only DC if (dcSession?.AuthKey == null) // we'll need to negociate an AuthKey => can't use media_only DC
@ -283,7 +283,7 @@ namespace WTelegram
} }
var dcOptions = GetDcOptions(Math.Abs(dcId), flags); var dcOptions = GetDcOptions(Math.Abs(dcId), flags);
var dcOption = dcOptions.FirstOrDefault() ?? throw new WTException($"Could not find adequate dc_option for DC {dcId}"); var dcOption = dcOptions.FirstOrDefault() ?? throw new WTException($"Could not find adequate dc_option for DC {dcId}");
dcSession ??= new Session.DCSession { Id = Helpers.RandomLong() }; // create new session only if not already existing dcSession ??= new(); // create new session only if not already existing
dcSession.DataCenter = dcOption; dcSession.DataCenter = dcOption;
return _session.DCSessions[dcId] = dcSession; return _session.DCSessions[dcId] = dcSession;
} }
@ -302,6 +302,7 @@ namespace WTelegram
var flags = _dcSession.DataCenter.flags; var flags = _dcSession.DataCenter.flags;
if (dcId < 0) flags = (flags & DcOption.Flags.ipv6) | DcOption.Flags.media_only; if (dcId < 0) flags = (flags & DcOption.Flags.ipv6) | DcOption.Flags.media_only;
altSession = GetOrCreateDCSession(dcId, flags); altSession = GetOrCreateDCSession(dcId, flags);
_session.Save();
if (altSession.Client?.Disconnected ?? false) { altSession.Client.Dispose(); altSession.Client = null; } if (altSession.Client?.Disconnected ?? false) { altSession.Client.Dispose(); altSession.Client = null; }
altSession.Client ??= new Client(this, altSession); altSession.Client ??= new Client(this, altSession);
} }
@ -321,6 +322,7 @@ namespace WTelegram
if (authorization is not Auth_Authorization { user: User user }) if (authorization is not Auth_Authorization { user: User user })
throw new WTException("Failed to get Authorization: " + authorization.GetType().Name); throw new WTException("Failed to get Authorization: " + authorization.GetType().Name);
altSession.UserId = user.id; altSession.UserId = user.id;
lock (_session) _session.Save();
} }
} }
finally finally
@ -419,7 +421,7 @@ namespace WTelegram
} }
internal DateTime MsgIdToStamp(long serverMsgId) internal DateTime MsgIdToStamp(long serverMsgId)
=> new((serverMsgId >> 32) * 10000000 - _dcSession.ServerTicksOffset + 621355968000000000L, DateTimeKind.Utc); => new((serverMsgId >> 32) * 10000000 - _dcSession.serverTicksOffset + 621355968000000000L, DateTimeKind.Utc);
internal IObject ReadFrame(byte[] data, int dataLen) internal IObject ReadFrame(byte[] data, int dataLen)
{ {
@ -432,7 +434,7 @@ namespace WTelegram
throw new WTException($"Packet payload too small: {dataLen}"); throw new WTException($"Packet payload too small: {dataLen}");
long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data); long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data);
if (authKeyId != _dcSession.AuthKeyID) if (authKeyId != _dcSession.authKeyID)
throw new WTException($"Received a packet encrypted with unexpected key {authKeyId:X}"); throw new WTException($"Received a packet encrypted with unexpected key {authKeyId:X}");
if (authKeyId == 0) // Unencrypted message if (authKeyId == 0) // Unencrypted message
{ {
@ -468,7 +470,7 @@ namespace WTelegram
if (length < 0 || length % 4 != 0) throw new WTException($"Invalid message_data_length: {length}"); if (length < 0 || length % 4 != 0) throw new WTException($"Invalid message_data_length: {length}");
if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new WTException($"Invalid message padding length: {decrypted_data.Length - 32}-{length}"); if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new WTException($"Invalid message padding length: {decrypted_data.Length - 32}-{length}");
if (sessionId != _dcSession.Id) throw new WTException($"Unexpected session ID: {sessionId} != {_dcSession.Id}"); if (sessionId != _dcSession.id) throw new WTException($"Unexpected session ID: {sessionId} != {_dcSession.id}");
if ((msgId & 1) == 0) throw new WTException($"msg_id is not odd: {msgId}"); if ((msgId & 1) == 0) throw new WTException($"msg_id is not odd: {msgId}");
if (!_dcSession.CheckNewMsgId(msgId)) if (!_dcSession.CheckNewMsgId(msgId))
{ {
@ -477,19 +479,20 @@ namespace WTelegram
} }
var utcNow = DateTime.UtcNow; var utcNow = DateTime.UtcNow;
if (_lastRecvMsgId == 0) // resync ServerTicksOffset on first message if (_lastRecvMsgId == 0) // resync ServerTicksOffset on first message
_dcSession.ServerTicksOffset = (msgId >> 32) * 10000000 - utcNow.Ticks + 621355968000000000L; _dcSession.serverTicksOffset = (msgId >> 32) * 10000000 - utcNow.Ticks + 621355968000000000L;
var msgStamp = MsgIdToStamp(_lastRecvMsgId = msgId); var msgStamp = MsgIdToStamp(_lastRecvMsgId = msgId);
long deltaTicks = (msgStamp - utcNow).Ticks; long deltaTicks = (msgStamp - utcNow).Ticks;
if (deltaTicks is > 0) if (deltaTicks is > 0)
if (deltaTicks < Ticks5Secs) // resync if next message is less than 5 seconds in the future if (deltaTicks < Ticks5Secs) // resync if next message is less than 5 seconds in the future
_dcSession.ServerTicksOffset += deltaTicks; _dcSession.serverTicksOffset += deltaTicks;
else if (_dcSession.ServerTicksOffset < -Ticks5Secs && deltaTicks + _dcSession.ServerTicksOffset < 0) else if (_dcSession.serverTicksOffset < -Ticks5Secs && deltaTicks + _dcSession.serverTicksOffset < 0)
_dcSession.ServerTicksOffset += deltaTicks; _dcSession.serverTicksOffset += deltaTicks;
if (serverSalt != _dcSession.Salt && serverSalt != _dcSession.OldSalt && serverSalt != _dcSession.Salts?.Values.ElementAtOrDefault(1)) if (serverSalt != _dcSession.Salt && serverSalt != _dcSession.OldSalt && serverSalt != _dcSession.Salts?.Values.ElementAtOrDefault(1))
{ {
Helpers.Log(3, $"{_dcSession.DcID}>Server salt has changed: {_dcSession.Salt:X} -> {serverSalt:X}"); Helpers.Log(3, $"{_dcSession.DcID}>Server salt has changed: {_dcSession.Salt:X} -> {serverSalt:X}");
_dcSession.OldSalt = _dcSession.Salt; _dcSession.OldSalt = _dcSession.Salt;
_dcSession.Salt = serverSalt; _dcSession.Salt = serverSalt;
lock (_session) _session.Save();
if (++_saltChangeCounter >= 10) if (++_saltChangeCounter >= 10)
throw new WTException("Server salt changed too often! Security issue?"); throw new WTException("Server salt changed too often! Security issue?");
CheckSalt(); CheckSalt();
@ -499,7 +502,7 @@ namespace WTelegram
var ctorNb = reader.ReadUInt32(); var ctorNb = reader.ReadUInt32();
if (ctorNb != Layer.BadMsgCtor && deltaTicks / TimeSpan.TicksPerSecond is > 30 or < -300) if (ctorNb != Layer.BadMsgCtor && deltaTicks / TimeSpan.TicksPerSecond is > 30 or < -300)
{ // msg_id values that belong over 30 seconds in the future or over 300 seconds in the past are to be ignored. { // msg_id values that belong over 30 seconds in the future or over 300 seconds in the past are to be ignored.
Helpers.Log(1, $"{_dcSession.DcID}>Ignoring 0x{ctorNb:X8} because of wrong timestamp {msgStamp:u} - {utcNow:u} Δ={new TimeSpan(_dcSession.ServerTicksOffset):c}"); Helpers.Log(1, $"{_dcSession.DcID}>Ignoring 0x{ctorNb:X8} because of wrong timestamp {msgStamp:u} - {utcNow:u} Δ={new TimeSpan(_dcSession.serverTicksOffset):c}");
return null; return null;
} }
try try
@ -546,9 +549,11 @@ namespace WTelegram
{ {
var keys = _dcSession.Salts.Keys; var keys = _dcSession.Salts.Keys;
if (keys[^1] == DateTime.MaxValue) return; // GetFutureSalts ongoing if (keys[^1] == DateTime.MaxValue) return; // GetFutureSalts ongoing
var now = DateTime.UtcNow.AddTicks(_dcSession.ServerTicksOffset); var now = DateTime.UtcNow.AddTicks(_dcSession.serverTicksOffset);
for (; keys.Count > 1 && keys[1] < now; _dcSession.OldSalt = _dcSession.Salt, _dcSession.Salt = _dcSession.Salts.Values[0]) bool removed = false;
for (; keys.Count > 1 && keys[1] < now; _dcSession.OldSalt = _dcSession.Salt, _dcSession.Salt = _dcSession.Salts.Values[0], removed = true)
_dcSession.Salts.RemoveAt(0); _dcSession.Salts.RemoveAt(0);
if (removed) _session.Save();
if (_dcSession.Salts.Count > 48) return; if (_dcSession.Salts.Count > 48) return;
} }
_dcSession.Salts[DateTime.MaxValue] = 0; _dcSession.Salts[DateTime.MaxValue] = 0;
@ -694,7 +699,7 @@ namespace WTelegram
rpc.tcs.SetResult(obj); rpc.tcs.SetResult(obj);
return; return;
} }
else if (_dcSession.AuthKeyID == 0) else if (_dcSession.authKeyID == 0)
throw new WTException($"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}"); throw new WTException($"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}");
lock (_pendingRpcs) lock (_pendingRpcs)
_pendingRpcs[_bareRpc.msgId] = _bareRpc; _pendingRpcs[_bareRpc.msgId] = _bareRpc;
@ -726,7 +731,7 @@ namespace WTelegram
break; // we don't do anything with these, for now break; // we don't do anything with these, for now
case BadMsgNotification badMsgNotification: case BadMsgNotification badMsgNotification:
await _sendSemaphore.WaitAsync(); await _sendSemaphore.WaitAsync();
bool retryLast = badMsgNotification.bad_msg_id == _dcSession.LastSentMsgId; bool retryLast = badMsgNotification.bad_msg_id == _dcSession.lastSentMsgId;
var lastSentMsg = _lastSentMsg; var lastSentMsg = _lastSentMsg;
_sendSemaphore.Release(); _sendSemaphore.Release();
var logLevel = badMsgNotification.error_code == 48 ? 2 : 4; var logLevel = badMsgNotification.error_code == 48 ? 2 : 4;
@ -735,14 +740,14 @@ namespace WTelegram
{ {
case 16: // msg_id too low (most likely, client time is wrong; synchronize it using msg_id notifications and re-send the original message) case 16: // msg_id too low (most likely, client time is wrong; synchronize it using msg_id notifications and re-send the original message)
case 17: // msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id) case 17: // msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)
_dcSession.LastSentMsgId = 0; _dcSession.lastSentMsgId = 0;
var localTime = DateTime.UtcNow; var localTime = DateTime.UtcNow;
_dcSession.ServerTicksOffset = (_lastRecvMsgId >> 32) * 10000000 - localTime.Ticks + 621355968000000000L; _dcSession.serverTicksOffset = (_lastRecvMsgId >> 32) * 10000000 - localTime.Ticks + 621355968000000000L;
Helpers.Log(1, $"Time offset: {_dcSession.ServerTicksOffset} | Server: {MsgIdToStamp(_lastRecvMsgId).AddTicks(_dcSession.ServerTicksOffset).TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC"); Helpers.Log(1, $"Time offset: {_dcSession.serverTicksOffset} | Server: {MsgIdToStamp(_lastRecvMsgId).AddTicks(_dcSession.serverTicksOffset).TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC");
break; break;
case 32: // msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno) case 32: // msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)
case 33: // msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno) case 33: // msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)
if (_dcSession.Seqno <= 1) if (_dcSession.seqno <= 1)
retryLast = false; retryLast = false;
else else
{ {
@ -754,6 +759,7 @@ namespace WTelegram
case 48: // incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it) case 48: // incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)
_dcSession.OldSalt = _dcSession.Salt; _dcSession.OldSalt = _dcSession.Salt;
_dcSession.Salt = ((BadServerSalt)badMsgNotification).new_server_salt; _dcSession.Salt = ((BadServerSalt)badMsgNotification).new_server_salt;
lock (_session) _session.Save();
CheckSalt(); CheckSalt();
break; break;
default: default:
@ -942,7 +948,7 @@ namespace WTelegram
// is it address for a known DCSession? // is it address for a known DCSession?
_dcSession = _session.DCSessions.Values.FirstOrDefault(dcs => dcs.EndPoint.Equals(endpoint)); _dcSession = _session.DCSessions.Values.FirstOrDefault(dcs => dcs.EndPoint.Equals(endpoint));
if (defaultDc != 0) _dcSession ??= _session.DCSessions.GetValueOrDefault(defaultDc); if (defaultDc != 0) _dcSession ??= _session.DCSessions.GetValueOrDefault(defaultDc);
_dcSession ??= new() { Id = Helpers.RandomLong() }; _dcSession ??= new();
_dcSession.Client = this; _dcSession.Client = this;
_dcSession.DataCenter = null; _dcSession.DataCenter = null;
Helpers.Log(2, $"Connecting to {endpoint}..."); Helpers.Log(2, $"Connecting to {endpoint}...");
@ -976,7 +982,7 @@ namespace WTelegram
try try
{ {
if (_dcSession.AuthKeyID == 0) if (_dcSession.authKeyID == 0)
await CreateAuthorizationKey(this, _dcSession); await CreateAuthorizationKey(this, _dcSession);
if (_networkStream != null) _ = KeepAlive(_cts.Token); if (_networkStream != null) _ = KeepAlive(_cts.Token);
@ -1125,6 +1131,7 @@ namespace WTelegram
if (self.id == long.Parse(botToken.Split(':')[0])) if (self.id == long.Parse(botToken.Split(':')[0]))
{ {
_session.UserId = _dcSession.UserId = self.id; _session.UserId = _dcSession.UserId = self.id;
lock (_session) _session.Save();
RaiseUpdates(self); RaiseUpdates(self);
return User = self; return User = self;
} }
@ -1164,6 +1171,7 @@ namespace WTelegram
self.phone == string.Concat((phone_number = Config("phone_number")).Where(char.IsDigit))) self.phone == string.Concat((phone_number = Config("phone_number")).Where(char.IsDigit)))
{ {
_session.UserId = _dcSession.UserId = self.id; _session.UserId = _dcSession.UserId = self.id;
lock (_session) _session.Save();
RaiseUpdates(self); RaiseUpdates(self);
return User = self; return User = self;
} }
@ -1420,21 +1428,17 @@ namespace WTelegram
internal (long msgId, int seqno) NewMsgId(bool isContent) internal (long msgId, int seqno) NewMsgId(bool isContent)
{ {
int seqno; int seqno;
long msgId = DateTime.UtcNow.Ticks + _dcSession.ServerTicksOffset - 621355968000000000L; long msgId = DateTime.UtcNow.Ticks + _dcSession.serverTicksOffset - 621355968000000000L;
msgId = msgId * 428 + (msgId >> 24) * 25110956; // approximately unixtime*2^32 and divisible by 4 msgId = msgId * 428 + (msgId >> 24) * 25110956; // approximately unixtime*2^32 and divisible by 4
lock (_session) if (msgId <= _dcSession.lastSentMsgId) msgId = _dcSession.lastSentMsgId += 4; else _dcSession.lastSentMsgId = msgId;
{ seqno = isContent ? _dcSession.seqno++ * 2 + 1 : _dcSession.seqno * 2;
if (msgId <= _dcSession.LastSentMsgId) msgId = _dcSession.LastSentMsgId += 4; else _dcSession.LastSentMsgId = msgId;
seqno = isContent ? _dcSession.Seqno++ * 2 + 1 : _dcSession.Seqno * 2;
_session.Save();
}
return (msgId, seqno); return (msgId, seqno);
} }
private async Task SendAsync(IObject msg, bool isContent, Rpc rpc = null) private async Task SendAsync(IObject msg, bool isContent, Rpc rpc = null)
{ {
if (_reactorTask == null) throw new WTException("You must connect to Telegram first"); if (_reactorTask == null) throw new WTException("You must connect to Telegram first");
isContent &= _dcSession.AuthKeyID != 0; isContent &= _dcSession.authKeyID != 0;
var (msgId, seqno) = NewMsgId(isContent); var (msgId, seqno) = NewMsgId(isContent);
if (rpc != null) if (rpc != null)
lock (_pendingRpcs) lock (_pendingRpcs)
@ -1462,7 +1466,7 @@ namespace WTelegram
using var writer = new BinaryWriter(memStream); using var writer = new BinaryWriter(memStream);
writer.Write(0); // int32 payload_len (to be patched with payload length) writer.Write(0); // int32 payload_len (to be patched with payload length)
if (_dcSession.AuthKeyID == 0) // send unencrypted message if (_dcSession.authKeyID == 0) // send unencrypted message
{ {
if (_bareRpc == null) throw new WTException($"Shouldn't send a {msg.GetType().Name} unencrypted"); if (_bareRpc == null) throw new WTException($"Shouldn't send a {msg.GetType().Name} unencrypted");
writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted) writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted)
@ -1479,7 +1483,7 @@ namespace WTelegram
using var clearWriter = new BinaryWriter(clearStream); using var clearWriter = new BinaryWriter(clearStream);
clearWriter.Write(_dcSession.AuthKey, 88, 32); clearWriter.Write(_dcSession.AuthKey, 88, 32);
clearWriter.Write(_dcSession.Salt); // int64 salt clearWriter.Write(_dcSession.Salt); // int64 salt
clearWriter.Write(_dcSession.Id); // int64 session_id clearWriter.Write(_dcSession.id); // int64 session_id
clearWriter.Write(msgId); // int64 message_id clearWriter.Write(msgId); // int64 message_id
clearWriter.Write(seqno); // int32 msg_seqno clearWriter.Write(seqno); // int32 msg_seqno
clearWriter.Write(0); // int32 message_data_length (to be patched) clearWriter.Write(0); // int32 message_data_length (to be patched)
@ -1499,13 +1503,13 @@ namespace WTelegram
const int msgKeyOffset = 8; // msg_key = middle 128-bits of SHA256(authkey_part+plaintext+padding) const int msgKeyOffset = 8; // msg_key = middle 128-bits of SHA256(authkey_part+plaintext+padding)
byte[] encrypted_data = EncryptDecryptMessage(clearBuffer.AsSpan(32, clearLength + padding), true, 0, _dcSession.AuthKey, msgKeyLarge, msgKeyOffset, _sha256); byte[] encrypted_data = EncryptDecryptMessage(clearBuffer.AsSpan(32, clearLength + padding), true, 0, _dcSession.AuthKey, msgKeyLarge, msgKeyOffset, _sha256);
writer.Write(_dcSession.AuthKeyID); // int64 auth_key_id writer.Write(_dcSession.authKeyID); // int64 auth_key_id
writer.Write(msgKeyLarge, msgKeyOffset, 16); // int128 msg_key writer.Write(msgKeyLarge, msgKeyOffset, 16); // int128 msg_key
writer.Write(encrypted_data); // bytes encrypted_data writer.Write(encrypted_data); // bytes encrypted_data
} }
if (_paddedMode) // Padded intermediate mode => append random padding if (_paddedMode) // Padded intermediate mode => append random padding
{ {
var padding = new byte[_random.Next(_dcSession.AuthKeyID == 0 ? 257 : 16)]; var padding = new byte[_random.Next(_dcSession.authKeyID == 0 ? 257 : 16)];
RNG.GetBytes(padding); RNG.GetBytes(padding);
writer.Write(padding); writer.Write(padding);
} }
@ -1572,7 +1576,7 @@ namespace WTelegram
/// <returns>Wait for the reply and return the resulting object, or throws an RpcException if an error was replied</returns> /// <returns>Wait for the reply and return the resulting object, or throws an RpcException if an error was replied</returns>
public async Task<T> Invoke<T>(IMethod<T> query) public async Task<T> Invoke<T>(IMethod<T> query)
{ {
if (_dcSession.WithoutUpdates && query is not IMethod<Pong> and not IMethod<FutureSalts>) if (_dcSession.withoutUpdates && query is not IMethod<Pong> and not IMethod<FutureSalts>)
query = new TL.Methods.InvokeWithoutUpdates<T> { query = query }; query = new TL.Methods.InvokeWithoutUpdates<T> { query = query };
bool got503 = false; bool got503 = false;
retry: retry:
@ -1610,7 +1614,7 @@ namespace WTelegram
{ {
if (x <= FloodRetryThreshold) if (x <= FloodRetryThreshold)
{ {
if (x == 0) x =1; if (x == 0) x = 1;
await Task.Delay(x * 1000); await Task.Delay(x * 1000);
goto retry; goto retry;
} }
@ -1623,14 +1627,16 @@ namespace WTelegram
else if (code == 400 && message == "CONNECTION_NOT_INITED") else if (code == 400 && message == "CONNECTION_NOT_INITED")
{ {
await InitConnection(); await InitConnection();
lock (_session) _session.Save();
goto retry; goto retry;
} }
else if (code == 500 && message == "AUTH_RESTART") else if (code == 500 && message == "AUTH_RESTART")
{ lock (_session)
_session.UserId = 0; // force a full login authorization flow, next time {
User = null; _session.UserId = 0; // force a full login authorization flow, next time
lock (_session) _session.Save(); User = null;
} _session.Save();
}
throw new RpcException(code, message, x); throw new RpcException(code, message, x);
case ReactorError: case ReactorError:
goto retry; goto retry;

View file

@ -110,9 +110,9 @@ namespace WTelegram
var g_a = BigEndianInteger(serverDHinnerData.g_a); var g_a = BigEndianInteger(serverDHinnerData.g_a);
var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime); var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime);
CheckGoodPrime(dh_prime, serverDHinnerData.g); CheckGoodPrime(dh_prime, serverDHinnerData.g);
session.LastSentMsgId = 0; session.lastSentMsgId = 0;
session.ServerTicksOffset = (serverDHinnerData.server_time - localTime).Ticks; session.serverTicksOffset = (serverDHinnerData.server_time - localTime).Ticks;
Helpers.Log(1, $"Time offset: {session.ServerTicksOffset} | Server: {serverDHinnerData.server_time.TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC"); Helpers.Log(1, $"Time offset: {session.serverTicksOffset} | Server: {serverDHinnerData.server_time.TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC");
//6) //6)
var salt = new byte[256]; var salt = new byte[256];
RNG.GetBytes(salt); RNG.GetBytes(salt);
@ -159,7 +159,7 @@ namespace WTelegram
if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, sha1.ComputeHash(expected_new_nonceN).Skip(4))) if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, sha1.ComputeHash(expected_new_nonceN).Skip(4)))
throw new WTException("setClientDHparamsAnswer.new_nonce_hashN mismatch"); throw new WTException("setClientDHparamsAnswer.new_nonce_hashN mismatch");
session.AuthKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12)); session.authKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12));
session.AuthKey = authKey; session.AuthKey = authKey;
session.Salt = BinaryPrimitives.ReadInt64LittleEndian(pqInnerData.new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw); session.Salt = BinaryPrimitives.ReadInt64LittleEndian(pqInnerData.new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw);
session.OldSalt = session.Salt; session.OldSalt = session.Salt;

View file

@ -21,25 +21,25 @@ namespace WTelegram
public sealed class DCSession public sealed class DCSession
{ {
public long Id;
public long AuthKeyID;
public byte[] AuthKey; // 2048-bit = 256 bytes public byte[] AuthKey; // 2048-bit = 256 bytes
public long UserId; public long UserId;
public long OldSalt; // still accepted for a further 1800 seconds public long OldSalt; // still accepted for a further 1800 seconds
public long Salt; public long Salt;
public SortedList<DateTime, long> Salts; public SortedList<DateTime, long> Salts;
public int Seqno;
public long ServerTicksOffset;
public long LastSentMsgId;
public TL.DcOption DataCenter; public TL.DcOption DataCenter;
public bool WithoutUpdates;
public int Layer; public int Layer;
internal long id = Helpers.RandomLong();
internal long authKeyID;
internal int seqno;
internal long serverTicksOffset;
internal long lastSentMsgId;
internal bool withoutUpdates;
internal Client Client; internal Client Client;
internal int DcID => DataCenter?.id ?? 0; internal int DcID => DataCenter?.id ?? 0;
internal IPEndPoint EndPoint => DataCenter == null ? null : new(IPAddress.Parse(DataCenter.ip_address), DataCenter.port); internal IPEndPoint EndPoint => DataCenter == null ? null : new(IPAddress.Parse(DataCenter.ip_address), DataCenter.port);
internal void Renew() { Helpers.Log(3, $"Renewing session on DC {DcID}..."); Id = Helpers.RandomLong(); Seqno = 0; LastSentMsgId = 0; } internal void Renew() { Helpers.Log(3, $"Renewing session on DC {DcID}..."); id = Helpers.RandomLong(); seqno = 0; lastSentMsgId = 0; }
public void DisableUpdates(bool disable = true) { if (WithoutUpdates != disable) { WithoutUpdates = disable; Renew(); } } public void DisableUpdates(bool disable = true) { if (withoutUpdates != disable) { withoutUpdates = disable; Renew(); } }
const int MsgIdsN = 512; const int MsgIdsN = 512;
private long[] _msgIds; private long[] _msgIds;
@ -117,6 +117,9 @@ namespace WTelegram
throw new WTException("Integrity check failed in session loading"); throw new WTException("Integrity check failed in session loading");
session = JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions); session = JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions);
Helpers.Log(2, "Loaded previous session"); Helpers.Log(2, "Loaded previous session");
using var sha1 = SHA1.Create();
foreach (var dcs in session.DCSessions.Values)
dcs.authKeyID = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(dcs.AuthKey).AsSpan(12));
} }
session ??= new Session(); session ??= new Session();
session._store = store; session._store = store;