2015-09-28 04:01:17 +02:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Text.RegularExpressions ;
using System.Threading ;
using System.Threading.Tasks ;
using Ionic.Zlib ;
using TLSharp.Core.MTProto ;
using TLSharp.Core.MTProto.Crypto ;
using TLSharp.Core.Requests ;
using TLSharp.Core.Utils ;
namespace TLSharp.Core.Network
{
2016-04-18 12:50:57 +02:00
public class MtProtoSender
{
//private ulong sessionId = GenerateRandomUlong();
private TcpTransport _transport ;
private Session _session ;
public List < ulong > needConfirmation = new List < ulong > ( ) ;
public MtProtoSender ( TcpTransport transport , Session session )
{
_transport = transport ;
_session = session ;
}
public void ChangeTransport ( TcpTransport transport )
{
_transport = transport ;
}
private int GenerateSequence ( bool confirmed )
{
return confirmed ? _session . Sequence + + * 2 + 1 : _session . Sequence * 2 ;
}
2016-09-24 15:38:26 +02:00
public async Task Send ( TeleSharp . TL . TLMethod request )
2016-04-18 12:50:57 +02:00
{
// TODO: refactor
if ( needConfirmation . Any ( ) )
{
var ackRequest = new AckRequest ( needConfirmation ) ;
using ( var memory = new MemoryStream ( ) )
using ( var writer = new BinaryWriter ( memory ) )
{
2016-09-24 15:38:26 +02:00
ackRequest . SerializeBody ( writer ) ;
2016-04-18 12:50:57 +02:00
await Send ( memory . ToArray ( ) , ackRequest ) ;
needConfirmation . Clear ( ) ;
}
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
using ( var memory = new MemoryStream ( ) )
using ( var writer = new BinaryWriter ( memory ) )
{
2016-09-24 15:38:26 +02:00
request . SerializeBody ( writer ) ;
2016-04-18 12:50:57 +02:00
await Send ( memory . ToArray ( ) , request ) ;
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
_session . Save ( ) ;
}
2016-09-24 15:38:26 +02:00
public async Task Send ( byte [ ] packet , TeleSharp . TL . TLMethod request )
2016-04-18 12:50:57 +02:00
{
request . MessageId = _session . GetNewMessageId ( ) ;
byte [ ] msgKey ;
byte [ ] ciphertext ;
using ( MemoryStream plaintextPacket = makeMemory ( 8 + 8 + 8 + 4 + 4 + packet . Length ) )
{
using ( BinaryWriter plaintextWriter = new BinaryWriter ( plaintextPacket ) )
{
plaintextWriter . Write ( _session . Salt ) ;
plaintextWriter . Write ( _session . Id ) ;
plaintextWriter . Write ( request . MessageId ) ;
plaintextWriter . Write ( GenerateSequence ( request . Confirmed ) ) ;
plaintextWriter . Write ( packet . Length ) ;
plaintextWriter . Write ( packet ) ;
msgKey = Helpers . CalcMsgKey ( plaintextPacket . GetBuffer ( ) ) ;
ciphertext = AES . EncryptAES ( Helpers . CalcKey ( _session . AuthKey . Data , msgKey , true ) , plaintextPacket . GetBuffer ( ) ) ;
}
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
using ( MemoryStream ciphertextPacket = makeMemory ( 8 + 16 + ciphertext . Length ) )
{
using ( BinaryWriter writer = new BinaryWriter ( ciphertextPacket ) )
{
writer . Write ( _session . AuthKey . Id ) ;
writer . Write ( msgKey ) ;
writer . Write ( ciphertext ) ;
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
await _transport . Send ( ciphertextPacket . GetBuffer ( ) ) ;
}
}
}
private Tuple < byte [ ] , ulong , int > DecodeMessage ( byte [ ] body )
{
byte [ ] message ;
ulong remoteMessageId ;
int remoteSequence ;
using ( var inputStream = new MemoryStream ( body ) )
using ( var inputReader = new BinaryReader ( inputStream ) )
{
if ( inputReader . BaseStream . Length < 8 )
throw new InvalidOperationException ( $"Can't decode packet" ) ;
ulong remoteAuthKeyId = inputReader . ReadUInt64 ( ) ; // TODO: check auth key id
byte [ ] msgKey = inputReader . ReadBytes ( 16 ) ; // TODO: check msg_key correctness
AESKeyData keyData = Helpers . CalcKey ( _session . AuthKey . Data , msgKey , false ) ;
byte [ ] plaintext = AES . DecryptAES ( keyData , inputReader . ReadBytes ( ( int ) ( inputStream . Length - inputStream . Position ) ) ) ;
using ( MemoryStream plaintextStream = new MemoryStream ( plaintext ) )
using ( BinaryReader plaintextReader = new BinaryReader ( plaintextStream ) )
{
var remoteSalt = plaintextReader . ReadUInt64 ( ) ;
var remoteSessionId = plaintextReader . ReadUInt64 ( ) ;
remoteMessageId = plaintextReader . ReadUInt64 ( ) ;
remoteSequence = plaintextReader . ReadInt32 ( ) ;
int msgLen = plaintextReader . ReadInt32 ( ) ;
message = plaintextReader . ReadBytes ( msgLen ) ;
}
}
return new Tuple < byte [ ] , ulong , int > ( message , remoteMessageId , remoteSequence ) ;
}
2016-09-24 15:38:26 +02:00
public async Task < byte [ ] > Receive ( TeleSharp . TL . TLMethod request )
2016-04-18 12:50:57 +02:00
{
while ( ! request . ConfirmReceived )
{
var result = DecodeMessage ( ( await _transport . Receieve ( ) ) . Body ) ;
using ( var messageStream = new MemoryStream ( result . Item1 , false ) )
using ( var messageReader = new BinaryReader ( messageStream ) )
{
processMessage ( result . Item2 , result . Item3 , messageReader , request ) ;
}
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
return null ;
}
2016-09-24 15:38:26 +02:00
private bool processMessage ( ulong messageId , int sequence , BinaryReader messageReader , TeleSharp . TL . TLMethod request )
2016-04-18 12:50:57 +02:00
{
// TODO: check salt
// TODO: check sessionid
// TODO: check seqno
//logger.debug("processMessage: msg_id {0}, sequence {1}, data {2}", BitConverter.ToString(((MemoryStream)messageReader.BaseStream).GetBuffer(), (int) messageReader.BaseStream.Position, (int) (messageReader.BaseStream.Length - messageReader.BaseStream.Position)).Replace("-","").ToLower());
needConfirmation . Add ( messageId ) ;
uint code = messageReader . ReadUInt32 ( ) ;
messageReader . BaseStream . Position - = 4 ;
switch ( code )
{
case 0x73f1f8dc : // container
//logger.debug("MSG container");
return HandleContainer ( messageId , sequence , messageReader , request ) ;
case 0x7abe77ec : // ping
//logger.debug("MSG ping");
return HandlePing ( messageId , sequence , messageReader ) ;
case 0x347773c5 : // pong
//logger.debug("MSG pong");
return HandlePong ( messageId , sequence , messageReader ) ;
case 0xae500895 : // future_salts
//logger.debug("MSG future_salts");
return HandleFutureSalts ( messageId , sequence , messageReader ) ;
case 0x9ec20908 : // new_session_created
//logger.debug("MSG new_session_created");
return HandleNewSessionCreated ( messageId , sequence , messageReader ) ;
case 0x62d6b459 : // msgs_ack
//logger.debug("MSG msds_ack");
return HandleMsgsAck ( messageId , sequence , messageReader ) ;
case 0xedab447b : // bad_server_salt
//logger.debug("MSG bad_server_salt");
return HandleBadServerSalt ( messageId , sequence , messageReader , request ) ;
case 0xa7eff811 : // bad_msg_notification
//logger.debug("MSG bad_msg_notification");
return HandleBadMsgNotification ( messageId , sequence , messageReader ) ;
case 0x276d3ec6 : // msg_detailed_info
//logger.debug("MSG msg_detailed_info");
return HandleMsgDetailedInfo ( messageId , sequence , messageReader ) ;
case 0xf35c6d01 : // rpc_result
//logger.debug("MSG rpc_result");
return HandleRpcResult ( messageId , sequence , messageReader , request ) ;
case 0x3072cfa1 : // gzip_packed
//logger.debug("MSG gzip_packed");
return HandleGzipPacked ( messageId , sequence , messageReader , request ) ;
case 0xe317af7e :
case 0xd3f45784 :
case 0x2b2fbd4e :
case 0x78d4dec1 :
case 0x725b04c3 :
case 0x74ae4240 :
return HandleUpdate ( messageId , sequence , messageReader ) ;
default :
//logger.debug("unknown message: {0}", code);
return false ;
}
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
private bool HandleUpdate ( ulong messageId , int sequence , BinaryReader messageReader )
{
return false ;
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
/ *
2015-09-28 04:01:17 +02:00
try
{
UpdatesEvent ( TL . Parse < Updates > ( messageReader ) ) ;
return true ;
}
catch ( Exception e )
{
logger . warning ( "update processing exception: {0}" , e ) ;
return false ;
}
* /
2016-04-18 12:50:57 +02:00
}
2016-09-24 15:38:26 +02:00
private bool HandleGzipPacked ( ulong messageId , int sequence , BinaryReader messageReader , TeleSharp . TL . TLMethod request )
2016-04-18 12:50:57 +02:00
{
uint code = messageReader . ReadUInt32 ( ) ;
byte [ ] packedData = GZipStream . UncompressBuffer ( Serializers . Bytes . read ( messageReader ) ) ;
using ( MemoryStream packedStream = new MemoryStream ( packedData , false ) )
using ( BinaryReader compressedReader = new BinaryReader ( packedStream ) )
{
processMessage ( messageId , sequence , compressedReader , request ) ;
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
return true ;
}
2015-09-28 04:01:17 +02:00
2016-09-24 15:38:26 +02:00
private bool HandleRpcResult ( ulong messageId , int sequence , BinaryReader messageReader , TeleSharp . TL . TLMethod request )
2016-04-18 12:50:57 +02:00
{
uint code = messageReader . ReadUInt32 ( ) ;
ulong requestId = messageReader . ReadUInt64 ( ) ;
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
if ( requestId = = ( ulong ) request . MessageId )
request . ConfirmReceived = true ;
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
//throw new NotImplementedException();
/ *
2015-09-28 04:01:17 +02:00
lock ( runningRequests )
{
if ( ! runningRequests . ContainsKey ( requestId ) )
{
logger . warning ( "rpc response on unknown request: {0}" , requestId ) ;
messageReader . BaseStream . Position - = 12 ;
return false ;
}
request = runningRequests [ requestId ] ;
runningRequests . Remove ( requestId ) ;
}
* /
2016-04-18 12:50:57 +02:00
uint innerCode = messageReader . ReadUInt32 ( ) ;
if ( innerCode = = 0x2144ca19 )
{ // rpc_error
int errorCode = messageReader . ReadInt32 ( ) ;
string errorMessage = Serializers . String . read ( messageReader ) ;
if ( errorMessage . StartsWith ( "FLOOD_WAIT_" ) )
{
var resultString = Regex . Match ( errorMessage , @"\d+" ) . Value ;
var seconds = int . Parse ( resultString ) ;
Debug . WriteLine ( $"Should wait {seconds} sec." ) ;
Thread . Sleep ( 1000 * seconds ) ;
}
else if ( errorMessage . StartsWith ( "PHONE_MIGRATE_" ) )
{
var resultString = Regex . Match ( errorMessage , @"\d+" ) . Value ;
var dcIdx = int . Parse ( resultString ) ;
2016-10-22 16:00:15 +02:00
throw new MigrationNeededException ( dcIdx ) ;
2016-04-18 12:50:57 +02:00
}
2016-10-29 10:47:18 +02:00
else if ( errorMessage . StartsWith ( "FILE_MIGRATE_" ) )
{
var resultString = Regex . Match ( errorMessage , @"\d+" ) . Value ;
var dcIdx = int . Parse ( resultString ) ;
throw new FileMigrationException ( dcIdx ) ;
}
else if ( errorMessage . StartsWith ( "USER_MIGRATE_" ) )
{
var resultString = Regex . Match ( errorMessage , @"\d+" ) . Value ;
var dcIdx = int . Parse ( resultString ) ;
throw new UserMigrationException ( dcIdx ) ;
}
2016-04-18 12:50:57 +02:00
else
{
throw new InvalidOperationException ( errorMessage ) ;
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
}
else if ( innerCode = = 0x3072cfa1 )
{
try
{
// gzip_packed
byte [ ] packedData = Serializers . Bytes . read ( messageReader ) ;
2016-07-19 15:47:08 +02:00
using ( var ms = new MemoryStream ( ) )
2016-04-18 12:50:57 +02:00
{
2016-07-19 15:47:08 +02:00
using ( var packedStream = new MemoryStream ( packedData , false ) )
using ( var zipStream = new GZipStream ( packedStream , CompressionMode . Decompress ) )
{
zipStream . CopyTo ( ms ) ;
ms . Position = 0 ;
}
using ( var compressedReader = new BinaryReader ( ms ) )
{
2016-09-24 15:38:26 +02:00
request . deserializeResponse ( compressedReader ) ;
2016-07-19 15:47:08 +02:00
}
2016-04-18 12:50:57 +02:00
}
}
catch ( ZlibException ex )
{
}
}
else
{
messageReader . BaseStream . Position - = 4 ;
2016-09-24 15:38:26 +02:00
request . deserializeResponse ( messageReader ) ;
2016-04-18 12:50:57 +02:00
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
return false ;
}
private bool HandleMsgDetailedInfo ( ulong messageId , int sequence , BinaryReader messageReader )
{
return false ;
}
private bool HandleBadMsgNotification ( ulong messageId , int sequence , BinaryReader messageReader )
{
uint code = messageReader . ReadUInt32 ( ) ;
ulong requestId = messageReader . ReadUInt64 ( ) ;
int requestSequence = messageReader . ReadInt32 ( ) ;
int errorCode = messageReader . ReadInt32 ( ) ;
switch ( errorCode )
{
case 16 :
2016-10-23 10:17:59 +02:00
throw new InvalidOperationException ( "msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original message had waited too long on the client to be transmitted)" ) ;
2016-04-18 12:50:57 +02:00
case 17 :
2016-10-23 10:17:59 +02:00
throw new InvalidOperationException ( "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)" ) ;
2016-04-18 12:50:57 +02:00
case 18 :
throw new InvalidOperationException ( "incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)" ) ;
case 19 :
throw new InvalidOperationException ( "container msg_id is the same as msg_id of a previously received message (this must never happen)" ) ;
case 20 :
throw new InvalidOperationException ( "message too old, and it cannot be verified whether the server has received a message with this msg_id or not" ) ;
case 32 :
throw new InvalidOperationException ( "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 :
throw new InvalidOperationException ( " 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 34 :
throw new InvalidOperationException ( "an even msg_seqno expected (irrelevant message), but odd received" ) ;
case 35 :
throw new InvalidOperationException ( "odd msg_seqno expected (relevant message), but even received" ) ;
case 48 :
throw new InvalidOperationException ( "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 64 :
throw new InvalidOperationException ( "invalid container" ) ;
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
}
throw new NotImplementedException ( "This should never happens" ) ;
/ *
2015-09-28 04:01:17 +02:00
logger . debug ( "bad_msg_notification: msgid {0}, seq {1}, errorcode {2}" , requestId , requestSequence ,
errorCode ) ;
* /
2016-04-18 12:50:57 +02:00
/ *
2015-09-28 04:01:17 +02:00
if ( ! runningRequests . ContainsKey ( requestId ) )
{
logger . debug ( "bad msg notification on unknown request" ) ;
return true ;
}
* /
2016-04-18 12:50:57 +02:00
//OnBrokenSessionEvent();
//MTProtoRequest request = runningRequests[requestId];
//request.OnException(new MTProtoBadMessageException(errorCode));
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
return true ;
}
2015-09-28 04:01:17 +02:00
2016-09-24 15:38:26 +02:00
private bool HandleBadServerSalt ( ulong messageId , int sequence , BinaryReader messageReader , TeleSharp . TL . TLMethod request )
2016-04-18 12:50:57 +02:00
{
uint code = messageReader . ReadUInt32 ( ) ;
ulong badMsgId = messageReader . ReadUInt64 ( ) ;
int badMsgSeqNo = messageReader . ReadInt32 ( ) ;
int errorCode = messageReader . ReadInt32 ( ) ;
ulong newSalt = messageReader . ReadUInt64 ( ) ;
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
//logger.debug("bad_server_salt: msgid {0}, seq {1}, errorcode {2}, newsalt {3}", badMsgId, badMsgSeqNo, errorCode, newSalt);
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
_session . Salt = newSalt ;
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
//resend
Send ( request ) ;
/ *
2015-09-28 04:01:17 +02:00
if ( ! runningRequests . ContainsKey ( badMsgId ) ) {
logger . debug ( "bad server salt on unknown message" ) ;
return true ;
}
* /
2016-04-18 12:50:57 +02:00
//MTProtoRequest request = runningRequests[badMsgId];
//request.OnException(new MTProtoBadServerSaltException(salt));
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
return true ;
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
private bool HandleMsgsAck ( ulong messageId , int sequence , BinaryReader messageReader )
{
return false ;
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
private bool HandleNewSessionCreated ( ulong messageId , int sequence , BinaryReader messageReader )
{
return false ;
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
private bool HandleFutureSalts ( ulong messageId , int sequence , BinaryReader messageReader )
{
uint code = messageReader . ReadUInt32 ( ) ;
ulong requestId = messageReader . ReadUInt64 ( ) ;
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
messageReader . BaseStream . Position - = 12 ;
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
throw new NotImplementedException ( "Handle future server salts function isn't implemented." ) ;
/ *
2015-09-28 04:01:17 +02:00
if ( ! runningRequests . ContainsKey ( requestId ) )
{
logger . info ( "future salts on unknown request" ) ;
return false ;
}
* /
2016-04-18 12:50:57 +02:00
// MTProtoRequest request = runningRequests[requestId];
// runningRequests.Remove(requestId);
// request.OnResponse(messageReader);
return true ;
}
private bool HandlePong ( ulong messageId , int sequence , BinaryReader messageReader )
{
return false ;
}
private bool HandlePing ( ulong messageId , int sequence , BinaryReader messageReader )
{
return false ;
}
2016-09-24 15:38:26 +02:00
private bool HandleContainer ( ulong messageId , int sequence , BinaryReader messageReader , TeleSharp . TL . TLMethod request )
2016-04-18 12:50:57 +02:00
{
uint code = messageReader . ReadUInt32 ( ) ;
int size = messageReader . ReadInt32 ( ) ;
for ( int i = 0 ; i < size ; i + + )
{
ulong innerMessageId = messageReader . ReadUInt64 ( ) ;
int innerSequence = messageReader . ReadInt32 ( ) ;
int innerLength = messageReader . ReadInt32 ( ) ;
long beginPosition = messageReader . BaseStream . Position ;
try
{
if ( ! processMessage ( innerMessageId , sequence , messageReader , request ) )
{
messageReader . BaseStream . Position = beginPosition + innerLength ;
}
}
catch ( Exception e )
{
// logger.error("failed to process message in contailer: {0}", e);
messageReader . BaseStream . Position = beginPosition + innerLength ;
}
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
return false ;
}
2015-09-28 04:01:17 +02:00
2016-04-18 12:50:57 +02:00
private MemoryStream makeMemory ( int len )
{
return new MemoryStream ( new byte [ len ] , 0 , len , true , true ) ;
}
}
2016-10-22 16:00:15 +02:00
internal class MigrationNeededException : Exception
{
internal int DC { get ; private set ; }
internal MigrationNeededException ( int dc )
2016-10-24 08:57:04 +02:00
: base ( $"Your phone number is registered to a different DC: {dc}. Please migrate." )
2016-10-22 16:00:15 +02:00
{
DC = dc ;
}
}
2016-10-29 10:47:18 +02:00
internal class FileMigrationException : Exception
{
internal int DC { get ; private set ; }
internal FileMigrationException ( int dc )
: base ( $"File is located on a different DC: {dc}. Please migrate." )
{
DC = dc ;
}
}
internal class UserMigrationException : Exception
{
internal int DC { get ; private set ; }
internal UserMigrationException ( int dc )
: base ( $"User is located on a different DC: {dc}. Please migrate." )
{
DC = dc ;
}
}
2015-09-28 04:01:17 +02:00
}