TLSharp/TLSharp.Core/Network/MtProtoSender.cs
Andres G. Aragoneses c644bac4e4 Fix typo in string of exception message
This was meant to start with $, not including
the $ char inside the string, otherwise the
compiler string interpolation doesn't work.
2016-10-24 14:57:04 +08:00

496 lines
20 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;
}
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);
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);
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);
throw new MigrationNeededException(dcIdx);
}
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)
{
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);
}
}
internal class MigrationNeededException : Exception
{
internal int DC { get; private set; }
internal MigrationNeededException(int dc)
: base ($"Your phone number is registered to a different DC: {dc}. Please migrate.")
{
DC = dc;
}
}
}