mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2026-01-11 03:00:05 +01:00
Implemented several of the TODOs:
- Encrypt session file - Export/Import user authorization on DC migration. - Implemented SignUpRequired - function requests moved to class Fn, generation fix
This commit is contained in:
parent
61077c380c
commit
6315b78803
108
Client.cs
108
Client.cs
|
|
@ -14,8 +14,6 @@ using System.Threading.Tasks;
|
|||
using TL;
|
||||
using static WTelegram.Encryption;
|
||||
|
||||
//TODO: include XML comments in nuget
|
||||
|
||||
namespace WTelegram
|
||||
{
|
||||
public sealed class Client : IDisposable
|
||||
|
|
@ -40,7 +38,7 @@ namespace WTelegram
|
|||
_updateHandler = updateHandler;
|
||||
_apiId = int.Parse(Config("api_id"));
|
||||
_apiHash = Config("api_hash");
|
||||
_session = Session.LoadOrCreate(Config("session_pathname"));
|
||||
_session = Session.LoadOrCreate(Config("session_pathname"), Convert.FromHexString(_apiHash));
|
||||
}
|
||||
|
||||
public string Config(string config)
|
||||
|
|
@ -76,6 +74,12 @@ namespace WTelegram
|
|||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822")]
|
||||
public void LoadPublicKey(string pem) => Encryption.LoadPublicKey(pem);
|
||||
|
||||
public void Reset() // disconnect and reset session (forget server address, current user and authkey)
|
||||
{
|
||||
_tcpClient.Close();
|
||||
_session.Reset();
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
var endpoint = _session.DataCenter == null ? IPEndPoint.Parse(Config("server_address"))
|
||||
|
|
@ -89,10 +93,10 @@ namespace WTelegram
|
|||
if (_session.AuthKey == null)
|
||||
await CreateAuthorizationKey(this, _session);
|
||||
|
||||
TLConfig = await CallAsync(new InvokeWithLayer<Config>
|
||||
TLConfig = await CallAsync(new Fn.InvokeWithLayer<Config>
|
||||
{
|
||||
layer = Schema.Layer,
|
||||
query = new InitConnection<Config>
|
||||
query = new Fn.InitConnection<Config>
|
||||
{
|
||||
api_id = _apiId,
|
||||
device_model = Config("device_model"),
|
||||
|
|
@ -101,7 +105,7 @@ namespace WTelegram
|
|||
system_lang_code = Config("system_lang_code"),
|
||||
lang_pack = Config("lang_pack"),
|
||||
lang_code = Config("lang_code"),
|
||||
query = new Help_GetConfig()
|
||||
query = new Fn.Help_GetConfig()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -109,7 +113,9 @@ namespace WTelegram
|
|||
private async Task MigrateDCAsync(int dcId)
|
||||
{
|
||||
Helpers.Log(2, $"Migrate to DC {dcId}...");
|
||||
//TODO: Export/Import client authorization?
|
||||
Auth_ExportedAuthorization exported = null;
|
||||
if (_session.User != null)
|
||||
exported = await CallAsync(new Fn.Auth_ExportAuthorization { dc_id = 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);
|
||||
|
|
@ -118,10 +124,15 @@ namespace WTelegram
|
|||
else
|
||||
dcOptions = dcOptions.OrderBy(dc => dc.flags & DcOption.Flags.ipv6); // list ipv4 first
|
||||
var dcOption = dcOptions.FirstOrDefault();
|
||||
_session.DataCenter = dcOption ?? throw new ApplicationException($"Could not find adequate dcOption for DC {dcId}");
|
||||
_session.AuthKeyID = _session.Salt = _session.Seqno = 0;
|
||||
_session.AuthKey = null;
|
||||
_session.Reset(dcOption ?? throw new ApplicationException($"Could not find adequate dcOption for DC {dcId}"));
|
||||
await ConnectAsync();
|
||||
if (exported != null)
|
||||
{
|
||||
var authorization = await CallAsync(new Fn.Auth_ImportAuthorization { id = exported.id, bytes = 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);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -134,7 +145,7 @@ namespace WTelegram
|
|||
public async Task SendAsync(ITLObject msg, bool isContent = true)
|
||||
{
|
||||
if (_session.AuthKeyID != 0) await CheckMsgsToAck();
|
||||
using var memStream = new MemoryStream(1024); //TODO: choose a useful capacity
|
||||
using var memStream = new MemoryStream(1024);
|
||||
using var writer = new BinaryWriter(memStream, Encoding.UTF8);
|
||||
writer.Write(0); // int32 frame_len (to be patched with full frame length)
|
||||
writer.Write(_frame_seqTx++); // int32 frame_seq
|
||||
|
|
@ -153,8 +164,8 @@ namespace WTelegram
|
|||
else
|
||||
{
|
||||
Helpers.Log(1, $"Sending {msg.GetType().Name,-50} #{(short)msgId.GetHashCode():X4}");
|
||||
//TODO: Implement MTProto 2.0
|
||||
using var clearStream = new MemoryStream(1024); //TODO: choose a useful capacity
|
||||
//TODO: implement MTProto 2.0
|
||||
using var clearStream = new MemoryStream(1024);
|
||||
using var clearWriter = new BinaryWriter(clearStream, Encoding.UTF8);
|
||||
clearWriter.Write(_session.Salt); // int64 salt
|
||||
clearWriter.Write(_session.Id); // int64 session_id
|
||||
|
|
@ -193,7 +204,10 @@ namespace WTelegram
|
|||
{
|
||||
var data = await RecvFrameAsync();
|
||||
if (data.Length == 4 && data[3] == 0xFF)
|
||||
throw new ApplicationException($"Server replied with error code: {TransportError(-BinaryPrimitives.ReadInt32LittleEndian(data))}");
|
||||
{
|
||||
int error_code = -BinaryPrimitives.ReadInt32LittleEndian(data);
|
||||
throw new RpcException(error_code, TransportError(error_code));
|
||||
}
|
||||
if (data.Length < 24) // authKeyId+msgId+length+ctorNb | authKeyId+msgKey
|
||||
throw new ApplicationException($"Packet payload too small: {data.Length}");
|
||||
|
||||
|
|
@ -246,7 +260,7 @@ namespace WTelegram
|
|||
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); // hack necessary because some RPC return bare types like bool or int[]
|
||||
return DeserializeRpcResult(reader); // necessary hack because some RPC return bare types like bool or int[]
|
||||
else
|
||||
return Schema.DeserializeObject(reader, realType);
|
||||
}
|
||||
|
|
@ -317,6 +331,8 @@ namespace WTelegram
|
|||
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 (; ;)
|
||||
{
|
||||
|
|
@ -383,7 +399,7 @@ namespace WTelegram
|
|||
throw new ApplicationException($"Got RpcResult({rpcResult.result.GetType().Name}) for unknown msgId {rpcResult.req_msg_id}");
|
||||
break; // silently ignore results for msg_id from previous sessions
|
||||
default:
|
||||
_updateHandler?.Invoke(obj);
|
||||
if (_updateHandler != null) await _updateHandler?.Invoke(obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -393,7 +409,7 @@ namespace WTelegram
|
|||
if (_session.User != null)
|
||||
return Schema.Deserialize<User>(_session.User);
|
||||
string phone_number = Config("phone_number");
|
||||
var sentCode = await CallAsync(new Auth_SendCode
|
||||
var sentCode = await CallAsync(new Fn.Auth_SendCode
|
||||
{
|
||||
phone_number = phone_number,
|
||||
api_id = _apiId,
|
||||
|
|
@ -402,16 +418,36 @@ namespace WTelegram
|
|||
});
|
||||
Helpers.Log(3, $"A verification code has been sent via {sentCode.type.GetType().Name[17..]}");
|
||||
var verification_code = Config("verification_code");
|
||||
var authorization = await CallAsync(new Auth_SignIn
|
||||
Auth_AuthorizationBase authorization;
|
||||
try
|
||||
{
|
||||
phone_number = phone_number,
|
||||
phone_code_hash = sentCode.phone_code_hash,
|
||||
phone_code = verification_code
|
||||
});
|
||||
if (authorization is not Auth_Authorization { user: User user } auth_success)
|
||||
authorization = await CallAsync(new Fn.Auth_SignIn
|
||||
{
|
||||
phone_number = phone_number,
|
||||
phone_code_hash = sentCode.phone_code_hash,
|
||||
phone_code = verification_code
|
||||
});
|
||||
}
|
||||
catch (RpcException e) when (e.Code == 400 && e.Message == "SESSION_PASSWORD_NEEDED")
|
||||
{
|
||||
throw new NotImplementedException("Library does not support 2FA yet"); //TODO: support 2FA
|
||||
}
|
||||
if (authorization is Auth_AuthorizationSignUpRequired signUpRequired)
|
||||
{
|
||||
if (signUpRequired.terms_of_service != null && _updateHandler != null)
|
||||
await _updateHandler?.Invoke(signUpRequired.terms_of_service); // give caller the possibility to read and accept TOS
|
||||
authorization = await CallAsync(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"),
|
||||
});
|
||||
}
|
||||
if (authorization is not Auth_Authorization { user: User user })
|
||||
throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name);
|
||||
//TODO: support Auth_AuthorizationSignUpRequired?
|
||||
_session.User = Schema.Serialize(user);
|
||||
_session.Save();
|
||||
return user;
|
||||
}
|
||||
|
||||
|
|
@ -436,11 +472,11 @@ namespace WTelegram
|
|||
int file_part = 0, read;
|
||||
for (long bytesLeft = length; bytesLeft != 0; file_part++)
|
||||
{
|
||||
// TODO: parallelize several parts sending through a N-semaphore?
|
||||
//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 Upload_SaveBigFilePart { bytes = bytes, file_id = file_id, file_part = file_part, file_total_parts = file_total_parts }
|
||||
: new Upload_SaveFilePart { bytes = bytes, file_id = file_id, file_part = file_part });
|
||||
? 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);
|
||||
bytesLeft -= read;
|
||||
if (read < partSize && bytesLeft != 0) throw new ApplicationException($"Failed to fully read stream ({read},{bytesLeft})");
|
||||
|
|
@ -451,6 +487,8 @@ namespace WTelegram
|
|||
}
|
||||
}
|
||||
|
||||
//TODO: include XML comments in nuget?
|
||||
|
||||
/// <summary>Helper function to send a text or media message more easily</summary>
|
||||
/// <param name="peer">destination of message</param>
|
||||
/// <param name="caption">media caption</param>
|
||||
|
|
@ -486,7 +524,7 @@ namespace WTelegram
|
|||
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 Messages_SendMessage
|
||||
? new Fn.Messages_SendMessage
|
||||
{
|
||||
flags = GetFlags(),
|
||||
peer = peer,
|
||||
|
|
@ -496,9 +534,9 @@ namespace WTelegram
|
|||
entities = entities,
|
||||
schedule_date = schedule_date
|
||||
}
|
||||
: new Messages_SendMedia
|
||||
: new Fn.Messages_SendMedia
|
||||
{
|
||||
flags = (Messages_SendMedia.Flags)GetFlags(),
|
||||
flags = (Fn.Messages_SendMedia.Flags)GetFlags(),
|
||||
peer = peer,
|
||||
reply_to_msg_id = reply_to_msg_id,
|
||||
media = media,
|
||||
|
|
@ -509,13 +547,13 @@ namespace WTelegram
|
|||
};
|
||||
return CallAsync(request);
|
||||
|
||||
Messages_SendMessage.Flags GetFlags()
|
||||
Fn.Messages_SendMessage.Flags GetFlags()
|
||||
{
|
||||
return ((reply_to_msg_id != 0) ? Messages_SendMessage.Flags.has_reply_to_msg_id : 0)
|
||||
| (disable_preview ? Messages_SendMessage.Flags.no_webpage : 0)
|
||||
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 ? Messages_SendMessage.Flags.has_entities : 0)
|
||||
| (schedule_date != default ? Messages_SendMessage.Flags.has_schedule_date : 0);
|
||||
| (entities != null ? Fn.Messages_SendMessage.Flags.has_entities : 0)
|
||||
| (schedule_date != default ? Fn.Messages_SendMessage.Flags.has_schedule_date : 0);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace WTelegram
|
|||
if (PublicKeys.Count == 0) LoadDefaultPublicKey();
|
||||
|
||||
//1)
|
||||
var reqPQ = new ReqPQ() { nonce = new Int128(RNG) };
|
||||
var reqPQ = new Fn.ReqPQ() { nonce = new Int128(RNG) };
|
||||
await client.SendAsync(reqPQ, false);
|
||||
//2)
|
||||
var reply = await client.RecvInternalAsync();
|
||||
|
|
@ -153,7 +153,7 @@ 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 ReqDHParams MakeReqDHparam(long publicKey_fingerprint, RSAPublicKey publicKey, PQInnerData pqInnerData)
|
||||
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);
|
||||
|
|
@ -168,7 +168,7 @@ namespace WTelegram
|
|||
|
||||
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 ReqDHParams
|
||||
return new Fn.ReqDHParams
|
||||
{
|
||||
nonce = pqInnerData.nonce,
|
||||
server_nonce = pqInnerData.server_nonce,
|
||||
|
|
@ -179,10 +179,10 @@ namespace WTelegram
|
|||
};
|
||||
}
|
||||
|
||||
private static SetClientDHParams MakeClientDHparams(byte[] tmp_aes_key, byte[] tmp_aes_iv, ClientDHInnerData clientDHinnerData)
|
||||
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(512); //TODO: choose a useful capacity
|
||||
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);
|
||||
|
|
@ -194,7 +194,7 @@ namespace WTelegram
|
|||
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 SetClientDHParams
|
||||
return new Fn.SetClientDHParams
|
||||
{
|
||||
nonce = clientDHinnerData.nonce,
|
||||
server_nonce = clientDHinnerData.server_nonce,
|
||||
|
|
|
|||
48
Generator.cs
48
Generator.cs
|
|
@ -20,6 +20,7 @@ namespace WTelegram
|
|||
sw.WriteLine();
|
||||
sw.WriteLine("namespace TL");
|
||||
sw.WriteLine("{");
|
||||
string tabIndent = "\t";
|
||||
Dictionary<string, TypeInfo> typeInfos = new();
|
||||
foreach (var ctor in schema.constructors)
|
||||
{
|
||||
|
|
@ -69,8 +70,9 @@ namespace WTelegram
|
|||
foreach (var typeInfo in typeInfos.Values)
|
||||
WriteTypeInfo(sw, typeInfo);
|
||||
|
||||
sw.WriteLine("\t// ---functions---");
|
||||
sw.WriteLine();
|
||||
sw.WriteLine("\tpublic static partial class Fn // ---functions---");
|
||||
sw.WriteLine("\t{");
|
||||
tabIndent = "\t\t";
|
||||
var methods = new List<TypeInfo>();
|
||||
foreach (var method in schema.methods)
|
||||
{
|
||||
|
|
@ -78,6 +80,7 @@ namespace WTelegram
|
|||
typeInfo.Structs.Add(new Constructor { id = method.id, @params = method.@params, predicate = method.method, type = method.type });
|
||||
WriteTypeInfo(sw, typeInfo, true);
|
||||
}
|
||||
sw.WriteLine("\t}");
|
||||
sw.WriteLine("}");
|
||||
|
||||
void WriteTypeInfo(StreamWriter sw, TypeInfo typeInfo, bool isMethod = false)
|
||||
|
|
@ -87,21 +90,21 @@ namespace WTelegram
|
|||
if (isMethod)
|
||||
parentClass = $"ITLFunction<{MapType(typeInfo.ReturnName, "")}>";
|
||||
if (typeInfo.NeedAbstract == -1)
|
||||
sw.WriteLine($"\tpublic abstract class {parentClass} : ITLObject {{ }}");
|
||||
sw.WriteLine($"{tabIndent}public abstract class {parentClass} : ITLObject {{ }}");
|
||||
int skipParams = 0;
|
||||
foreach (var ctor in typeInfo.Structs)
|
||||
{
|
||||
string className = CSharpName(ctor.predicate) + genericType;
|
||||
if (ctor.id == null)
|
||||
sw.Write($"\tpublic abstract class {className} : ITLObject");
|
||||
sw.Write($"{tabIndent}public abstract class {className} : ITLObject");
|
||||
else
|
||||
{
|
||||
int ctorId = int.Parse(ctor.id);
|
||||
sw.Write($"\t[TLDef(0x{ctorId:X}, \"{ctor.predicate}#{ctorId:x8} ");
|
||||
sw.Write($"{tabIndent}[TLDef(0x{ctorId:X}, \"{ctor.predicate}#{ctorId:x8} ");
|
||||
if (genericType != null) sw.Write($"{{{typeInfo.ReturnName}:Type}} ");
|
||||
foreach (var parm in ctor.@params) sw.Write($"{parm.name}:{parm.type} ");
|
||||
sw.WriteLine($"= {ctor.type}\")]");
|
||||
sw.Write($"\tpublic class {className} : ");
|
||||
sw.Write($"{tabIndent}public class {className} : ");
|
||||
sw.Write(skipParams == 0 && typeInfo.NeedAbstract > 0 ? "ITLObject" : parentClass);
|
||||
}
|
||||
var parms = ctor.@params.Skip(skipParams).ToArray();
|
||||
|
|
@ -110,14 +113,15 @@ namespace WTelegram
|
|||
sw.WriteLine(" { }");
|
||||
continue;
|
||||
}
|
||||
if (parms.Length == 1)
|
||||
sw.Write(" { ");
|
||||
else
|
||||
var hasFlagEnum = parms.Any(p => p.type.StartsWith("flags."));
|
||||
bool multiline = hasFlagEnum || parms.Length > 1;
|
||||
if (multiline)
|
||||
{
|
||||
sw.WriteLine();
|
||||
sw.WriteLine("\t{");
|
||||
sw.WriteLine(tabIndent + "{");
|
||||
}
|
||||
var hasFlagEnum = parms.Any(p => p.type.StartsWith("flags."));
|
||||
else
|
||||
sw.Write(" { ");
|
||||
if (hasFlagEnum)
|
||||
{
|
||||
var list = new SortedList<int, string>();
|
||||
|
|
@ -136,21 +140,19 @@ namespace WTelegram
|
|||
if (list.Values.Contains(name)) name += "_field";
|
||||
list[mask] = name;
|
||||
}
|
||||
sw.Write("\t\t[Flags] public enum Flags { ");
|
||||
int lineLen = 36;
|
||||
string line = tabIndent + "\t[Flags] public enum Flags { ";
|
||||
foreach (var (mask, name) in list)
|
||||
{
|
||||
var str = $"{name} = 0x{mask:X}, ";
|
||||
if (lineLen + str.Length >= 140) { sw.WriteLine(); sw.Write("\t\t\t"); lineLen = 12; }
|
||||
sw.Write(str);
|
||||
lineLen += str.Length;
|
||||
if (line.Length + str.Length + tabIndent.Length * 3 >= 134) { sw.WriteLine(line); line = tabIndent + "\t\t"; }
|
||||
line += str;
|
||||
}
|
||||
sw.WriteLine("}");
|
||||
sw.WriteLine(line.TrimEnd(',', ' ') + " }");
|
||||
}
|
||||
foreach (var parm in parms)
|
||||
{
|
||||
if (parm.type.EndsWith("?true")) continue;
|
||||
if (parms.Length > 1) sw.Write("\t\t");
|
||||
if (multiline) sw.Write(tabIndent + "\t");
|
||||
if (parm.type == "#")
|
||||
sw.Write($"public {(hasFlagEnum ? "Flags" : "int")} {parm.name};");
|
||||
else
|
||||
|
|
@ -163,15 +165,15 @@ namespace WTelegram
|
|||
else
|
||||
sw.Write($"public {MapType(parm.type, parm.name)} {MapName(parm.name)};");
|
||||
}
|
||||
if (parms.Length > 1) sw.WriteLine();
|
||||
if (multiline) sw.WriteLine();
|
||||
}
|
||||
|
||||
if (ctorNeedClone.Contains(className))
|
||||
sw.WriteLine($"\t\tpublic {className} Clone() => ({className})MemberwiseClone();");
|
||||
if (parms.Length == 1)
|
||||
sw.WriteLine(" }");
|
||||
sw.WriteLine($"{tabIndent}\tpublic {className} Clone() => ({className})MemberwiseClone();");
|
||||
if (multiline)
|
||||
sw.WriteLine(tabIndent + "}");
|
||||
else
|
||||
sw.WriteLine("\t}");
|
||||
sw.WriteLine(" }");
|
||||
skipParams = typeInfo.NeedAbstract;
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
|
|
|||
48
Session.cs
48
Session.cs
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace WTelegram
|
||||
|
|
@ -16,36 +16,57 @@ namespace WTelegram
|
|||
public long ServerTicksOffset;
|
||||
public long LastSentMsgId;
|
||||
public TL.DcOption DataCenter;
|
||||
public byte[] User; // serialization of TL.User
|
||||
public byte[] User; // serialization of TL.User
|
||||
|
||||
public DateTime SessionStart => _sessionStart;
|
||||
private readonly DateTime _sessionStart = DateTime.UtcNow;
|
||||
private string _pathname;
|
||||
private byte[] _apiHash; // used as AES key for encryption of session file
|
||||
|
||||
internal static Session LoadOrCreate(string pathname)
|
||||
internal static Session LoadOrCreate(string pathname, byte[] apiHash)
|
||||
{
|
||||
if (File.Exists(pathname))
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(pathname);
|
||||
var session = JsonSerializer.Deserialize<Session>(json, Helpers.JsonOptions);
|
||||
var session = Load(pathname, apiHash);
|
||||
session._pathname = pathname;
|
||||
session._apiHash = apiHash;
|
||||
Helpers.Log(2, "Loaded previous session");
|
||||
return session;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Helpers.Log(4, $"Exception while reading session file: {ex.Message}");
|
||||
throw new ApplicationException($"Exception while reading session file: {ex.Message}\nDelete the file to start a new session", ex);
|
||||
}
|
||||
}
|
||||
return new Session { _pathname = pathname, Id = Helpers.RandomLong() };
|
||||
return new Session { _pathname = pathname, _apiHash = apiHash, Id = Helpers.RandomLong() };
|
||||
}
|
||||
|
||||
internal static Session Load(string pathname, byte[] apiHash)
|
||||
{
|
||||
var input = File.ReadAllBytes(pathname);
|
||||
using var aes = Aes.Create();
|
||||
using var decryptor = aes.CreateDecryptor(apiHash, input[0..16]);
|
||||
var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16);
|
||||
if (!SHA256.HashData(utf8Json.AsSpan(32)).SequenceEqual(utf8Json[0..32]))
|
||||
throw new ApplicationException("Integrity check failed in session loading");
|
||||
return JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions);
|
||||
}
|
||||
|
||||
internal void Save()
|
||||
{
|
||||
//TODO: Add some encryption (with prepended SHA256) to prevent from stealing the key
|
||||
File.WriteAllText(_pathname, JsonSerializer.Serialize(this, Helpers.JsonOptions));
|
||||
var utf8Json = JsonSerializer.SerializeToUtf8Bytes(this, Helpers.JsonOptions);
|
||||
var finalBlock = new byte[16];
|
||||
var output = new byte[(16 + 32 + utf8Json.Length + 15) & ~15];
|
||||
Encryption.RNG.GetBytes(output, 0, 16);
|
||||
using var aes = Aes.Create();
|
||||
using var encryptor = aes.CreateEncryptor(_apiHash, output[0..16]);
|
||||
encryptor.TransformBlock(SHA256.HashData(utf8Json), 0, 32, output, 16);
|
||||
encryptor.TransformBlock(utf8Json, 0, utf8Json.Length & ~15, output, 48);
|
||||
utf8Json.AsSpan(utf8Json.Length & ~15).CopyTo(finalBlock);
|
||||
encryptor.TransformFinalBlock(finalBlock, 0, utf8Json.Length & 15).CopyTo(output.AsMemory(48 + utf8Json.Length & ~15));
|
||||
File.WriteAllBytes(_pathname, output);
|
||||
}
|
||||
|
||||
internal (long msgId, int seqno) NewMsg(bool isContent)
|
||||
|
|
@ -61,5 +82,12 @@ namespace WTelegram
|
|||
|
||||
internal DateTime MsgIdToStamp(long serverMsgId)
|
||||
=> new((serverMsgId >> 32) * 10000000 - ServerTicksOffset + 621355968000000000L, DateTimeKind.Utc);
|
||||
|
||||
internal void Reset(TL.DcOption newDC = null)
|
||||
{
|
||||
DataCenter = newDC;
|
||||
AuthKeyID = Salt = Seqno = 0;
|
||||
AuthKey = User = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
109
TL.MTProto.cs
109
TL.MTProto.cs
|
|
@ -238,60 +238,61 @@ namespace TL
|
|||
[TLDef(0xEA109B13, "destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes")]
|
||||
public class DestroyAuthKeyFail : DestroyAuthKeyRes { }
|
||||
|
||||
// ---functions---
|
||||
|
||||
[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 { }
|
||||
|
||||
[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 static partial class Fn // ---functions---
|
||||
{
|
||||
public Int128 nonce;
|
||||
public Int128 server_nonce;
|
||||
public byte[] p;
|
||||
public byte[] q;
|
||||
public long public_key_fingerprint;
|
||||
public byte[] encrypted_data;
|
||||
[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 { }
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
[TLDef(0x58E4A740, "rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer")]
|
||||
public class ReqRpcDropAnswer : ITLFunction<RpcDropAnswer> { public long req_msg_id; }
|
||||
|
||||
[TLDef(0xB921BD04, "get_future_salts#b921bd04 num:int = FutureSalts")]
|
||||
public class GetFutureSalts : ITLFunction<FutureSalts> { public int num; }
|
||||
|
||||
[TLDef(0x7ABE77EC, "ping#7abe77ec ping_id:long = Pong")]
|
||||
public class Ping : ITLFunction<Pong> { public long ping_id; }
|
||||
|
||||
[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
|
||||
}
|
||||
|
||||
[TLDef(0xE7512126, "destroy_session#e7512126 session_id:long = DestroySessionRes")]
|
||||
public class DestroySession : ITLFunction<DestroySessionRes> { public long session_id; }
|
||||
|
||||
[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
|
||||
}
|
||||
|
||||
[TLDef(0xD1435160, "destroy_auth_key#d1435160 = DestroyAuthKeyRes")]
|
||||
public class DestroyAuthKey : ITLFunction<DestroyAuthKeyRes> { }
|
||||
|
||||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
[TLDef(0x58E4A740, "rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer")]
|
||||
public class ReqRpcDropAnswer : ITLFunction<RpcDropAnswer> { public long req_msg_id; }
|
||||
|
||||
[TLDef(0xB921BD04, "get_future_salts#b921bd04 num:int = FutureSalts")]
|
||||
public class GetFutureSalts : ITLFunction<FutureSalts> { public int num; }
|
||||
|
||||
[TLDef(0x7ABE77EC, "ping#7abe77ec ping_id:long = Pong")]
|
||||
public class Ping : ITLFunction<Pong> { public long ping_id; }
|
||||
|
||||
[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
|
||||
}
|
||||
|
||||
[TLDef(0xE7512126, "destroy_session#e7512126 session_id:long = DestroySessionRes")]
|
||||
public class DestroySession : ITLFunction<DestroySessionRes> { public long session_id; }
|
||||
|
||||
[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
|
||||
}
|
||||
|
||||
[TLDef(0xD1435160, "destroy_auth_key#d1435160 = DestroyAuthKeyRes")]
|
||||
public class DestroyAuthKey : ITLFunction<DestroyAuthKeyRes> { }
|
||||
|
||||
}
|
||||
|
|
|
|||
4835
TL.Schema.cs
4835
TL.Schema.cs
File diff suppressed because it is too large
Load diff
|
|
@ -177,6 +177,7 @@ namespace TL
|
|||
public long secret;
|
||||
}
|
||||
|
||||
// ---functions---
|
||||
|
||||
public static partial class Fn // ---functions---
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue