Switch to MTProto Intermediate transport protocol (more lightweight/adequate for TCP)

This commit is contained in:
Wizou 2021-09-17 03:44:52 +02:00
parent a403a462db
commit 661b5223ac
2 changed files with 13 additions and 33 deletions

View file

@ -15,6 +15,9 @@ using System.Threading.Tasks;
using TL; using TL;
using static WTelegram.Encryption; using static WTelegram.Encryption;
// necessary for .NET Standard 2.0 compilation:
#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
namespace WTelegram namespace WTelegram
{ {
public sealed partial class Client : IDisposable public sealed partial class Client : IDisposable
@ -27,9 +30,9 @@ namespace WTelegram
private readonly int _apiId; private readonly int _apiId;
private readonly string _apiHash; private readonly string _apiHash;
private readonly Session _session; private readonly Session _session;
private static readonly byte[] IntermediateHeader = new byte[4] { 0xee, 0xee, 0xee, 0xee };
private TcpClient _tcpClient; private TcpClient _tcpClient;
private NetworkStream _networkStream; private NetworkStream _networkStream;
private int _frame_seqTx = 0, _frame_seqRx = 0;
private ITLFunction _lastSentMsg; private ITLFunction _lastSentMsg;
private long _lastRecvMsgId; private long _lastRecvMsgId;
private readonly List<long> _msgsToAck = new(); private readonly List<long> _msgsToAck = new();
@ -119,7 +122,7 @@ namespace WTelegram
_tcpClient = new TcpClient(endpoint.AddressFamily); _tcpClient = new TcpClient(endpoint.AddressFamily);
await _tcpClient.ConnectAsync(endpoint.Address, endpoint.Port); await _tcpClient.ConnectAsync(endpoint.Address, endpoint.Port);
_networkStream = _tcpClient.GetStream(); _networkStream = _tcpClient.GetStream();
_frame_seqTx = _frame_seqRx = 0; await _networkStream.WriteAsync(IntermediateHeader, 0, 4);
_cts = new(); _cts = new();
_reactorTask = Reactor(_networkStream, _cts); _reactorTask = Reactor(_networkStream, _cts);
_sendSemaphore.Release(); _sendSemaphore.Release();
@ -316,33 +319,20 @@ namespace WTelegram
} }
private async Task<byte[]> RecvFrameAsync(NetworkStream stream, CancellationToken ct) private static async Task<byte[]> RecvFrameAsync(NetworkStream stream, CancellationToken ct)
{ {
byte[] frame = new byte[8]; byte[] overhead = new byte[4];
if (await FullReadAsync(stream, frame, 8, ct) != 8) if (await FullReadAsync(stream, overhead, 4, ct) != 4)
throw new ApplicationException("Could not read frame prefix : Connection shut down"); throw new ApplicationException("Could not read payload length : Connection shut down");
int length = BinaryPrimitives.ReadInt32LittleEndian(frame) - 12; int length = BinaryPrimitives.ReadInt32LittleEndian(overhead);
if (length <= 0 || length >= 0x10000) if (length <= 0 || length >= 0x10000)
throw new ApplicationException("Invalid frame_len"); throw new ApplicationException("Invalid frame_len");
int seqno = BinaryPrimitives.ReadInt32LittleEndian(frame.AsSpan(4));
if (seqno != _frame_seqRx++)
{
Trace.TraceWarning($"Unexpected frame_seq received: {seqno} instead of {_frame_seqRx}");
_frame_seqRx = seqno + 1;
}
var payload = new byte[length]; var payload = new byte[length];
if (await FullReadAsync(stream, payload, length, ct) != length) if (await FullReadAsync(stream, payload, length, ct) != length)
throw new ApplicationException("Could not read frame data : Connection shut down"); throw new ApplicationException("Could not read frame data : Connection shut down");
uint crc32 = Compat.UpdateCrc32(0, frame, 0, 8);
crc32 = Compat.UpdateCrc32(crc32, payload, 0, payload.Length);
if (await FullReadAsync(stream, frame, 4, ct) != 4)
throw new ApplicationException("Could not read frame CRC : Connection shut down");
if (crc32 != BinaryPrimitives.ReadUInt32LittleEndian(frame))
throw new ApplicationException("Invalid envelope CRC32");
return payload; return payload;
} }
#pragma warning disable CA1835 // necessary for .NET Standard 2.0 compilation
private static async Task<int> FullReadAsync(Stream stream, byte[] buffer, int length, CancellationToken ct = default) private static async Task<int> FullReadAsync(Stream stream, byte[] buffer, int length, CancellationToken ct = default)
{ {
for (int offset = 0; offset != length;) for (int offset = 0; offset != length;)
@ -369,8 +359,7 @@ namespace WTelegram
{ {
using var memStream = new MemoryStream(1024); using var memStream = new MemoryStream(1024);
using var writer = new BinaryWriter(memStream, Encoding.UTF8); using var writer = new BinaryWriter(memStream, Encoding.UTF8);
writer.Write(0); // int32 frame_len (to be patched with full frame length) writer.Write(0); // int32 payload_len (to be patched with payload length)
writer.Write(_frame_seqTx++); // int32 frame_seq
if (_session.AuthKeyID == 0) // send unencrypted message if (_session.AuthKeyID == 0) // send unencrypted message
{ {
@ -425,12 +414,10 @@ namespace WTelegram
} }
var buffer = memStream.GetBuffer(); var buffer = memStream.GetBuffer();
int frameLength = (int)memStream.Length; int frameLength = (int)memStream.Length;
BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength + 4); // patch frame_len with correct value BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength - 4); // patch payload_len with correct value
uint crc = Compat.UpdateCrc32(0, buffer, 0, frameLength);
writer.Write(crc); // int32 frame_crc
//TODO: support Transport obfuscation? //TODO: support Transport obfuscation?
await _networkStream.WriteAsync(memStream.GetBuffer(), 0, frameLength + 4); await _networkStream.WriteAsync(memStream.GetBuffer(), 0, frameLength);
_lastSentMsg = func; _lastSentMsg = func;
} }
finally finally
@ -439,7 +426,6 @@ namespace WTelegram
} }
return msgId; return msgId;
} }
#pragma warning restore CA1835
private static ITLFunction MakeFunction(ITLObject msg) private static ITLFunction MakeFunction(ITLObject msg)
=> writer => => writer =>

View file

@ -9,12 +9,6 @@ namespace WTelegram
{ {
static class Compat static class Compat
{ {
// see also https://github.com/dotnet/runtime/issues/2036 and https://github.com/dotnet/runtime/pull/53623
internal static readonly Func<uint, byte[], int, int, uint> UpdateCrc32 = (Func<uint, byte[], int, int, uint>)
typeof(System.IO.Compression.ZipArchive).Assembly.GetType("System.IO.Compression.Crc32Helper")
.GetMethod("UpdateCrc32", new[] { typeof(uint), typeof(byte[]), typeof(int), typeof(int) })
.CreateDelegate(typeof(Func<uint, byte[], int, int, uint>));
#if NETCOREAPP2_1_OR_GREATER #if NETCOREAPP2_1_OR_GREATER
internal static IPEndPoint IPEndPoint_Parse(string addr) => IPEndPoint.Parse(addr); internal static IPEndPoint IPEndPoint_Parse(string addr) => IPEndPoint.Parse(addr);
internal static BigInteger BigEndianInteger(byte[] value) => new(value, true, true); internal static BigInteger BigEndianInteger(byte[] value) => new(value, true, true);