mirror of
https://github.com/sochix/TLSharp.git
synced 2025-12-06 08:02:00 +01:00
Now we catch not only PhoneMigrateException but all DataCenterMigrationExceptions and reconnect Fixes #568
589 lines
23 KiB
C#
589 lines
23 KiB
C#
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
|
|
{
|
|
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;
|
|
}
|
|
|
|
public async Task Send(TeleSharp.TL.TLMethod request)
|
|
{
|
|
// TODO: refactor
|
|
if (needConfirmation.Any())
|
|
{
|
|
var ackRequest = new AckRequest(needConfirmation);
|
|
using (var memory = new MemoryStream())
|
|
using (var writer = new BinaryWriter(memory))
|
|
{
|
|
ackRequest.SerializeBody(writer);
|
|
await Send(memory.ToArray(), ackRequest);
|
|
needConfirmation.Clear();
|
|
}
|
|
}
|
|
|
|
|
|
using (var memory = new MemoryStream())
|
|
using (var writer = new BinaryWriter(memory))
|
|
{
|
|
request.SerializeBody(writer);
|
|
await Send(memory.ToArray(), request);
|
|
}
|
|
|
|
_session.Save();
|
|
}
|
|
|
|
public async Task Send(byte[] packet, TeleSharp.TL.TLMethod request)
|
|
{
|
|
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());
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
public async Task<byte[]> Receive(TeleSharp.TL.TLMethod request)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public async Task SendPingAsync()
|
|
{
|
|
var pingRequest = new PingRequest();
|
|
using (var memory = new MemoryStream())
|
|
using (var writer = new BinaryWriter(memory))
|
|
{
|
|
pingRequest.SerializeBody(writer);
|
|
await Send(memory.ToArray(), pingRequest);
|
|
}
|
|
|
|
await Receive(pingRequest);
|
|
}
|
|
|
|
private bool processMessage(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
|
|
{
|
|
// 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, request);
|
|
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;
|
|
}
|
|
}
|
|
|
|
private bool HandleUpdate(ulong messageId, int sequence, BinaryReader messageReader)
|
|
{
|
|
return false;
|
|
|
|
/*
|
|
try
|
|
{
|
|
UpdatesEvent(TL.Parse<Updates>(messageReader));
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.warning("update processing exception: {0}", e);
|
|
return false;
|
|
}
|
|
*/
|
|
}
|
|
|
|
private bool HandleGzipPacked(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
|
|
{
|
|
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);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool HandleRpcResult(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
|
|
{
|
|
uint code = messageReader.ReadUInt32();
|
|
ulong requestId = messageReader.ReadUInt64();
|
|
|
|
if (requestId == (ulong)request.MessageId)
|
|
request.ConfirmReceived = true;
|
|
|
|
//throw new NotImplementedException();
|
|
/*
|
|
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);
|
|
}
|
|
*/
|
|
|
|
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);
|
|
throw new FloodException(TimeSpan.FromSeconds(seconds));
|
|
}
|
|
else if (errorMessage.StartsWith("PHONE_MIGRATE_"))
|
|
{
|
|
var resultString = Regex.Match(errorMessage, @"\d+").Value;
|
|
var dcIdx = int.Parse(resultString);
|
|
throw new PhoneMigrationException(dcIdx);
|
|
}
|
|
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);
|
|
}
|
|
else if (errorMessage.StartsWith("NETWORK_MIGRATE_"))
|
|
{
|
|
var resultString = Regex.Match(errorMessage, @"\d+").Value;
|
|
var dcIdx = int.Parse(resultString);
|
|
throw new NetworkMigrationException(dcIdx);
|
|
}
|
|
else if (errorMessage == "PHONE_CODE_INVALID")
|
|
{
|
|
throw new InvalidPhoneCodeException("The numeric code used to authenticate does not match the numeric code sent by SMS/Telegram");
|
|
}
|
|
else if (errorMessage == "SESSION_PASSWORD_NEEDED")
|
|
{
|
|
throw new CloudPasswordNeededException("This Account has Cloud Password !");
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException(errorMessage);
|
|
}
|
|
|
|
}
|
|
else if (innerCode == 0x3072cfa1)
|
|
{
|
|
try
|
|
{
|
|
// gzip_packed
|
|
byte[] packedData = Serializers.Bytes.read(messageReader);
|
|
using (var ms = new MemoryStream())
|
|
{
|
|
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))
|
|
{
|
|
request.deserializeResponse(compressedReader);
|
|
}
|
|
}
|
|
}
|
|
catch (ZlibException ex)
|
|
{
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
messageReader.BaseStream.Position -= 4;
|
|
request.deserializeResponse(messageReader);
|
|
}
|
|
|
|
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:
|
|
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)");
|
|
case 17:
|
|
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)");
|
|
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");
|
|
|
|
}
|
|
throw new NotImplementedException("This should never happens");
|
|
/*
|
|
logger.debug("bad_msg_notification: msgid {0}, seq {1}, errorcode {2}", requestId, requestSequence,
|
|
errorCode);
|
|
*/
|
|
/*
|
|
if (!runningRequests.ContainsKey(requestId))
|
|
{
|
|
logger.debug("bad msg notification on unknown request");
|
|
return true;
|
|
}
|
|
*/
|
|
|
|
//OnBrokenSessionEvent();
|
|
//MTProtoRequest request = runningRequests[requestId];
|
|
//request.OnException(new MTProtoBadMessageException(errorCode));
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool HandleBadServerSalt(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
|
|
{
|
|
uint code = messageReader.ReadUInt32();
|
|
ulong badMsgId = messageReader.ReadUInt64();
|
|
int badMsgSeqNo = messageReader.ReadInt32();
|
|
int errorCode = messageReader.ReadInt32();
|
|
ulong newSalt = messageReader.ReadUInt64();
|
|
|
|
//logger.debug("bad_server_salt: msgid {0}, seq {1}, errorcode {2}, newsalt {3}", badMsgId, badMsgSeqNo, errorCode, newSalt);
|
|
|
|
_session.Salt = newSalt;
|
|
|
|
//resend
|
|
Send(request);
|
|
/*
|
|
if(!runningRequests.ContainsKey(badMsgId)) {
|
|
logger.debug("bad server salt on unknown message");
|
|
return true;
|
|
}
|
|
*/
|
|
|
|
|
|
//MTProtoRequest request = runningRequests[badMsgId];
|
|
//request.OnException(new MTProtoBadServerSaltException(salt));
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool HandleMsgsAck(ulong messageId, int sequence, BinaryReader messageReader)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
private bool HandleNewSessionCreated(ulong messageId, int sequence, BinaryReader messageReader)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
private bool HandleFutureSalts(ulong messageId, int sequence, BinaryReader messageReader)
|
|
{
|
|
uint code = messageReader.ReadUInt32();
|
|
ulong requestId = messageReader.ReadUInt64();
|
|
|
|
messageReader.BaseStream.Position -= 12;
|
|
|
|
throw new NotImplementedException("Handle future server salts function isn't implemented.");
|
|
/*
|
|
if (!runningRequests.ContainsKey(requestId))
|
|
{
|
|
logger.info("future salts on unknown request");
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
// MTProtoRequest request = runningRequests[requestId];
|
|
// runningRequests.Remove(requestId);
|
|
// request.OnResponse(messageReader);
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool HandlePong(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
|
|
{
|
|
uint code = messageReader.ReadUInt32();
|
|
ulong msgId = messageReader.ReadUInt64();
|
|
|
|
if (msgId == (ulong)request.MessageId)
|
|
{
|
|
request.ConfirmReceived = true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool HandlePing(ulong messageId, int sequence, BinaryReader messageReader)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
private bool HandleContainer(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private MemoryStream makeMemory(int len)
|
|
{
|
|
return new MemoryStream(new byte[len], 0, len, true, true);
|
|
}
|
|
}
|
|
|
|
public class FloodException : Exception
|
|
{
|
|
public TimeSpan TimeToWait { get; private set; }
|
|
|
|
internal FloodException(TimeSpan timeToWait)
|
|
: base($"Flood prevention. Telegram now requires your program to do requests again only after {timeToWait.TotalSeconds} seconds have passed ({nameof(TimeToWait)} property)." +
|
|
" If you think the culprit of this problem may lie in TLSharp's implementation, open a Github issue please.")
|
|
{
|
|
TimeToWait = timeToWait;
|
|
}
|
|
}
|
|
|
|
internal abstract class DataCenterMigrationException : Exception
|
|
{
|
|
internal int DC { get; private set; }
|
|
|
|
private const string REPORT_MESSAGE =
|
|
" See: https://github.com/sochix/TLSharp#i-get-a-xxxmigrationexception-or-a-migrate_x-error";
|
|
|
|
protected DataCenterMigrationException(string msg, int dc) : base (msg + REPORT_MESSAGE)
|
|
{
|
|
DC = dc;
|
|
}
|
|
}
|
|
|
|
internal class PhoneMigrationException : DataCenterMigrationException
|
|
{
|
|
internal PhoneMigrationException(int dc)
|
|
: base ($"Phone number registered to a different DC: {dc}.", dc)
|
|
{
|
|
}
|
|
}
|
|
|
|
internal class FileMigrationException : DataCenterMigrationException
|
|
{
|
|
internal FileMigrationException(int dc)
|
|
: base ($"File located on a different DC: {dc}.", dc)
|
|
{
|
|
}
|
|
}
|
|
|
|
internal class UserMigrationException : DataCenterMigrationException
|
|
{
|
|
internal UserMigrationException(int dc)
|
|
: base($"User located on a different DC: {dc}.", dc)
|
|
{
|
|
}
|
|
}
|
|
|
|
internal class NetworkMigrationException : DataCenterMigrationException
|
|
{
|
|
internal NetworkMigrationException(int dc)
|
|
: base($"Network located on a different DC: {dc}.", dc)
|
|
{
|
|
}
|
|
}
|
|
}
|