2021-08-04 00:40:09 +02:00
using System ;
2022-01-23 01:28:10 +01:00
using System.Buffers.Binary ;
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 ;
namespace WTelegram
{
2022-01-27 17:34:16 +01:00
internal class Session : IDisposable
2021-08-04 00:40:09 +02:00
{
2022-01-25 16:37:09 +01:00
public int ApiId ;
2021-11-07 09:09:15 +01:00
public long UserId ;
2021-09-23 09:27:52 +02:00
public int MainDC ;
public Dictionary < int , DCSession > DCSessions = new ( ) ;
2021-10-01 02:22:26 +02:00
public TL . DcOption [ ] DcOptions ;
2021-09-23 09:27:52 +02:00
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 ;
2023-05-02 22:49:38 +02:00
public SortedList < DateTime , long > Salts ;
2021-09-23 09:27:52 +02:00
public int Seqno ;
public long ServerTicksOffset ;
public long LastSentMsgId ;
public TL . DcOption DataCenter ;
2022-07-12 01:31:18 +02:00
public bool WithoutUpdates ;
2021-09-23 09:27:52 +02:00
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-12-04 00:14:15 +01:00
internal void Renew ( ) { Helpers . Log ( 3 , $"Renewing session on DC {DcID}..." ) ; Id = Helpers . RandomLong ( ) ; Seqno = 0 ; LastSentMsgId = 0 ; }
2022-07-12 01:31:18 +02:00
public void DisableUpdates ( bool disable = true ) { if ( WithoutUpdates ! = disable ) { WithoutUpdates = disable ; Renew ( ) ; } }
2023-07-08 01:34:31 +02:00
const int MsgIdsN = 512 ;
private long [ ] _msgIds ;
private int _msgIdsHead ;
2022-05-19 01:32:22 +02:00
internal bool CheckNewMsgId ( long msg_id )
{
2023-07-08 01:34:31 +02:00
if ( _msgIds = = null )
2022-05-19 01:32:22 +02:00
{
2023-07-08 01:34:31 +02:00
_msgIds = new long [ MsgIdsN ] ;
_msgIds [ 0 ] = msg_id ;
2022-07-01 12:18:13 +02:00
msg_id - = 300L < < 32 ; // until the array is filled with real values, allow ids up to 300 seconds in the past
2023-07-08 01:34:31 +02:00
for ( int i = 1 ; i < MsgIdsN ; i + + ) _msgIds [ i ] = msg_id ;
2022-05-19 01:32:22 +02:00
return true ;
}
2023-07-08 01:34:31 +02:00
int newHead = ( _msgIdsHead + 1 ) % MsgIdsN ;
if ( msg_id > _msgIds [ _msgIdsHead ] )
_msgIds [ _msgIdsHead = newHead ] = msg_id ;
else if ( msg_id < = _msgIds [ newHead ] )
2022-05-19 01:32:22 +02:00
return false ;
else
{
2023-07-08 01:34:31 +02:00
int min = 0 , max = MsgIdsN - 1 ;
2022-05-19 01:32:22 +02:00
while ( min < = max ) // binary search (rotated at newHead)
{
int mid = ( min + max ) / 2 ;
2023-07-08 01:34:31 +02:00
int sign = msg_id . CompareTo ( _msgIds [ ( mid + newHead ) % MsgIdsN ] ) ;
2022-05-19 01:32:22 +02:00
if ( sign = = 0 ) return false ;
else if ( sign < 0 ) max = mid - 1 ;
else min = mid + 1 ;
}
2023-07-08 01:34:31 +02:00
_msgIdsHead = newHead ;
for ( min = ( min + newHead ) % MsgIdsN ; newHead ! = min ; )
_msgIds [ newHead ] = _msgIds [ newHead = newHead = = 0 ? MsgIdsN - 1 : newHead - 1 ] ;
_msgIds [ min ] = msg_id ;
2022-05-19 01:32:22 +02:00
}
return true ;
}
2021-09-23 09:27:52 +02:00
}
2021-08-04 00:40:09 +02:00
public DateTime SessionStart = > _sessionStart ;
private readonly DateTime _sessionStart = DateTime . UtcNow ;
2021-09-29 04:38:39 +02:00
private readonly SHA256 _sha256 = SHA256 . Create ( ) ;
2022-01-26 22:00:52 +01:00
private Stream _store ;
2022-02-04 02:51:14 +01:00
private byte [ ] _reuseKey ; // used only if AES Encryptor.CanReuseTransform = false (Mono)
2022-01-27 17:34:16 +01:00
private byte [ ] _encrypted = new byte [ 16 ] ;
private ICryptoTransform _encryptor ;
private Utf8JsonWriter _jsonWriter ;
private readonly MemoryStream _jsonStream = new ( 4096 ) ;
public void Dispose ( )
{
_sha256 . Dispose ( ) ;
_store . Dispose ( ) ;
_encryptor . Dispose ( ) ;
_jsonWriter . Dispose ( ) ;
_jsonStream . Dispose ( ) ;
}
2021-08-04 00:40:09 +02:00
2022-01-26 22:00:52 +01:00
internal static Session LoadOrCreate ( Stream store , byte [ ] rgbKey )
2021-08-04 00:40:09 +02:00
{
2022-01-27 17:34:16 +01:00
using var aes = Aes . Create ( ) ;
Session session = null ;
2022-01-26 11:08:33 +01:00
try
2021-08-04 00:40:09 +02:00
{
2022-01-26 22:00:52 +01:00
var length = ( int ) store . Length ;
if ( length > 0 )
2021-08-04 00:40:09 +02:00
{
2022-01-23 01:28:10 +01:00
var input = new byte [ length ] ;
2022-01-26 22:00:52 +01:00
if ( store . Read ( input , 0 , length ) ! = length )
2023-04-02 13:44:23 +02:00
throw new WTException ( $"Can't read session block ({store.Position}, {length})" ) ;
2022-01-27 17:34:16 +01:00
using var sha256 = SHA256 . Create ( ) ;
using var decryptor = aes . CreateDecryptor ( rgbKey , input [ 0. . 16 ] ) ;
var utf8Json = decryptor . TransformFinalBlock ( input , 16 , input . Length - 16 ) ;
if ( ! sha256 . ComputeHash ( utf8Json , 32 , utf8Json . Length - 32 ) . SequenceEqual ( utf8Json [ 0. . 32 ] ) )
2023-04-02 13:44:23 +02:00
throw new WTException ( "Integrity check failed in session loading" ) ;
2022-01-27 17:34:16 +01:00
session = JsonSerializer . Deserialize < Session > ( utf8Json . AsSpan ( 32 ) , Helpers . JsonOptions ) ;
2021-08-04 10:11:07 +02:00
Helpers . Log ( 2 , "Loaded previous session" ) ;
2021-08-04 00:40:09 +02:00
}
2023-02-26 17:09:45 +01:00
session ? ? = new Session ( ) ;
session . _store = store ;
Encryption . RNG . GetBytes ( session . _encrypted , 0 , 16 ) ;
session . _encryptor = aes . CreateEncryptor ( rgbKey , session . _encrypted ) ;
if ( ! session . _encryptor . CanReuseTransform ) session . _reuseKey = rgbKey ;
session . _jsonWriter = new Utf8JsonWriter ( session . _jsonStream , default ) ;
return session ;
2022-01-26 11:08:33 +01:00
}
catch ( Exception ex )
{
2022-01-26 22:00:52 +01:00
store . Dispose ( ) ;
2023-04-02 13:44:23 +02:00
throw new WTException ( $"Exception while reading session file: {ex.Message}\nUse the correct api_hash/id/key, or delete the file to start a new session" , ex ) ;
2021-08-04 00:40:09 +02:00
}
2021-08-06 07:28:54 +02:00
}
2022-01-27 17:34:16 +01:00
internal void Save ( ) // must be called with lock(session)
2021-08-06 07:28:54 +02:00
{
2022-01-27 17:34:16 +01:00
JsonSerializer . Serialize ( _jsonWriter , this , Helpers . JsonOptions ) ;
var utf8Json = _jsonStream . GetBuffer ( ) ;
var utf8JsonLen = ( int ) _jsonStream . Position ;
int encryptedLen = 64 + ( utf8JsonLen & ~ 15 ) ;
2022-03-20 13:09:25 +01:00
lock ( _store ) // while updating _encrypted buffer and writing to store
{
if ( encryptedLen > _encrypted . Length )
Array . Copy ( _encrypted , _encrypted = new byte [ encryptedLen + 256 ] , 16 ) ;
_encryptor . TransformBlock ( _sha256 . ComputeHash ( utf8Json , 0 , utf8JsonLen ) , 0 , 32 , _encrypted , 16 ) ;
_encryptor . TransformBlock ( utf8Json , 0 , encryptedLen - 64 , _encrypted , 48 ) ;
_encryptor . TransformFinalBlock ( utf8Json , encryptedLen - 64 , utf8JsonLen & 15 ) . CopyTo ( _encrypted , encryptedLen - 16 ) ;
if ( ! _encryptor . CanReuseTransform ) // under Mono, AES encryptor is not reusable
using ( var aes = Aes . Create ( ) )
_encryptor = aes . CreateEncryptor ( _reuseKey , _encrypted [ 0. . 16 ] ) ;
_store . Position = 0 ;
_store . Write ( _encrypted , 0 , encryptedLen ) ;
_store . SetLength ( encryptedLen ) ;
}
2022-01-27 17:34:16 +01:00
_jsonStream . Position = 0 ;
_jsonWriter . Reset ( ) ;
2021-08-04 00:40:09 +02:00
}
}
2022-01-26 22:00:52 +01:00
internal class SessionStore : FileStream
{
public override long Length { get ; }
public override long Position { get = > base . Position ; set { } }
public override void SetLength ( long value ) { }
2022-01-27 17:34:16 +01:00
private readonly byte [ ] _header = new byte [ 8 ] ;
private int _nextPosition = 8 ;
2022-01-26 22:00:52 +01:00
public SessionStore ( string pathname )
2022-02-24 16:44:27 +01:00
: base ( pathname , FileMode . OpenOrCreate , FileAccess . ReadWrite , FileShare . None , 1 ) // no in-app buffering
2022-01-26 22:00:52 +01:00
{
if ( base . Read ( _header , 0 , 8 ) = = 8 )
{
var position = BinaryPrimitives . ReadInt32LittleEndian ( _header ) ;
var length = BinaryPrimitives . ReadInt32LittleEndian ( _header . AsSpan ( 4 ) ) ;
base . Position = position ;
Length = length ;
_nextPosition = position + length ;
}
}
public override void Write ( byte [ ] buffer , int offset , int count )
{
if ( _nextPosition > count * 3 ) _nextPosition = 8 ;
base . Position = _nextPosition ;
base . Write ( buffer , offset , count ) ;
BinaryPrimitives . WriteInt32LittleEndian ( _header , _nextPosition ) ;
BinaryPrimitives . WriteInt32LittleEndian ( _header . AsSpan ( 4 ) , count ) ;
_nextPosition + = count ;
base . Position = 0 ;
base . Write ( _header , 0 , 8 ) ;
}
}
2021-08-04 00:40:09 +02:00
}