mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2026-01-11 11:09:56 +01:00
TL Functions are now direct Client methods ; Renamed/rationalized serialization methods
This commit is contained in:
parent
8ae3c2a283
commit
674130e079
|
|
@ -1,5 +1,5 @@
|
|||
[](https://badge.fury.io/nu/WTelegramClient)
|
||||
[](https://dev.azure.com/wiz0u/WTelegramClient/_build/latest?definitionId=7&branchName=master)
|
||||
[](https://dev.azure.com/wiz0u/WTelegramClient/_packaging?_a=package&feed=WTelegramClient&package=WTelegramClient&protocolType=NuGet)
|
||||
|
||||
# WTelegramClient
|
||||
### _Telegram client library written 100% in C# and .NET Core_
|
||||
|
|
@ -108,7 +108,7 @@ Here are the main expected developments:
|
|||
- [x] Support SignUp of unregistered users
|
||||
- [x] Improve code Generator (import of TL-schema JSONs)
|
||||
- [x] Nuget deployment & public CI feed
|
||||
- [ ] Convert API functions classes to real methods and serialize structures without using Reflection
|
||||
- [x] Convert API functions classes to real methods and serialize structures without using Reflection
|
||||
- [ ] Separate task/thread for reading/handling update messages independently from CallAsync
|
||||
- [ ] Support MTProto 2.0
|
||||
- [ ] Support users with 2FA enabled
|
||||
|
|
|
|||
181
src/Client.cs
181
src/Client.cs
|
|
@ -16,7 +16,7 @@ using static WTelegram.Encryption;
|
|||
|
||||
namespace WTelegram
|
||||
{
|
||||
public sealed class Client : IDisposable
|
||||
public sealed partial class Client : IDisposable
|
||||
{
|
||||
public Config TLConfig { get; private set; }
|
||||
private readonly Func<string, string> _config;
|
||||
|
|
@ -27,7 +27,7 @@ namespace WTelegram
|
|||
private TcpClient _tcpClient;
|
||||
private NetworkStream _networkStream;
|
||||
private int _frame_seqTx = 0, _frame_seqRx = 0;
|
||||
private ITLObject _lastSentMsg;
|
||||
private ITLFunction<object> _lastSentMsg;
|
||||
private Type _lastRpcResultType = typeof(object);
|
||||
private readonly List<long> _msgsToAck = new();
|
||||
private int _unexpectedSaltChange;
|
||||
|
|
@ -93,21 +93,15 @@ namespace WTelegram
|
|||
if (_session.AuthKey == null)
|
||||
await CreateAuthorizationKey(this, _session);
|
||||
|
||||
TLConfig = await CallAsync(new Fn.InvokeWithLayer<Config>
|
||||
{
|
||||
layer = Schema.Layer,
|
||||
query = new Fn.InitConnection<Config>
|
||||
{
|
||||
api_id = _apiId,
|
||||
device_model = Config("device_model"),
|
||||
system_version = Config("system_version"),
|
||||
app_version = Config("app_version"),
|
||||
system_lang_code = Config("system_lang_code"),
|
||||
lang_pack = Config("lang_pack"),
|
||||
lang_code = Config("lang_code"),
|
||||
query = new Fn.Help_GetConfig()
|
||||
}
|
||||
});
|
||||
TLConfig = await InvokeWithLayer(Schema.Layer,
|
||||
InitConnection<Config>(_apiId,
|
||||
Config("device_model"),
|
||||
Config("system_version"),
|
||||
Config("app_version"),
|
||||
Config("system_lang_code"),
|
||||
Config("lang_pack"),
|
||||
Config("lang_code"),
|
||||
Help_GetConfig));
|
||||
}
|
||||
|
||||
private async Task MigrateDCAsync(int dcId)
|
||||
|
|
@ -115,7 +109,7 @@ namespace WTelegram
|
|||
Helpers.Log(2, $"Migrate to DC {dcId}...");
|
||||
Auth_ExportedAuthorization exported = null;
|
||||
if (_session.User != null)
|
||||
exported = await CallAsync(new Fn.Auth_ExportAuthorization { dc_id = dcId });
|
||||
exported = await Auth_ExportAuthorization(dcId);
|
||||
var prevFamily = _tcpClient.Client.RemoteEndPoint.AddressFamily;
|
||||
_tcpClient.Close();
|
||||
var dcOptions = TLConfig.dc_options.Where(dc => dc.id == dcId && (dc.flags & (DcOption.Flags.media_only | DcOption.Flags.cdn)) == 0);
|
||||
|
|
@ -128,10 +122,10 @@ namespace WTelegram
|
|||
await ConnectAsync();
|
||||
if (exported != null)
|
||||
{
|
||||
var authorization = await CallAsync(new Fn.Auth_ImportAuthorization { id = exported.id, bytes = exported.bytes });
|
||||
var authorization = await Auth_ImportAuthorization(exported.id, exported.bytes);
|
||||
if (authorization is not Auth_Authorization { user: User user })
|
||||
throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name);
|
||||
_session.User = Schema.Serialize(user);
|
||||
_session.User = user.Serialize();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +136,14 @@ namespace WTelegram
|
|||
_tcpClient?.Dispose();
|
||||
}
|
||||
|
||||
public async Task SendAsync(ITLObject msg, bool isContent = true)
|
||||
public Task SendAsync(ITLObject msg, bool isContent = true)
|
||||
=> SendAsync<object>(writer =>
|
||||
{
|
||||
writer.WriteTLObject(msg);
|
||||
return msg.GetType().Name;
|
||||
}, isContent);
|
||||
|
||||
public async Task SendAsync<X>(ITLFunction<X> msgSerializer, bool isContent = true)
|
||||
{
|
||||
if (_session.AuthKeyID != 0) await CheckMsgsToAck();
|
||||
using var memStream = new MemoryStream(1024);
|
||||
|
|
@ -150,20 +151,19 @@ namespace WTelegram
|
|||
writer.Write(0); // int32 frame_len (to be patched with full frame length)
|
||||
writer.Write(_frame_seqTx++); // int32 frame_seq
|
||||
|
||||
(long msgId, int seqno) = _session.NewMsg(isContent);
|
||||
(long msgId, int seqno) = _session.NewMsg(isContent && _session.AuthKeyID != 0);
|
||||
|
||||
if (_session.AuthKeyID == 0) // send unencrypted message
|
||||
{
|
||||
Helpers.Log(1, $"Sending {msg.GetType().Name}...");
|
||||
writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted)
|
||||
writer.Write(msgId); // int64 message_id
|
||||
writer.Write(0); // int32 message_data_length (to be patched)
|
||||
Schema.Serialize(writer, msg); // bytes message_data
|
||||
writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted)
|
||||
writer.Write(msgId); // int64 message_id
|
||||
writer.Write(0); // int32 message_data_length (to be patched)
|
||||
var typeName = msgSerializer(writer); // bytes message_data
|
||||
Helpers.Log(1, $"Sending {typeName}...");
|
||||
BinaryPrimitives.WriteInt32LittleEndian(memStream.GetBuffer().AsSpan(24), (int)memStream.Length - 28); // patch message_data_length
|
||||
}
|
||||
else
|
||||
{
|
||||
Helpers.Log(1, $"Sending {msg.GetType().Name,-50} #{(short)msgId.GetHashCode():X4}");
|
||||
//TODO: implement MTProto 2.0
|
||||
using var clearStream = new MemoryStream(1024);
|
||||
using var clearWriter = new BinaryWriter(clearStream, Encoding.UTF8);
|
||||
|
|
@ -172,7 +172,8 @@ namespace WTelegram
|
|||
clearWriter.Write(msgId); // int64 message_id
|
||||
clearWriter.Write(seqno); // int32 msg_seqno
|
||||
clearWriter.Write(0); // int32 message_data_length (to be patched)
|
||||
Schema.Serialize(clearWriter, msg); // bytes message_data
|
||||
var typeName = msgSerializer(clearWriter); // bytes message_data
|
||||
Helpers.Log(1, $"Sending {typeName,-50} #{(short)msgId.GetHashCode():X4}");
|
||||
int clearLength = (int)clearStream.Length; // length before padding (= 32 + message_data_length)
|
||||
int padding = (0x7FFFFFF0 - clearLength) % 16;
|
||||
clearStream.SetLength(clearLength + padding);
|
||||
|
|
@ -197,7 +198,7 @@ namespace WTelegram
|
|||
//TODO: support Transport obfuscation?
|
||||
|
||||
await _networkStream.WriteAsync(frame);
|
||||
_lastSentMsg = msg;
|
||||
_lastSentMsg = writer => msgSerializer(writer);
|
||||
}
|
||||
|
||||
internal async Task<ITLObject> RecvInternalAsync()
|
||||
|
|
@ -221,11 +222,8 @@ namespace WTelegram
|
|||
int length = reader.ReadInt32();
|
||||
if (length != data.Length - 20) throw new ApplicationException($"Unexpected unencrypted length {length} != {data.Length - 20}");
|
||||
|
||||
var ctorNb = reader.ReadUInt32();
|
||||
if (!Schema.Table.TryGetValue(ctorNb, out var realType))
|
||||
throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}");
|
||||
Helpers.Log(1, $"Receiving {realType.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} unencrypted");
|
||||
return Schema.DeserializeObject(reader, realType);
|
||||
return reader.ReadTLObject((type, _) =>
|
||||
Helpers.Log(1, $"Receiving {type.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} unencrypted"));
|
||||
}
|
||||
else if (authKeyId != _session.AuthKeyID)
|
||||
throw new ApplicationException($"Received a packet encrypted with unexpected key {authKeyId:X}");
|
||||
|
|
@ -255,15 +253,13 @@ namespace WTelegram
|
|||
if (!data.AsSpan(8, 16).SequenceEqual(SHA1.HashData(decrypted_data.AsSpan(0, 32 + length)).AsSpan(4)))
|
||||
throw new ApplicationException($"Mismatch between MsgKey & decrypted SHA1");
|
||||
|
||||
var ctorNb = reader.ReadUInt32();
|
||||
if (!Schema.Table.TryGetValue(ctorNb, out var realType))
|
||||
throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}");
|
||||
Helpers.Log(1, $"Receiving {realType.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} {(seqno == -1 ? "clearText" : "isContent")}={(seqno & 1) != 0}");
|
||||
if (realType == typeof(RpcResult))
|
||||
return DeserializeRpcResult(reader); // necessary hack because some RPC return bare types like bool or int[]
|
||||
else
|
||||
return Schema.DeserializeObject(reader, realType);
|
||||
}
|
||||
return reader.ReadTLObject((type, obj) =>
|
||||
{
|
||||
Helpers.Log(1, $"Receiving {type.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} {(seqno == -1 ? "clearText" : "isContent")}={(seqno & 1) != 0}");
|
||||
if (type == typeof(RpcResult))
|
||||
DeserializeRpcResult(reader, (RpcResult)obj); // necessary hack because some RPC return bare types like bool or int[]
|
||||
});
|
||||
}
|
||||
|
||||
static string TransportError(int error_code) => error_code switch
|
||||
{
|
||||
|
|
@ -310,16 +306,13 @@ namespace WTelegram
|
|||
return length;
|
||||
}
|
||||
|
||||
private RpcResult DeserializeRpcResult(BinaryReader reader)
|
||||
private void DeserializeRpcResult(BinaryReader reader, RpcResult rpcResult)
|
||||
{
|
||||
long reqMsgId = reader.ReadInt64();
|
||||
var rpcResult = new RpcResult { req_msg_id = reqMsgId };
|
||||
if (reqMsgId == _session.LastSentMsgId)
|
||||
rpcResult.result = Schema.DeserializeValue(reader, _lastRpcResultType);
|
||||
if ((rpcResult.req_msg_id = reader.ReadInt64()) == _session.LastSentMsgId)
|
||||
rpcResult.result = reader.ReadTLValue(_lastRpcResultType);
|
||||
else
|
||||
rpcResult.result = Schema.Deserialize<ITLObject>(reader);
|
||||
Helpers.Log(1, $" → {rpcResult.result.GetType().Name,-50} #{(short)reqMsgId.GetHashCode():X4}");
|
||||
return rpcResult;
|
||||
rpcResult.result = reader.ReadTLObject();
|
||||
Helpers.Log(1, $" → {rpcResult.result.GetType().Name,-50} #{(short)rpcResult.req_msg_id.GetHashCode():X4}");
|
||||
}
|
||||
|
||||
public class RpcException : Exception
|
||||
|
|
@ -328,16 +321,17 @@ namespace WTelegram
|
|||
public RpcException(int code, string message) : base(message) => Code = code;
|
||||
}
|
||||
|
||||
public async Task<X> CallAsync<X>(ITLFunction<X> request)
|
||||
public async Task<X> CallAsync<X>(ITLFunction<X> request)
|
||||
{
|
||||
await SendAsync(request);
|
||||
// TODO: create a background reactor system that handles incoming packets and wake up awaiting tasks when their result has arrived
|
||||
// This would allow parallelization of Send task and avoid the risk of calling RecvInternal concurrently
|
||||
_lastRpcResultType = typeof(X);
|
||||
for (; ;)
|
||||
for (; ;) //TODO: implement a timeout
|
||||
{
|
||||
var reply = await RecvInternalAsync();
|
||||
if (reply is RpcResult rpcResult && rpcResult.req_msg_id == _session.LastSentMsgId)
|
||||
if (reply is X plainResult) return plainResult;
|
||||
else if (reply is RpcResult rpcResult && rpcResult.req_msg_id == _session.LastSentMsgId)
|
||||
{
|
||||
if (rpcResult.result is RpcError rpcError)
|
||||
{
|
||||
|
|
@ -409,24 +403,13 @@ namespace WTelegram
|
|||
if (_session.User != null)
|
||||
return Schema.Deserialize<User>(_session.User);
|
||||
string phone_number = Config("phone_number");
|
||||
var sentCode = await CallAsync(new Fn.Auth_SendCode
|
||||
{
|
||||
phone_number = phone_number,
|
||||
api_id = _apiId,
|
||||
api_hash = _apiHash,
|
||||
settings = settings ?? new()
|
||||
});
|
||||
var sentCode = await Auth_SendCode(phone_number, _apiId, _apiHash, settings ?? new());
|
||||
Helpers.Log(3, $"A verification code has been sent via {sentCode.type.GetType().Name[17..]}");
|
||||
var verification_code = Config("verification_code");
|
||||
Auth_AuthorizationBase authorization;
|
||||
try
|
||||
{
|
||||
authorization = await CallAsync(new Fn.Auth_SignIn
|
||||
{
|
||||
phone_number = phone_number,
|
||||
phone_code_hash = sentCode.phone_code_hash,
|
||||
phone_code = verification_code
|
||||
});
|
||||
authorization = await Auth_SignIn(phone_number, sentCode.phone_code_hash, verification_code);
|
||||
}
|
||||
catch (RpcException e) when (e.Code == 400 && e.Message == "SESSION_PASSWORD_NEEDED")
|
||||
{
|
||||
|
|
@ -437,20 +420,15 @@ namespace WTelegram
|
|||
var waitUntil = DateTime.UtcNow.AddSeconds(3);
|
||||
if (signUpRequired.terms_of_service != null && _updateHandler != null)
|
||||
await _updateHandler?.Invoke(signUpRequired.terms_of_service); // give caller the possibility to read and accept TOS
|
||||
var signUp = new Fn.Auth_SignUp
|
||||
{
|
||||
phone_number = phone_number,
|
||||
phone_code_hash = sentCode.phone_code_hash,
|
||||
first_name = Config("first_name"),
|
||||
last_name = Config("last_name"),
|
||||
};
|
||||
var first_name = Config("first_name");
|
||||
var last_name = Config("last_name");
|
||||
var wait = waitUntil - DateTime.UtcNow;
|
||||
if (wait > TimeSpan.Zero) await Task.Delay(wait); // we get a FLOOD_WAIT_3 if we SignUp too fast
|
||||
authorization = await CallAsync(signUp);
|
||||
authorization = await Auth_SignUp(phone_number, sentCode.phone_code_hash, first_name, last_name);
|
||||
}
|
||||
if (authorization is not Auth_Authorization { user: User user })
|
||||
throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name);
|
||||
_session.User = Schema.Serialize(user);
|
||||
_session.User = user.Serialize();
|
||||
_session.Save();
|
||||
return user;
|
||||
}
|
||||
|
|
@ -477,10 +455,13 @@ namespace WTelegram
|
|||
{
|
||||
//TODO: parallelize several parts sending through a N-semaphore? (needs a reactor first)
|
||||
read = await FullReadAsync(stream, bytes, (int)Math.Min(partSize, bytesLeft));
|
||||
await CallAsync<bool>(isBig
|
||||
? new Fn.Upload_SaveBigFilePart { bytes = bytes, file_id = file_id, file_part = file_part, file_total_parts = file_total_parts }
|
||||
: new Fn.Upload_SaveFilePart { bytes = bytes, file_id = file_id, file_part = file_part });
|
||||
if (!isBig) md5.TransformBlock(bytes, 0, read, null, 0);
|
||||
if (isBig)
|
||||
await Upload_SaveBigFilePart(file_id, file_part, file_total_parts, bytes);
|
||||
else
|
||||
{
|
||||
await Upload_SaveFilePart(file_id, file_part, bytes);
|
||||
md5.TransformBlock(bytes, 0, read, null, 0);
|
||||
}
|
||||
bytesLeft -= read;
|
||||
if (read < partSize && bytesLeft != 0) throw new ApplicationException($"Failed to fully read stream ({read},{bytesLeft})");
|
||||
}
|
||||
|
|
@ -524,38 +505,12 @@ namespace WTelegram
|
|||
/// <param name="media">media specification or <see langword="null"/></param>
|
||||
public Task<UpdatesBase> SendMessageAsync(InputPeer peer, string text, InputMedia media = null, int reply_to_msg_id = 0, MessageEntity[] entities = null, DateTime schedule_date = default, bool disable_preview = false)
|
||||
{
|
||||
ITLFunction<UpdatesBase> request = (media == null)
|
||||
? new Fn.Messages_SendMessage
|
||||
{
|
||||
flags = GetFlags(),
|
||||
peer = peer,
|
||||
reply_to_msg_id = reply_to_msg_id,
|
||||
message = text,
|
||||
random_id = Helpers.RandomLong(),
|
||||
entities = entities,
|
||||
schedule_date = schedule_date
|
||||
}
|
||||
: new Fn.Messages_SendMedia
|
||||
{
|
||||
flags = (Fn.Messages_SendMedia.Flags)GetFlags(),
|
||||
peer = peer,
|
||||
reply_to_msg_id = reply_to_msg_id,
|
||||
media = media,
|
||||
message = text,
|
||||
random_id = Helpers.RandomLong(),
|
||||
entities = entities,
|
||||
schedule_date = schedule_date
|
||||
};
|
||||
return CallAsync(request);
|
||||
|
||||
Fn.Messages_SendMessage.Flags GetFlags()
|
||||
{
|
||||
return ((reply_to_msg_id != 0) ? Fn.Messages_SendMessage.Flags.has_reply_to_msg_id : 0)
|
||||
| (disable_preview ? Fn.Messages_SendMessage.Flags.no_webpage : 0)
|
||||
// | (reply_markup != null ? Messages_SendMessage.Flags.has_reply_markup : 0)
|
||||
| (entities != null ? Fn.Messages_SendMessage.Flags.has_entities : 0)
|
||||
| (schedule_date != default ? Fn.Messages_SendMessage.Flags.has_schedule_date : 0);
|
||||
}
|
||||
if (media == null)
|
||||
return Messages_SendMessage(peer, text, Helpers.RandomLong(),
|
||||
no_webpage: disable_preview, reply_to_msg_id: reply_to_msg_id, entities: entities, schedule_date: schedule_date);
|
||||
else
|
||||
return Messages_SendMedia(peer, media, text, Helpers.RandomLong(),
|
||||
reply_to_msg_id: reply_to_msg_id, entities: entities, schedule_date: schedule_date);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,14 +21,13 @@ namespace WTelegram
|
|||
if (PublicKeys.Count == 0) LoadDefaultPublicKey();
|
||||
|
||||
//1)
|
||||
var reqPQ = new Fn.ReqPQ() { nonce = new Int128(RNG) };
|
||||
await client.SendAsync(reqPQ, false);
|
||||
var nonce = new Int128(RNG);
|
||||
var resPQ = await client.ReqPQ(nonce);
|
||||
//2)
|
||||
var reply = await client.RecvInternalAsync();
|
||||
if (reply is not ResPQ resPQ) throw new ApplicationException($"Expected ResPQ but got {reply.GetType().Name}");
|
||||
if (resPQ.nonce != reqPQ.nonce) throw new ApplicationException("Nonce mismatch");
|
||||
if (resPQ.nonce != nonce) throw new ApplicationException("Nonce mismatch");
|
||||
var fingerprint = resPQ.server_public_key_fingerprints.FirstOrDefault(PublicKeys.ContainsKey);
|
||||
if (fingerprint == 0) throw new ApplicationException("Couldn't match any server_public_key_fingerprints");
|
||||
var publicKey = PublicKeys[fingerprint];
|
||||
Helpers.Log(2, $"Selected public key with fingerprint {fingerprint:X}");
|
||||
//3)
|
||||
long retry_id = 0;
|
||||
|
|
@ -36,72 +35,92 @@ namespace WTelegram
|
|||
ulong p = Helpers.PQFactorize(pq);
|
||||
ulong q = pq / p;
|
||||
//4)
|
||||
var new_nonce = new Int256(RNG);
|
||||
var reqDHparams = MakeReqDHparam(fingerprint, PublicKeys[fingerprint], new PQInnerData
|
||||
var pqInnerData = new PQInnerData
|
||||
{
|
||||
pq = resPQ.pq,
|
||||
p = Helpers.ToBigEndian(p),
|
||||
q = Helpers.ToBigEndian(q),
|
||||
nonce = resPQ.nonce,
|
||||
nonce = nonce,
|
||||
server_nonce = resPQ.server_nonce,
|
||||
new_nonce = new_nonce,
|
||||
});
|
||||
await client.SendAsync(reqDHparams, false);
|
||||
new_nonce = new Int256(RNG),
|
||||
};
|
||||
byte[] encrypted_data;
|
||||
{ // the following code was the way TDLib did it (and seems still accepted) until they changed on 8 July 2021
|
||||
using var clearStream = new MemoryStream(255);
|
||||
clearStream.Position = 20; // skip SHA1 area (to be patched)
|
||||
using var writer = new BinaryWriter(clearStream, Encoding.UTF8);
|
||||
writer.WriteTLObject(pqInnerData);
|
||||
int clearLength = (int)clearStream.Length; // length before padding (= 20 + message_data_length)
|
||||
if (clearLength > 255) throw new ApplicationException("PQInnerData too big");
|
||||
byte[] clearBuffer = clearStream.GetBuffer();
|
||||
RNG.GetBytes(clearBuffer, clearLength, 255 - clearLength);
|
||||
SHA1.HashData(clearBuffer.AsSpan(20..clearLength), clearBuffer); // patch with SHA1
|
||||
encrypted_data = BigInteger.ModPow(new BigInteger(clearBuffer, true, true), // encrypt with RSA key
|
||||
new BigInteger(publicKey.e, true, true), new BigInteger(publicKey.n, true, true)).ToByteArray(true, true);
|
||||
}
|
||||
var serverDHparams = await client.ReqDHParams(pqInnerData.nonce, pqInnerData.server_nonce, pqInnerData.p, pqInnerData.q, fingerprint, encrypted_data);
|
||||
//5)
|
||||
reply = await client.RecvInternalAsync();
|
||||
if (reply is not ServerDHParams serverDHparams) throw new ApplicationException($"Expected ServerDHParams but got {reply.GetType().Name}");
|
||||
var localTime = DateTimeOffset.UtcNow;
|
||||
if (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new ApplicationException("not server_DH_params_ok");
|
||||
if (serverDHparamsOk.nonce != resPQ.nonce) throw new ApplicationException("Nonce mismatch");
|
||||
if (serverDHparamsOk.nonce != nonce) throw new ApplicationException("Nonce mismatch");
|
||||
if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch");
|
||||
var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(resPQ.server_nonce, new_nonce);
|
||||
var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(resPQ.server_nonce, pqInnerData.new_nonce);
|
||||
var answer = AES_IGE_EncryptDecrypt(serverDHparamsOk.encrypted_answer, tmp_aes_key, tmp_aes_iv, false);
|
||||
|
||||
using var encryptedReader = new BinaryReader(new MemoryStream(answer));
|
||||
var answerHash = encryptedReader.ReadBytes(20);
|
||||
var answerObj = Schema.DeserializeValue(encryptedReader, typeof(object));
|
||||
var answerObj = encryptedReader.ReadTLObject();
|
||||
if (answerObj is not ServerDHInnerData serverDHinnerData) throw new ApplicationException("not server_DH_inner_data");
|
||||
long padding = encryptedReader.BaseStream.Length - encryptedReader.BaseStream.Position;
|
||||
if (padding >= 16) throw new ApplicationException("Too much pad");
|
||||
if (!Enumerable.SequenceEqual(SHA1.HashData(answer.AsSpan(20..^(int)padding)), answerHash))
|
||||
throw new ApplicationException("Answer SHA1 mismatch");
|
||||
if (serverDHinnerData.nonce != resPQ.nonce) throw new ApplicationException("Nonce mismatch");
|
||||
if (serverDHinnerData.nonce != nonce) throw new ApplicationException("Nonce mismatch");
|
||||
if (serverDHinnerData.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch");
|
||||
var g_a = new BigInteger(serverDHinnerData.g_a, true, true);
|
||||
var dh_prime = new BigInteger(serverDHinnerData.dh_prime, true, true);
|
||||
ValidityChecks(dh_prime, serverDHinnerData.g);
|
||||
Helpers.Log(1, $"Server time: {serverDHinnerData.server_time} UTC");
|
||||
session.ServerTicksOffset = (serverDHinnerData.server_time - localTime).Ticks;
|
||||
|
||||
//6)
|
||||
var bData = new byte[256];
|
||||
RNG.GetBytes(bData);
|
||||
var b = new BigInteger(bData, true, true);
|
||||
var g_b = BigInteger.ModPow(serverDHinnerData.g, b, dh_prime);
|
||||
var setClientDHparams = MakeClientDHparams(tmp_aes_key, tmp_aes_iv, new ClientDHInnerData
|
||||
var clientDHinnerData = new ClientDHInnerData
|
||||
{
|
||||
nonce = resPQ.nonce,
|
||||
nonce = nonce,
|
||||
server_nonce = resPQ.server_nonce,
|
||||
retry_id = retry_id,
|
||||
g_b = g_b.ToByteArray(true, true)
|
||||
});
|
||||
await client.SendAsync(setClientDHparams, false);
|
||||
};
|
||||
{ // the following code was the way TDLib did it (and seems still accepted) until they changed on 8 July 2021
|
||||
using var clearStream = new MemoryStream(384);
|
||||
clearStream.Position = 20; // skip SHA1 area (to be patched)
|
||||
using var writer = new BinaryWriter(clearStream, Encoding.UTF8);
|
||||
writer.WriteTLObject(clientDHinnerData);
|
||||
int clearLength = (int)clearStream.Length; // length before padding (= 20 + message_data_length)
|
||||
int paddingToAdd = (0x7FFFFFF0 - clearLength) % 16;
|
||||
clearStream.SetLength(clearLength + paddingToAdd);
|
||||
byte[] clearBuffer = clearStream.GetBuffer();
|
||||
RNG.GetBytes(clearBuffer, clearLength, paddingToAdd);
|
||||
SHA1.HashData(clearBuffer.AsSpan(20..clearLength), clearBuffer);
|
||||
|
||||
encrypted_data = AES_IGE_EncryptDecrypt(clearBuffer.AsSpan(0, clearLength + paddingToAdd), tmp_aes_key, tmp_aes_iv, true);
|
||||
}
|
||||
var setClientDHparamsAnswer = await client.SetClientDHParams(clientDHinnerData.nonce, clientDHinnerData.server_nonce, encrypted_data);
|
||||
//7)
|
||||
var gab = BigInteger.ModPow(g_a, b, dh_prime);
|
||||
var authKey = gab.ToByteArray(true, true);
|
||||
|
||||
//8)
|
||||
var authKeyHash = SHA1.HashData(authKey);
|
||||
retry_id = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash); // (auth_key_aux_hash)
|
||||
//9)
|
||||
reply = await client.RecvInternalAsync();
|
||||
if (reply is not SetClientDHParamsAnswer setClientDHparamsAnswer) throw new ApplicationException($"Expected SetClientDHParamsAnswer but got {reply.GetType().Name}");
|
||||
if (setClientDHparamsAnswer is not DHGenOk) throw new ApplicationException("not dh_gen_ok");
|
||||
if (setClientDHparamsAnswer.nonce != resPQ.nonce) throw new ApplicationException("Nonce mismatch");
|
||||
if (setClientDHparamsAnswer.nonce != nonce) throw new ApplicationException("Nonce mismatch");
|
||||
if (setClientDHparamsAnswer.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch");
|
||||
var expected_new_nonceN = new byte[32 + 1 + 8];
|
||||
new_nonce.raw.CopyTo(expected_new_nonceN, 0);
|
||||
pqInnerData.new_nonce.raw.CopyTo(expected_new_nonceN, 0);
|
||||
expected_new_nonceN[32] = 1;
|
||||
Array.Copy(authKeyHash, 0, expected_new_nonceN, 33, 8); // (auth_key_aux_hash)
|
||||
if (!Enumerable.SequenceEqual(setClientDHparamsAnswer.new_nonce_hashN.raw, SHA1.HashData(expected_new_nonceN).Skip(4)))
|
||||
|
|
@ -109,7 +128,7 @@ namespace WTelegram
|
|||
|
||||
session.AuthKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12));
|
||||
session.AuthKey = authKey;
|
||||
session.Salt = BinaryPrimitives.ReadInt64LittleEndian(new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw);
|
||||
session.Salt = BinaryPrimitives.ReadInt64LittleEndian(pqInnerData.new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw);
|
||||
session.Save();
|
||||
|
||||
static (byte[] key, byte[] iv) ConstructTmpAESKeyIV(Int128 server_nonce, Int256 new_nonce)
|
||||
|
|
@ -153,62 +172,13 @@ namespace WTelegram
|
|||
// We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well.
|
||||
}
|
||||
|
||||
private static Fn.ReqDHParams MakeReqDHparam(long publicKey_fingerprint, RSAPublicKey publicKey, PQInnerData pqInnerData)
|
||||
{
|
||||
// the following code was the way TDLib did it (and seems still accepted) until they changed on 8 July 2021
|
||||
using var clearStream = new MemoryStream(255);
|
||||
clearStream.Position = 20; // skip SHA1 area (to be patched)
|
||||
using var writer = new BinaryWriter(clearStream, Encoding.UTF8);
|
||||
Schema.Serialize(writer, pqInnerData);
|
||||
int clearLength = (int)clearStream.Length; // length before padding (= 20 + message_data_length)
|
||||
if (clearLength > 255) throw new ApplicationException("PQInnerData too big");
|
||||
byte[] clearBuffer = clearStream.GetBuffer();
|
||||
RNG.GetBytes(clearBuffer, clearLength, 255 - clearLength);
|
||||
SHA1.HashData(clearBuffer.AsSpan(20..clearLength), clearBuffer); // patch with SHA1
|
||||
|
||||
var encrypted_data = BigInteger.ModPow(new BigInteger(clearBuffer, true, true), // encrypt with RSA key
|
||||
new BigInteger(publicKey.e, true, true), new BigInteger(publicKey.n, true, true)).ToByteArray(true, true);
|
||||
return new Fn.ReqDHParams
|
||||
{
|
||||
nonce = pqInnerData.nonce,
|
||||
server_nonce = pqInnerData.server_nonce,
|
||||
p = pqInnerData.p,
|
||||
q = pqInnerData.q,
|
||||
public_key_fingerprint = publicKey_fingerprint,
|
||||
encrypted_data = encrypted_data
|
||||
};
|
||||
}
|
||||
|
||||
private static Fn.SetClientDHParams MakeClientDHparams(byte[] tmp_aes_key, byte[] tmp_aes_iv, ClientDHInnerData clientDHinnerData)
|
||||
{
|
||||
// the following code was the way TDLib did it (and seems still accepted) until they changed on 8 July 2021
|
||||
using var clearStream = new MemoryStream(384);
|
||||
clearStream.Position = 20; // skip SHA1 area (to be patched)
|
||||
using var writer = new BinaryWriter(clearStream, Encoding.UTF8);
|
||||
Schema.Serialize(writer, clientDHinnerData);
|
||||
int clearLength = (int)clearStream.Length; // length before padding (= 20 + message_data_length)
|
||||
int padding = (0x7FFFFFF0 - clearLength) % 16;
|
||||
clearStream.SetLength(clearLength + padding);
|
||||
byte[] clearBuffer = clearStream.GetBuffer();
|
||||
RNG.GetBytes(clearBuffer, clearLength, padding);
|
||||
SHA1.HashData(clearBuffer.AsSpan(20..clearLength), clearBuffer);
|
||||
|
||||
var encrypted_data = AES_IGE_EncryptDecrypt(clearBuffer.AsSpan(0, clearLength + padding), tmp_aes_key, tmp_aes_iv, true);
|
||||
return new Fn.SetClientDHParams
|
||||
{
|
||||
nonce = clientDHinnerData.nonce,
|
||||
server_nonce = clientDHinnerData.server_nonce,
|
||||
encrypted_data = encrypted_data
|
||||
};
|
||||
}
|
||||
|
||||
public static void LoadPublicKey(string pem)
|
||||
{
|
||||
using var rsa = RSA.Create();
|
||||
rsa.ImportFromPem(pem);
|
||||
var rsaParam = rsa.ExportParameters(false);
|
||||
var publicKey = new RSAPublicKey { n = rsaParam.Modulus, e = rsaParam.Exponent };
|
||||
var bareData = Schema.Serialize(publicKey).AsSpan(4); // bare serialization
|
||||
var bareData = publicKey.Serialize().AsSpan(4); // bare serialization
|
||||
var fingerprint = BinaryPrimitives.ReadInt64LittleEndian(SHA1.HashData(bareData).AsSpan(12)); // 64 lower-order bits of SHA1
|
||||
PublicKeys[fingerprint] = publicKey;
|
||||
Helpers.Log(1, $"Loaded a public key with fingerprint {fingerprint:X}");
|
||||
|
|
|
|||
279
src/Generator.cs
279
src/Generator.cs
|
|
@ -16,6 +16,7 @@ namespace WTelegram
|
|||
readonly Dictionary<int, string> ctorToTypes = new();
|
||||
readonly HashSet<string> allTypes = new();
|
||||
readonly Dictionary<int, Dictionary<string, TypeInfo>> typeInfosByLayer = new();
|
||||
readonly Dictionary<string, int> knownStyles = new() { ["InitConnection"] = 1, ["Help_GetConfig"] = 0, ["HttpWait"] = -1 };
|
||||
Dictionary<string, TypeInfo> typeInfos;
|
||||
int currentLayer;
|
||||
string tabIndent;
|
||||
|
|
@ -30,18 +31,20 @@ namespace WTelegram
|
|||
//await File.WriteAllBytesAsync("TL.MTProto.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/mtproto-json"));
|
||||
//await File.WriteAllBytesAsync("TL.Schema.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/json"));
|
||||
//await File.WriteAllBytesAsync("TL.Secret.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/end-to-end-json"));
|
||||
FromJson("TL.MTProto.json", "TL.MTProto.cs", @"TL.Table.cs");
|
||||
FromJson("TL.MTProto.json", "TL.MTProto.cs", @"TL.Table.cs", true);
|
||||
FromJson("TL.Schema.json", "TL.Schema.cs", @"TL.Table.cs");
|
||||
FromJson("TL.Secret.json", "TL.Secret.cs", @"TL.Table.cs");
|
||||
}
|
||||
|
||||
public void FromJson(string jsonPath, string outputCs, string tableCs = null)
|
||||
public void FromJson(string jsonPath, string outputCs, string tableCs = null, bool legacy = false)
|
||||
{
|
||||
Console.WriteLine("Parsing " + jsonPath);
|
||||
var schema = JsonSerializer.Deserialize<SchemaJson>(File.ReadAllText(jsonPath));
|
||||
if (legacy) InjectLegacy(schema);
|
||||
using var sw = File.CreateText(outputCs);
|
||||
sw.WriteLine("// This file is (mainly) generated automatically using the Generator class");
|
||||
sw.WriteLine("using System;");
|
||||
if (schema.methods.Count != 0) sw.WriteLine("using System.Threading.Tasks;");
|
||||
sw.WriteLine();
|
||||
sw.WriteLine("namespace TL");
|
||||
sw.Write("{");
|
||||
|
|
@ -107,7 +110,7 @@ namespace WTelegram
|
|||
}
|
||||
}
|
||||
foreach (var typeInfo in typeInfos.Values)
|
||||
WriteTypeInfo(sw, typeInfo, jsonPath, layerPrefix, false);
|
||||
WriteTypeInfo(sw, typeInfo, layerPrefix, false);
|
||||
if (layer.Key != 0)
|
||||
{
|
||||
sw.WriteLine("\t}");
|
||||
|
|
@ -116,20 +119,28 @@ namespace WTelegram
|
|||
}
|
||||
if (typeInfosByLayer[0]["Message"].SameName.ID == 0x5BB8E511) typeInfosByLayer[0].Remove("Message");
|
||||
|
||||
sw.WriteLine();
|
||||
var methods = new List<TypeInfo>();
|
||||
if (schema.methods.Length != 0)
|
||||
if (schema.methods.Count != 0)
|
||||
{
|
||||
typeInfos = typeInfosByLayer[0];
|
||||
sw.WriteLine("\tpublic static partial class Fn // ---functions---");
|
||||
sw.WriteLine("}");
|
||||
sw.WriteLine("");
|
||||
sw.WriteLine("namespace WTelegram\t\t// ---functions---");
|
||||
sw.WriteLine("{");
|
||||
sw.WriteLine("\tusing System.IO;");
|
||||
sw.WriteLine("\tusing TL;");
|
||||
sw.WriteLine();
|
||||
sw.WriteLine("\tpublic partial class Client");
|
||||
//sw.WriteLine("\tpublic static partial class Fn // ---functions---");
|
||||
sw.Write("\t{");
|
||||
tabIndent = "\t\t";
|
||||
foreach (var method in schema.methods)
|
||||
{
|
||||
var typeInfo = new TypeInfo { ReturnName = method.type };
|
||||
typeInfo.Structs.Add(new Constructor { id = method.id, @params = method.@params, predicate = method.method, type = method.type });
|
||||
methods.Add(typeInfo);
|
||||
WriteTypeInfo(sw, typeInfo, jsonPath, "", true);
|
||||
WriteMethod(sw, method);
|
||||
//var typeInfo = new TypeInfo { ReturnName = method.type };
|
||||
//typeInfo.Structs.Add(new Constructor { id = method.id, @params = method.@params, predicate = method.method, type = method.type });
|
||||
//methods.Add(typeInfo);
|
||||
//WriteTypeInfo(sw, typeInfo, "", true);
|
||||
}
|
||||
sw.WriteLine("\t}");
|
||||
}
|
||||
|
|
@ -138,7 +149,35 @@ namespace WTelegram
|
|||
if (tableCs != null) UpdateTable(jsonPath, tableCs, methods);
|
||||
}
|
||||
|
||||
void WriteTypeInfo(StreamWriter sw, TypeInfo typeInfo, string definedIn, string layerPrefix, bool isMethod)
|
||||
private static void InjectLegacy(SchemaJson schema)
|
||||
{
|
||||
foreach (var c in schema.constructors.Where(c => c.type == "P_Q_inner_data"))
|
||||
c.predicate = c.predicate[..^2] + "DC";
|
||||
var add = new Constructor { id = ID(0x83C95AEC), predicate = "p_q_inner_data", type = "P_Q_inner_data",
|
||||
@params = Params("pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256") };
|
||||
schema.constructors.Insert(schema.constructors.FindIndex(c => c.type == add.type), add);
|
||||
add = new Constructor { id = ID(0x79CB045D), predicate = "server_DH_params_fail", type = "Server_DH_Params",
|
||||
@params = Params("nonce:int128 server_nonce:int128 new_nonce_hash:int128") };
|
||||
schema.constructors.Insert(schema.constructors.FindIndex(c => c.type == add.type), add);
|
||||
add = new Constructor { id = ID(0x7A19CB76), predicate = "RSA_public_key", type = "RSAPublicKey",
|
||||
@params = Params("n:bytes e:bytes") };
|
||||
schema.constructors.Insert(schema.constructors.FindIndex(c => c.type == "DestroyAuthKeyRes"), add);
|
||||
foreach (var c in schema.constructors.Where(c => c.type == "Set_client_DH_params_answer"))
|
||||
{
|
||||
c.predicate = "DH" + c.predicate[2..];
|
||||
c.@params[2].name = "new_nonce_hashN";
|
||||
}
|
||||
schema.constructors.Find(c => c.predicate == "future_salts").@params[2].type = "Vector<FutureSalt>";
|
||||
var addm = new Method { id = ID(0x60469778), method= "req_PQ", type = "ResPQ",
|
||||
@params = Params("nonce:int128") };
|
||||
schema.methods.Insert(0, addm);
|
||||
|
||||
static string ID(uint id) => ((int)id).ToString();
|
||||
static Param[] Params(string args)
|
||||
=> args.Split(' ').Select(s => { var nt = s.Split(':'); return new Param { name = nt[0], type = nt[1] }; }).ToArray();
|
||||
}
|
||||
|
||||
void WriteTypeInfo(StreamWriter sw, TypeInfo typeInfo, string layerPrefix, bool isMethod)
|
||||
{
|
||||
var parentClass = typeInfo.NeedAbstract != 0 ? typeInfo.ReturnName : "ITLObject";
|
||||
var genericType = typeInfo.ReturnName.Length == 1 ? $"<{typeInfo.ReturnName}>" : null;
|
||||
|
|
@ -238,49 +277,185 @@ namespace WTelegram
|
|||
sw.WriteLine(" }");
|
||||
skipParams = typeInfo.NeedAbstract;
|
||||
}
|
||||
string MapName(string name) => name switch
|
||||
{
|
||||
"out" => "out_",
|
||||
"static" => "static_",
|
||||
"long" => "long_",
|
||||
"default" => "default_",
|
||||
"public" => "public_",
|
||||
"params" => "params_",
|
||||
"private" => "private_",
|
||||
_ => name
|
||||
};
|
||||
}
|
||||
|
||||
string MapType(string type, string name)
|
||||
private static string MapName(string name) => name switch
|
||||
{
|
||||
"out" => "out_",
|
||||
"static" => "static_",
|
||||
"long" => "long_",
|
||||
"default" => "default_",
|
||||
"public" => "public_",
|
||||
"params" => "params_",
|
||||
"private" => "private_",
|
||||
_ => name
|
||||
};
|
||||
|
||||
private string MapType(string type, string name)
|
||||
{
|
||||
if (type.StartsWith("Vector<", StringComparison.OrdinalIgnoreCase))
|
||||
return MapType(type[7..^1], name) + "[]";
|
||||
else if (type == "Bool")
|
||||
return "bool";
|
||||
else if (type == "bytes")
|
||||
return "byte[]";
|
||||
else if (type == "int128")
|
||||
return "Int128";
|
||||
else if (type == "int256")
|
||||
return "Int256";
|
||||
else if (type == "Object")
|
||||
return "ITLObject";
|
||||
else if (type == "!X")
|
||||
return "ITLFunction<X>";
|
||||
else if (typeInfos.TryGetValue(type, out var typeInfo))
|
||||
return typeInfo.ReturnName;
|
||||
else if (type == "int")
|
||||
{
|
||||
if (type.StartsWith("Vector<", StringComparison.OrdinalIgnoreCase))
|
||||
return MapType(type[7..^1], name) + "[]";
|
||||
else if (type == "Bool")
|
||||
return "bool";
|
||||
else if (type == "bytes")
|
||||
return "byte[]";
|
||||
else if (type == "int128")
|
||||
return "Int128";
|
||||
else if (type == "int256")
|
||||
return "Int256";
|
||||
else if (type == "Object")
|
||||
return "ITLObject";
|
||||
else if (type == "!X")
|
||||
return "ITLFunction<X>";
|
||||
else if (typeInfos.TryGetValue(type, out var typeInfo))
|
||||
return typeInfo.ReturnName;
|
||||
else if (type == "int")
|
||||
{
|
||||
var name2 = '_' + name + '_';
|
||||
if (name2.EndsWith("_date_") || name2.EndsWith("_time_") || name2 == "_expires_" || name2 == "_now_" || name2.StartsWith("_valid_"))
|
||||
return "DateTime";
|
||||
else
|
||||
return "int";
|
||||
}
|
||||
else if (type == "string")
|
||||
return name.StartsWith("md5") ? "byte[]" : "string";
|
||||
var name2 = '_' + name + '_';
|
||||
if (name2.EndsWith("_date_") || name2.EndsWith("_time_") || name2.StartsWith("_valid_") ||
|
||||
name2 == "_expires_" || name2 == "_expires_at_" || name2 == "_now_")
|
||||
return "DateTime";
|
||||
else
|
||||
return type;
|
||||
return "int";
|
||||
}
|
||||
else if (type == "string")
|
||||
return name.StartsWith("md5") ? "byte[]" : "string";
|
||||
else
|
||||
return type;
|
||||
}
|
||||
|
||||
private string MapOptionalType(string type, string name)
|
||||
{
|
||||
if (type == "Bool")
|
||||
return "bool?";
|
||||
else if (type == "long")
|
||||
return "long?";
|
||||
else if (type == "double")
|
||||
return "double?";
|
||||
else if (type == "int128")
|
||||
return "Int128?";
|
||||
else if (type == "int256")
|
||||
return "Int256?";
|
||||
else if (type == "int")
|
||||
{
|
||||
var name2 = '_' + name + '_';
|
||||
if (name2.EndsWith("_date_") || name2.EndsWith("_time_") || name2 == "_expires_" || name2 == "_now_" || name2.StartsWith("_valid_"))
|
||||
return "DateTime?";
|
||||
else
|
||||
return "int?";
|
||||
}
|
||||
else
|
||||
return MapType(type, name);
|
||||
}
|
||||
|
||||
private void WriteMethod(StreamWriter sw, Method method)
|
||||
{
|
||||
int ctorNb = int.Parse(method.id);
|
||||
var funcName = CSharpName(method.method);
|
||||
string returnType = MapType(method.type, "");
|
||||
int style = knownStyles.GetValueOrDefault(funcName, 2);
|
||||
// styles: 0 = static string, 1 = static ITLFunction<>, 2 = Task<>, -1 = skip method
|
||||
if (style == -1) return;
|
||||
sw.WriteLine();
|
||||
|
||||
sw.Write($"{tabIndent}//{method.method}#{ctorNb:x8} ");
|
||||
if (method.type.Length == 1) { sw.Write($"{{{method.type}:Type}} "); funcName += $"<{returnType}>"; }
|
||||
foreach (var parm in method.@params) sw.Write($"{parm.name}:{parm.type} ");
|
||||
sw.WriteLine($"= {method.type}");
|
||||
|
||||
if (style == 0) sw.WriteLine($"{tabIndent}public Task<{returnType}> {funcName}() => CallAsync<{returnType}>({funcName});");
|
||||
if (style == 0) sw.Write($"{tabIndent}public static string {funcName}(BinaryWriter writer");
|
||||
if (style == 1) sw.Write($"{tabIndent}public static ITLFunction<{returnType}> {funcName}(");
|
||||
if (style == 2) sw.Write($"{tabIndent}public Task<{returnType}> {funcName}(");
|
||||
bool first = style != 0;
|
||||
foreach (var parm in method.@params) // output non-optional parameters
|
||||
{
|
||||
if (parm.type == "#" || parm.type.StartsWith("flags.")) continue;
|
||||
if (first) first = false; else sw.Write(", ");
|
||||
sw.Write($"{MapType(parm.type, parm.name)} {MapName(parm.name)}");
|
||||
}
|
||||
string flagExpr = null;
|
||||
foreach (var parm in method.@params) // output optional parameters
|
||||
{
|
||||
if (!parm.type.StartsWith("flags.")) continue;
|
||||
var parmName = MapName(parm.name);
|
||||
int qm = parm.type.IndexOf('?');
|
||||
int bit = int.Parse(parm.type[6..qm]);
|
||||
if (first) first = false; else sw.Write(", ");
|
||||
if (parm.type.EndsWith("?true"))
|
||||
{
|
||||
sw.Write($"bool {parmName} = false");
|
||||
flagExpr += $" | ({parmName} ? 0x{1 << bit:X} : 0)";
|
||||
}
|
||||
else
|
||||
{
|
||||
sw.Write($"{MapOptionalType(parm.type[(qm + 1)..], parm.name)} {parmName} = null");
|
||||
flagExpr += $" | ({parmName} != null ? 0x{1 << bit:X} : 0)";
|
||||
}
|
||||
}
|
||||
if (flagExpr != null) flagExpr = flagExpr.IndexOf('|', 3) >= 0 ? flagExpr[3..] : flagExpr[4..^1];
|
||||
sw.WriteLine(")");
|
||||
if (style != 0) tabIndent += "\t";
|
||||
if (style == 1) sw.WriteLine($"{tabIndent}=> writer =>");
|
||||
if (style == 2) sw.WriteLine($"{tabIndent}=> CallAsync<{returnType}>(writer =>");
|
||||
sw.WriteLine(tabIndent + "{");
|
||||
sw.WriteLine($"{tabIndent}\twriter.Write(0x{ctorNb:X8});");
|
||||
foreach (var parm in method.@params) // serialize request
|
||||
{
|
||||
var parmName = MapName(parm.name);
|
||||
var parmType = parm.type;
|
||||
if (parmType.StartsWith("flags."))
|
||||
{
|
||||
if (parmType.EndsWith("?true")) continue;
|
||||
int qm = parmType.IndexOf('?');
|
||||
parmType = parmType[(qm + 1)..];
|
||||
sw.WriteLine($"{tabIndent}\tif ({parmName} != null)");
|
||||
sw.Write('\t');
|
||||
if (MapOptionalType(parmType, parm.name).EndsWith('?'))
|
||||
parmName += ".Value";
|
||||
}
|
||||
switch (parmType)
|
||||
{
|
||||
case "Bool":
|
||||
sw.WriteLine($"{tabIndent}\twriter.Write({parmName} ? 0x997275B5 : 0xBC799737);");
|
||||
break;
|
||||
case "bytes":
|
||||
sw.WriteLine($"{tabIndent}\twriter.WriteTLBytes({parmName});");
|
||||
break;
|
||||
case "long": case "int128": case "int256": case "double":
|
||||
sw.WriteLine($"{tabIndent}\twriter.Write({parmName});");
|
||||
break;
|
||||
case "int":
|
||||
if (MapType(parmType, parm.name) == "int")
|
||||
sw.WriteLine($"{tabIndent}\twriter.Write({parmName});");
|
||||
else
|
||||
sw.WriteLine($"{tabIndent}\twriter.WriteTLStamp({parmName});");
|
||||
break;
|
||||
case "string":
|
||||
if (parm.name.StartsWith("md5"))
|
||||
sw.WriteLine($"{tabIndent}\twriter.WriteTLBytes({parmName});");
|
||||
else
|
||||
sw.WriteLine($"{tabIndent}\twriter.WriteTLString({parmName});");
|
||||
break;
|
||||
case "#":
|
||||
sw.WriteLine($"{tabIndent}\twriter.Write({flagExpr});");
|
||||
break;
|
||||
case "!X":
|
||||
sw.WriteLine($"{tabIndent}\t{parmName}(writer);");
|
||||
break;
|
||||
default:
|
||||
if (parmType.StartsWith("Vector<", StringComparison.OrdinalIgnoreCase))
|
||||
sw.WriteLine($"{tabIndent}\twriter.WriteTLVector({parmName});");
|
||||
else
|
||||
sw.WriteLine($"{tabIndent}\twriter.WriteTLObject({parmName});");
|
||||
break;
|
||||
}
|
||||
}
|
||||
sw.WriteLine($"{tabIndent}\treturn \"{funcName}\";");
|
||||
if (style == 0) sw.WriteLine(tabIndent + "}");
|
||||
if (style == 1) sw.WriteLine(tabIndent + "};");
|
||||
if (style == 2) sw.WriteLine(tabIndent + "});");
|
||||
if (style != 0) tabIndent = tabIndent[0..^1];
|
||||
}
|
||||
|
||||
void UpdateTable(string jsonPath, string tableCs, List<TypeInfo> methods)
|
||||
|
|
@ -347,8 +522,8 @@ namespace WTelegram
|
|||
#pragma warning disable IDE1006 // Naming Styles
|
||||
public class SchemaJson
|
||||
{
|
||||
public Constructor[] constructors { get; set; }
|
||||
public Method[] methods { get; set; }
|
||||
public List<Constructor> constructors { get; set; }
|
||||
public List<Method> methods { get; set; }
|
||||
}
|
||||
|
||||
public class Constructor
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// This file is (mainly) generated automatically using the Generator class
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TL
|
||||
{
|
||||
|
|
@ -12,7 +13,7 @@ namespace TL
|
|||
public long[] server_public_key_fingerprints;
|
||||
}
|
||||
|
||||
[TLDef(0x83C95AEC)] //p_q_inner_data# pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data
|
||||
[TLDef(0x83C95AEC)] //p_q_inner_data#83c95aec pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data
|
||||
public class PQInnerData : ITLObject
|
||||
{
|
||||
public byte[] pq;
|
||||
|
|
@ -22,12 +23,9 @@ namespace TL
|
|||
public Int128 server_nonce;
|
||||
public Int256 new_nonce;
|
||||
}
|
||||
[TLDef(0xA9F55F95)] //p_q_inner_data_dc#a9f55f95 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data
|
||||
public class PQInnerDataDC : PQInnerData
|
||||
{
|
||||
public int dc;
|
||||
}
|
||||
[TLDef(0x56FDDF88)] //p_q_inner_data_temp_dc#56fddf88 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data
|
||||
[TLDef(0xA9F55F95)] //p_q_inner_data_DC#a9f55f95 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data
|
||||
public class PQInnerDataDC : PQInnerData { public int dc; }
|
||||
[TLDef(0x56FDDF88)] //p_q_inner_data_temp_DC#56fddf88 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data
|
||||
public class PQInnerDataTempDC : PQInnerData
|
||||
{
|
||||
public int dc;
|
||||
|
|
@ -70,11 +68,11 @@ namespace TL
|
|||
public Int128 server_nonce;
|
||||
public Int128 new_nonce_hashN; // 16 low order bytes from SHA1(new_nonce + (01=ok, 02=retry, 03=fail) + 8 high order bytes from SHA1(auth_key))
|
||||
}
|
||||
[TLDef(0x3BCBF734)] //dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer
|
||||
[TLDef(0x3BCBF734)] //DH_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hashN:int128 = Set_client_DH_params_answer
|
||||
public class DHGenOk : SetClientDHParamsAnswer { }
|
||||
[TLDef(0x46DC1FB9)] //dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer
|
||||
[TLDef(0x46DC1FB9)] //DH_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hashN:int128 = Set_client_DH_params_answer
|
||||
public class DHGenRetry : SetClientDHParamsAnswer { }
|
||||
[TLDef(0xA69DAE02)] //dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer
|
||||
[TLDef(0xA69DAE02)] //DH_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hashN:int128 = Set_client_DH_params_answer
|
||||
public class DHGenFail : SetClientDHParamsAnswer { }
|
||||
|
||||
[TLDef(0x75A3F765)] //bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner
|
||||
|
|
@ -90,8 +88,8 @@ namespace TL
|
|||
[TLDef(0xF35C6D01)] //rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult
|
||||
public class RpcResult : ITLObject
|
||||
{
|
||||
public long req_msg_id;
|
||||
public object result;
|
||||
internal long req_msg_id;
|
||||
internal object result;
|
||||
}
|
||||
|
||||
[TLDef(0x2144CA19)] //rpc_error#2144ca19 error_code:int error_message:string = RpcError
|
||||
|
|
@ -122,7 +120,7 @@ namespace TL
|
|||
public long salt;
|
||||
}
|
||||
|
||||
[TLDef(0xAE500895)] //future_salts#ae500895 req_msg_id:long now:int salts:vector<future_salt> = FutureSalts
|
||||
[TLDef(0xAE500895)] //future_salts#ae500895 req_msg_id:long now:int salts:Vector<FutureSalt> = FutureSalts
|
||||
public class FutureSalts : ITLObject
|
||||
{
|
||||
public long req_msg_id;
|
||||
|
|
@ -156,8 +154,8 @@ namespace TL
|
|||
[TLDef(0x73F1F8DC)] //msg_container#73f1f8dc messages:vector<%Message> = MessageContainer
|
||||
public class MsgContainer : MessageContainer { public _Message[] messages; }
|
||||
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
//[TLDef(0x5BB8E511)] //message#5bb8e511 msg_id:long seqno:int bytes:int body:Object = Message
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006")]
|
||||
[TLDef(0x5BB8E511)] //message#5bb8e511 msg_id:long seqno:int bytes:int body:Object = Message
|
||||
public class _Message
|
||||
{
|
||||
public long msg_id;
|
||||
|
|
@ -165,7 +163,6 @@ namespace TL
|
|||
public int bytes;
|
||||
public ITLObject body;
|
||||
}
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
|
||||
public abstract class MessageCopy : ITLObject { }
|
||||
[TLDef(0xE06046B2)] //msg_copy#e06046b2 orig_message:Message = MessageCopy
|
||||
|
|
@ -224,7 +221,7 @@ namespace TL
|
|||
public int status;
|
||||
}
|
||||
|
||||
[TLDef(0x7A19CB76)] //rsa_public_key n:string e:string = RSAPublicKey
|
||||
[TLDef(0x7A19CB76)] //RSA_public_key#7a19cb76 n:bytes e:bytes = RSAPublicKey
|
||||
public class RSAPublicKey : ITLObject
|
||||
{
|
||||
public byte[] n;
|
||||
|
|
@ -238,61 +235,110 @@ namespace TL
|
|||
public class DestroyAuthKeyNone : DestroyAuthKeyRes { }
|
||||
[TLDef(0xEA109B13)] //destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes
|
||||
public class DestroyAuthKeyFail : DestroyAuthKeyRes { }
|
||||
}
|
||||
|
||||
public static partial class Fn // ---functions---
|
||||
namespace WTelegram // ---functions---
|
||||
{
|
||||
using System.IO;
|
||||
using TL;
|
||||
|
||||
public partial class Client
|
||||
{
|
||||
[TLDef(0x60469778)] //req_pq#60469778 nonce:int128 = ResPQ
|
||||
public class ReqPQ : ITLFunction<ResPQ> { public Int128 nonce; }
|
||||
[TLDef(0xBE7E8EF1)] //req_pq_multi#be7e8ef1 nonce:int128 = ResPQ
|
||||
public class ReqPQmulti : ResPQ { }
|
||||
//req_PQ#60469778 nonce:int128 = ResPQ
|
||||
public Task<ResPQ> ReqPQ(Int128 nonce)
|
||||
=> CallAsync<ResPQ>(writer =>
|
||||
{
|
||||
writer.Write(0x60469778);
|
||||
writer.Write(nonce);
|
||||
return "ReqPQ";
|
||||
});
|
||||
|
||||
[TLDef(0xD712E4BE)] //req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:bytes = Server_DH_Params
|
||||
public class ReqDHParams : ITLFunction<ServerDHParams>
|
||||
{
|
||||
public Int128 nonce;
|
||||
public Int128 server_nonce;
|
||||
public byte[] p;
|
||||
public byte[] q;
|
||||
public long public_key_fingerprint;
|
||||
public byte[] encrypted_data;
|
||||
}
|
||||
//req_pq_multi#be7e8ef1 nonce:int128 = ResPQ
|
||||
public Task<ResPQ> ReqPqMulti(Int128 nonce)
|
||||
=> CallAsync<ResPQ>(writer =>
|
||||
{
|
||||
writer.Write(0xBE7E8EF1);
|
||||
writer.Write(nonce);
|
||||
return "ReqPqMulti";
|
||||
});
|
||||
|
||||
[TLDef(0xF5045F1F)] //set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer
|
||||
public class SetClientDHParams : ITLFunction<SetClientDHParamsAnswer>
|
||||
{
|
||||
public Int128 nonce;
|
||||
public Int128 server_nonce;
|
||||
public byte[] encrypted_data;
|
||||
}
|
||||
//req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:bytes = Server_DH_Params
|
||||
public Task<ServerDHParams> ReqDHParams(Int128 nonce, Int128 server_nonce, byte[] p, byte[] q, long public_key_fingerprint, byte[] encrypted_data)
|
||||
=> CallAsync<ServerDHParams>(writer =>
|
||||
{
|
||||
writer.Write(0xD712E4BE);
|
||||
writer.Write(nonce);
|
||||
writer.Write(server_nonce);
|
||||
writer.WriteTLBytes(p);
|
||||
writer.WriteTLBytes(q);
|
||||
writer.Write(public_key_fingerprint);
|
||||
writer.WriteTLBytes(encrypted_data);
|
||||
return "ReqDHParams";
|
||||
});
|
||||
|
||||
[TLDef(0x58E4A740)] //rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer
|
||||
public class ReqRpcDropAnswer : ITLFunction<RpcDropAnswer> { public long req_msg_id; }
|
||||
//set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer
|
||||
public Task<SetClientDHParamsAnswer> SetClientDHParams(Int128 nonce, Int128 server_nonce, byte[] encrypted_data)
|
||||
=> CallAsync<SetClientDHParamsAnswer>(writer =>
|
||||
{
|
||||
writer.Write(0xF5045F1F);
|
||||
writer.Write(nonce);
|
||||
writer.Write(server_nonce);
|
||||
writer.WriteTLBytes(encrypted_data);
|
||||
return "SetClientDHParams";
|
||||
});
|
||||
|
||||
[TLDef(0xB921BD04)] //get_future_salts#b921bd04 num:int = FutureSalts
|
||||
public class GetFutureSalts : ITLFunction<FutureSalts> { public int num; }
|
||||
//rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer
|
||||
public Task<RpcDropAnswer> RpcDropAnswer(long req_msg_id)
|
||||
=> CallAsync<RpcDropAnswer>(writer =>
|
||||
{
|
||||
writer.Write(0x58E4A740);
|
||||
writer.Write(req_msg_id);
|
||||
return "RpcDropAnswer";
|
||||
});
|
||||
|
||||
[TLDef(0x7ABE77EC)] //ping#7abe77ec ping_id:long = Pong
|
||||
public class Ping : ITLFunction<Pong> { public long ping_id; }
|
||||
//get_future_salts#b921bd04 num:int = FutureSalts
|
||||
public Task<FutureSalts> GetFutureSalts(int num)
|
||||
=> CallAsync<FutureSalts>(writer =>
|
||||
{
|
||||
writer.Write(0xB921BD04);
|
||||
writer.Write(num);
|
||||
return "GetFutureSalts";
|
||||
});
|
||||
|
||||
[TLDef(0xF3427B8C)] //ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong
|
||||
public class PingDelayDisconnect : ITLFunction<Pong>
|
||||
{
|
||||
public long ping_id;
|
||||
public int disconnect_delay; // seconds
|
||||
}
|
||||
//ping#7abe77ec ping_id:long = Pong
|
||||
public Task<Pong> Ping(long ping_id)
|
||||
=> CallAsync<Pong>(writer =>
|
||||
{
|
||||
writer.Write(0x7ABE77EC);
|
||||
writer.Write(ping_id);
|
||||
return "Ping";
|
||||
});
|
||||
|
||||
[TLDef(0xE7512126)] //destroy_session#e7512126 session_id:long = DestroySessionRes
|
||||
public class DestroySession : ITLFunction<DestroySessionRes> { public long session_id; }
|
||||
//ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong
|
||||
public Task<Pong> PingDelayDisconnect(long ping_id, int disconnect_delay)
|
||||
=> CallAsync<Pong>(writer =>
|
||||
{
|
||||
writer.Write(0xF3427B8C);
|
||||
writer.Write(ping_id);
|
||||
writer.Write(disconnect_delay);
|
||||
return "PingDelayDisconnect";
|
||||
});
|
||||
|
||||
[TLDef(0x9299359F)] //http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait
|
||||
public class HttpWait : ITLObject
|
||||
{
|
||||
public int max_delay; // ms
|
||||
public int wait_after; // ms
|
||||
public int max_wait; // ms
|
||||
}
|
||||
//destroy_session#e7512126 session_id:long = DestroySessionRes
|
||||
public Task<DestroySessionRes> DestroySession(long session_id)
|
||||
=> CallAsync<DestroySessionRes>(writer =>
|
||||
{
|
||||
writer.Write(0xE7512126);
|
||||
writer.Write(session_id);
|
||||
return "DestroySession";
|
||||
});
|
||||
|
||||
[TLDef(0xD1435160)] //destroy_auth_key#d1435160 = DestroyAuthKeyRes
|
||||
public class DestroyAuthKey : ITLFunction<DestroyAuthKeyRes> { }
|
||||
//destroy_auth_key#d1435160 = DestroyAuthKeyRes
|
||||
public Task<DestroyAuthKeyRes> DestroyAuthKey()
|
||||
=> CallAsync<DestroyAuthKeyRes>(writer =>
|
||||
{
|
||||
writer.Write(0xD1435160);
|
||||
return "DestroyAuthKey";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5880
src/TL.Schema.cs
5880
src/TL.Schema.cs
File diff suppressed because it is too large
Load diff
|
|
@ -359,5 +359,4 @@ namespace TL
|
|||
[IfFlag(17)] public long grouped_id;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
114
src/TL.cs
114
src/TL.cs
|
|
@ -11,15 +11,15 @@ using WTelegram;
|
|||
namespace TL
|
||||
{
|
||||
public interface ITLObject { }
|
||||
public interface ITLFunction<R> : ITLObject { }
|
||||
public delegate string ITLFunction<out X>(BinaryWriter writer);
|
||||
|
||||
public static partial class Schema
|
||||
{
|
||||
internal static byte[] Serialize(ITLObject msg)
|
||||
internal static byte[] Serialize(this ITLObject msg)
|
||||
{
|
||||
using var memStream = new MemoryStream(1024);
|
||||
using (var writer = new BinaryWriter(memStream))
|
||||
Serialize(writer, msg);
|
||||
WriteTLObject(writer, msg);
|
||||
return memStream.ToArray();
|
||||
}
|
||||
|
||||
|
|
@ -27,27 +27,15 @@ namespace TL
|
|||
{
|
||||
using var memStream = new MemoryStream(bytes);
|
||||
using var reader = new BinaryReader(memStream);
|
||||
return Deserialize<T>(reader);
|
||||
return (T)reader.ReadTLObject();
|
||||
}
|
||||
|
||||
internal static void Serialize(BinaryWriter writer, ITLObject msg)
|
||||
internal static void WriteTLObject(this BinaryWriter writer, ITLObject obj)
|
||||
{
|
||||
var type = msg.GetType();
|
||||
if (obj == null) { writer.Write(NullCtor); return; }
|
||||
var type = obj.GetType();
|
||||
var ctorNb = type.GetCustomAttribute<TLDefAttribute>().CtorNb;
|
||||
writer.Write(ctorNb);
|
||||
SerializeObject(writer, msg);
|
||||
}
|
||||
|
||||
internal static T Deserialize<T>(BinaryReader reader) where T : ITLObject
|
||||
{
|
||||
var ctorNb = reader.ReadUInt32();
|
||||
if (!Table.TryGetValue(ctorNb, out var realType))
|
||||
throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}");
|
||||
return (T)DeserializeObject(reader, realType);
|
||||
}
|
||||
|
||||
internal static void SerializeObject(BinaryWriter writer, object obj)
|
||||
{
|
||||
var fields = obj.GetType().GetFields().GroupBy(f => f.DeclaringType).Reverse().SelectMany(g => g);
|
||||
int flags = 0;
|
||||
IfFlagAttribute ifFlag;
|
||||
|
|
@ -56,30 +44,35 @@ namespace TL
|
|||
if (((ifFlag = field.GetCustomAttribute<IfFlagAttribute>()) != null) && (flags & (1 << ifFlag.Bit)) == 0) continue;
|
||||
object value = field.GetValue(obj);
|
||||
if (value == null)
|
||||
SerializeNull(writer, field.FieldType);
|
||||
writer.WriteTLNull(field.FieldType);
|
||||
else
|
||||
SerializeValue(writer, value);
|
||||
writer.WriteTLValue(value);
|
||||
if (field.Name.Equals("Flags", StringComparison.OrdinalIgnoreCase)) flags = (int)value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static ITLObject DeserializeObject(BinaryReader reader, Type type)
|
||||
internal static ITLObject ReadTLObject(this BinaryReader reader, Action<Type, object> notifyType = null)
|
||||
{
|
||||
var ctorNb = reader.ReadUInt32();
|
||||
if (ctorNb == NullCtor) return null;
|
||||
if (!Table.TryGetValue(ctorNb, out var type))
|
||||
throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}");
|
||||
var obj = Activator.CreateInstance(type);
|
||||
notifyType?.Invoke(type, obj);
|
||||
var fields = obj.GetType().GetFields().GroupBy(f => f.DeclaringType).Reverse().SelectMany(g => g);
|
||||
int flags = 0;
|
||||
IfFlagAttribute ifFlag;
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (((ifFlag = field.GetCustomAttribute<IfFlagAttribute>()) != null) && (flags & (1 << ifFlag.Bit)) == 0) continue;
|
||||
object value = DeserializeValue(reader, field.FieldType);
|
||||
object value = reader.ReadTLValue(field.FieldType);
|
||||
field.SetValue(obj, value);
|
||||
if (field.Name.Equals("Flags", StringComparison.OrdinalIgnoreCase)) flags = (int)value;
|
||||
}
|
||||
return type == typeof(GzipPacked) ? UnzipPacket((GzipPacked)obj) : (ITLObject)obj;
|
||||
}
|
||||
|
||||
internal static void SerializeValue(BinaryWriter writer, object value)
|
||||
internal static void WriteTLValue(this BinaryWriter writer, object value)
|
||||
{
|
||||
var type = value.GetType();
|
||||
switch (Type.GetTypeCode(type))
|
||||
|
|
@ -89,25 +82,21 @@ namespace TL
|
|||
case TypeCode.Int64: writer.Write((long)value); break;
|
||||
case TypeCode.UInt64: writer.Write((ulong)value); break;
|
||||
case TypeCode.Double: writer.Write((double)value); break;
|
||||
case TypeCode.String: SerializeBytes(writer, Encoding.UTF8.GetBytes((string)value)); break;
|
||||
case TypeCode.DateTime: writer.Write((uint)(((DateTime)value).ToUniversalTime().Ticks / 10000000 - 62135596800L)); break;
|
||||
case TypeCode.Boolean: writer.Write((bool)value ? 0x997275b5 : 0xbc799737); break;
|
||||
case TypeCode.String: writer.WriteTLString((string)value); break;
|
||||
case TypeCode.DateTime: writer.WriteTLStamp((DateTime)value); break;
|
||||
case TypeCode.Boolean: writer.Write((bool)value ? 0x997275B5 : 0xBC799737); break;
|
||||
case TypeCode.Object:
|
||||
if (type.IsArray)
|
||||
{
|
||||
if (value is byte[] bytes)
|
||||
SerializeBytes(writer, bytes);
|
||||
writer.WriteTLBytes(bytes);
|
||||
else
|
||||
SerializeVector(writer, (Array)value);
|
||||
}
|
||||
writer.WriteTLVector((Array)value);
|
||||
else if (value is Int128 int128)
|
||||
writer.Write(int128);
|
||||
else if (value is Int256 int256)
|
||||
writer.Write(int256);
|
||||
else if (type.IsValueType)
|
||||
SerializeObject(writer, value);
|
||||
else if (value is ITLObject tlObject)
|
||||
Serialize(writer, tlObject);
|
||||
WriteTLObject(writer, tlObject);
|
||||
else
|
||||
ShouldntBeHere();
|
||||
break;
|
||||
|
|
@ -117,7 +106,7 @@ namespace TL
|
|||
}
|
||||
}
|
||||
|
||||
internal static object DeserializeValue(BinaryReader reader, Type type)
|
||||
internal static object ReadTLValue(this BinaryReader reader, Type type)
|
||||
{
|
||||
switch (Type.GetTypeCode(type))
|
||||
{
|
||||
|
|
@ -126,8 +115,8 @@ namespace TL
|
|||
case TypeCode.Int64: return reader.ReadInt64();
|
||||
case TypeCode.UInt64: return reader.ReadUInt64();
|
||||
case TypeCode.Double: return reader.ReadDouble();
|
||||
case TypeCode.String: return Encoding.UTF8.GetString(DeserializeBytes(reader));
|
||||
case TypeCode.DateTime: return new DateTime((reader.ReadUInt32() + 62135596800L) * 10000000, DateTimeKind.Utc);
|
||||
case TypeCode.String: return reader.ReadTLString();
|
||||
case TypeCode.DateTime: return reader.ReadTLStamp();
|
||||
case TypeCode.Boolean:
|
||||
return reader.ReadUInt32() switch
|
||||
{
|
||||
|
|
@ -139,36 +128,35 @@ namespace TL
|
|||
if (type.IsArray)
|
||||
{
|
||||
if (type == typeof(byte[]))
|
||||
return DeserializeBytes(reader);
|
||||
return reader.ReadTLBytes();
|
||||
else if (type == typeof(_Message[]))
|
||||
return DeserializeMessages(reader);
|
||||
return reader.ReadTLMessages();
|
||||
else
|
||||
return DeserializeVector(reader, type);
|
||||
return reader.ReadTLVector(type);
|
||||
}
|
||||
else if (type == typeof(Int128))
|
||||
return new Int128(reader);
|
||||
else if (type == typeof(Int256))
|
||||
return new Int256(reader);
|
||||
else if (type.IsValueType)
|
||||
return DeserializeObject(reader, type);
|
||||
else
|
||||
return Deserialize<ITLObject>(reader);
|
||||
return ReadTLObject(reader);
|
||||
default:
|
||||
ShouldntBeHere();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SerializeVector(BinaryWriter writer, Array array)
|
||||
internal static void WriteTLVector(this BinaryWriter writer, Array array)
|
||||
{
|
||||
writer.Write(VectorCtor);
|
||||
if (array == null) { writer.Write(0); return; }
|
||||
int count = array.Length;
|
||||
writer.Write(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
SerializeValue(writer, array.GetValue(i));
|
||||
writer.WriteTLValue(array.GetValue(i));
|
||||
}
|
||||
|
||||
private static object DeserializeVector(BinaryReader reader, Type type)
|
||||
internal static Array ReadTLVector(this BinaryReader reader, Type type)
|
||||
{
|
||||
var ctorNb = reader.ReadInt32();
|
||||
if (ctorNb != VectorCtor) throw new ApplicationException($"Cannot deserialize {type.Name} with ctor #{ctorNb:x}");
|
||||
|
|
@ -176,12 +164,30 @@ namespace TL
|
|||
int count = reader.ReadInt32();
|
||||
Array array = (Array)Activator.CreateInstance(type, count);
|
||||
for (int i = 0; i < count; i++)
|
||||
array.SetValue(DeserializeValue(reader, elementType), i);
|
||||
array.SetValue(reader.ReadTLValue(elementType), i);
|
||||
return array;
|
||||
}
|
||||
|
||||
private static void SerializeBytes(BinaryWriter writer, byte[] bytes)
|
||||
internal static void WriteTLStamp(this BinaryWriter writer, DateTime datetime)
|
||||
=> writer.Write((uint)(datetime.ToUniversalTime().Ticks / 10000000 - 62135596800L));
|
||||
|
||||
internal static DateTime ReadTLStamp(this BinaryReader reader)
|
||||
=> new((reader.ReadUInt32() + 62135596800L) * 10000000, DateTimeKind.Utc);
|
||||
|
||||
internal static void WriteTLString(this BinaryWriter writer, string str)
|
||||
{
|
||||
if (str == null)
|
||||
writer.Write(0);
|
||||
else
|
||||
writer.WriteTLBytes(Encoding.UTF8.GetBytes(str));
|
||||
}
|
||||
|
||||
internal static string ReadTLString(this BinaryReader reader)
|
||||
=> Encoding.UTF8.GetString(reader.ReadTLBytes());
|
||||
|
||||
internal static void WriteTLBytes(this BinaryWriter writer, byte[] bytes)
|
||||
{
|
||||
if (bytes == null) { writer.Write(0); return; }
|
||||
int length = bytes.Length;
|
||||
if (length < 254)
|
||||
writer.Write((byte)length);
|
||||
|
|
@ -194,7 +200,7 @@ namespace TL
|
|||
while (++length % 4 != 0) writer.Write((byte)0);
|
||||
}
|
||||
|
||||
private static byte[] DeserializeBytes(BinaryReader reader)
|
||||
internal static byte[] ReadTLBytes(this BinaryReader reader)
|
||||
{
|
||||
byte[] bytes;
|
||||
int length = reader.ReadByte();
|
||||
|
|
@ -210,7 +216,7 @@ namespace TL
|
|||
return bytes;
|
||||
}
|
||||
|
||||
internal static void SerializeNull(BinaryWriter writer, Type type)
|
||||
internal static void WriteTLNull(this BinaryWriter writer, Type type)
|
||||
{
|
||||
if (!type.IsArray)
|
||||
writer.Write(NullCtor);
|
||||
|
|
@ -219,7 +225,7 @@ namespace TL
|
|||
writer.Write(0); // null arrays are serialized as empty
|
||||
}
|
||||
|
||||
private static _Message[] DeserializeMessages(BinaryReader reader)
|
||||
internal static _Message[] ReadTLMessages(this BinaryReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
var array = new _Message[count];
|
||||
|
|
@ -234,7 +240,7 @@ namespace TL
|
|||
var pos = reader.BaseStream.Position;
|
||||
try
|
||||
{
|
||||
array[i].body = (ITLObject)DeserializeValue(reader, typeof(ITLObject));
|
||||
array[i].body = reader.ReadTLObject();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -245,10 +251,10 @@ namespace TL
|
|||
return array;
|
||||
}
|
||||
|
||||
private static ITLObject UnzipPacket(GzipPacked obj)
|
||||
internal static ITLObject UnzipPacket(GzipPacked obj)
|
||||
{
|
||||
using var reader = new BinaryReader(new GZipStream(new MemoryStream(obj.packed_data), CompressionMode.Decompress));
|
||||
var result = Deserialize<ITLObject>(reader);
|
||||
var result = ReadTLObject(reader);
|
||||
Helpers.Log(1, $" → {result.GetType().Name}");
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue