diff --git a/.github/dev.yml b/.github/dev.yml index 5412eb9..1eff107 100644 --- a/.github/dev.yml +++ b/.github/dev.yml @@ -2,7 +2,7 @@ pr: none trigger: - master -name: 3.3.4-dev.$(Rev:r) +name: 3.4.1-dev.$(Rev:r) pool: vmImage: ubuntu-latest diff --git a/.github/release.yml b/.github/release.yml index c5423ff..f5b9a10 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -1,7 +1,7 @@ pr: none trigger: none -name: 3.3.$(Rev:r) +name: 3.4.$(Rev:r) pool: vmImage: ubuntu-latest diff --git a/src/Client.Helpers.cs b/src/Client.Helpers.cs index 961934e..58d5d7b 100644 --- a/src/Client.Helpers.cs +++ b/src/Client.Helpers.cs @@ -55,7 +55,7 @@ namespace WTelegram lock (tasks) tasks[file_part] = task; if (!isBig) md5.TransformBlock(bytes, 0, read, null, 0); - if (read < FilePartSize && bytesLeft != 0) throw new ApplicationException($"Failed to fully read stream ({read},{bytesLeft})"); + if (read < FilePartSize && bytesLeft != 0) throw new WTException($"Failed to fully read stream ({read},{bytesLeft})"); async Task SavePart(int file_part, byte[] bytes) { @@ -331,7 +331,7 @@ namespace WTelegram if (fileSize != 0 && fileOffset >= fileSize) { if (await task != ((fileSize - 1) % FilePartSize) + 1) - throw new ApplicationException("Downloaded file size does not match expected file size"); + throw new WTException("Downloaded file size does not match expected file size"); break; } @@ -362,7 +362,7 @@ namespace WTelegram _parallelTransfers.Release(); } if (fileBase is not Upload_File fileData) - throw new ApplicationException("Upload_GetFile returned unsupported " + fileBase?.GetType().Name); + throw new WTException("Upload_GetFile returned unsupported " + fileBase?.GetType().Name); if (fileData.bytes.Length != FilePartSize) abort = true; if (fileData.bytes.Length != 0) { @@ -480,7 +480,7 @@ namespace WTelegram mds.messages = messageList.ToArray(); return mds; case Messages_Dialogs md: return md; - default: throw new ApplicationException("Messages_GetDialogs returned unexpected " + dialogs?.GetType().Name); + default: throw new WTException("Messages_GetDialogs returned unexpected " + dialogs?.GetType().Name); } } @@ -762,13 +762,13 @@ namespace WTelegram long chatId = long.Parse(url[(start + 2)..slash]); var chats = await this.Channels_GetChannels(new InputChannel(chatId, 0)); if (!chats.chats.TryGetValue(chatId, out chat)) - throw new ApplicationException($"Channel {chatId} not found"); + throw new WTException($"Channel {chatId} not found"); } else { var resolved = await this.Contacts_ResolveUsername(url[start..slash]); chat = resolved.Chat; - if (chat is null) throw new ApplicationException($"@{url[start..slash]} is not a Chat/Channel"); + if (chat is null) throw new WTException($"@{url[start..slash]} is not a Chat/Channel"); } int msgId = int.Parse(url[(slash + 1)..end]); return await this.Channels_GetMessages((Channel)chat, msgId) as Messages_ChannelMessages; diff --git a/src/Client.cs b/src/Client.cs index 3bcfc31..c9006a5 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -120,7 +120,7 @@ namespace WTelegram internal Task ConfigAsync(string what) => Task.Run(() => Config(what)); internal string Config(string what) - => _config(what) ?? DefaultConfig(what) ?? throw new ApplicationException("You must provide a config value for " + what); + => _config(what) ?? DefaultConfig(what) ?? throw new WTException("You must provide a config value for " + what); /// Default config values, used if your Config callback returns public static string DefaultConfig(string what) => what switch @@ -232,7 +232,7 @@ namespace WTelegram if ((dcSession?.AuthKeyID ?? 0) == 0) // we will need to negociate an AuthKey => can't use media_only DC flags &= ~DcOption.Flags.media_only; var dcOptions = _session.DcOptions.Where(dc => dc.id == dcId).OrderBy(dc => dc.flags ^ flags); - var dcOption = dcOptions.FirstOrDefault() ?? throw new ApplicationException($"Could not find adequate dc_option for DC {dcId}"); + var dcOption = dcOptions.FirstOrDefault() ?? throw new WTException($"Could not find adequate dc_option for DC {dcId}"); dcSession ??= new Session.DCSession { Id = Helpers.RandomLong() }; // create new session only if not already existing dcSession.DataCenter = dcOption; return _session.DCSessions[dcId] = dcSession; @@ -267,7 +267,7 @@ namespace WTelegram { var authorization = await altSession.Client.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); + throw new WTException("Failed to get Authorization: " + authorization.GetType().Name); altSession.UserId = user.id; } } @@ -289,19 +289,19 @@ namespace WTelegram try { if (await stream.FullReadAsync(data, 4, cts.Token) != 4) - throw new ApplicationException(ConnectionShutDown); + throw new WTException(ConnectionShutDown); #if OBFUSCATION _recvCtr.EncryptDecrypt(data, 4); #endif int payloadLen = BinaryPrimitives.ReadInt32LittleEndian(data); if (payloadLen <= 0) - throw new ApplicationException("Could not read frame data : Invalid payload length"); + throw new WTException("Could not read frame data : Invalid payload length"); else if (payloadLen > data.Length) data = new byte[payloadLen]; else if (Math.Max(payloadLen, MinBufferSize) < data.Length / 4) data = new byte[Math.Max(payloadLen, MinBufferSize)]; if (await stream.FullReadAsync(data, payloadLen, cts.Token) != payloadLen) - throw new ApplicationException("Could not read frame data : Connection shut down"); + throw new WTException("Could not read frame data : Connection shut down"); #if OBFUSCATION _recvCtr.EncryptDecrypt(data, payloadLen); #endif @@ -310,7 +310,7 @@ namespace WTelegram catch (Exception ex) // an exception in RecvAsync is always fatal { if (cts.IsCancellationRequested) return; - bool disconnectedAltDC = !IsMainDC && ex is ApplicationException { Message: ConnectionShutDown } or IOException { InnerException: SocketException }; + bool disconnectedAltDC = !IsMainDC && ex is WTException { Message: ConnectionShutDown } or IOException { InnerException: SocketException }; if (disconnectedAltDC) Helpers.Log(3, $"{_dcSession.DcID}>Alt DC disconnected: {ex.Message}"); else @@ -378,35 +378,35 @@ namespace WTelegram throw new RpcException(error_code, TransportError(error_code)); } if (dataLen < 24) // authKeyId+msgId+length+ctorNb | authKeyId+msgKey - throw new ApplicationException($"Packet payload too small: {dataLen}"); + throw new WTException($"Packet payload too small: {dataLen}"); long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data); if (authKeyId != _dcSession.AuthKeyID) - throw new ApplicationException($"Received a packet encrypted with unexpected key {authKeyId:X}"); + throw new WTException($"Received a packet encrypted with unexpected key {authKeyId:X}"); if (authKeyId == 0) // Unencrypted message { using var reader = new BinaryReader(new MemoryStream(data, 8, dataLen - 8)); long msgId = _lastRecvMsgId = reader.ReadInt64(); - if ((msgId & 1) == 0) throw new ApplicationException($"Invalid server msgId {msgId}"); + if ((msgId & 1) == 0) throw new WTException($"Invalid server msgId {msgId}"); int length = reader.ReadInt32(); dataLen -= 20; if (length > dataLen || length < dataLen - (_paddedMode ? 15 : 0)) - throw new ApplicationException($"Unexpected unencrypted/padding length {dataLen} - {length}"); + throw new WTException($"Unexpected unencrypted/padding length {dataLen} - {length}"); var obj = reader.ReadTLObject(); Helpers.Log(1, $"{_dcSession.DcID}>Receiving {obj.GetType().Name,-40} {MsgIdToStamp(msgId):u} clear{((msgId & 2) == 0 ? "" : " NAR")}"); - if (_bareRpc == null) throw new ApplicationException("Shouldn't receive unencrypted packet at this point"); + if (_bareRpc == null) throw new WTException("Shouldn't receive unencrypted packet at this point"); return obj; } else { byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, (dataLen - 24) & ~0xF), false, 8, _dcSession.AuthKey, data, 8, _sha256Recv); if (decrypted_data.Length < 36) // header below+ctorNb - throw new ApplicationException($"Decrypted packet too small: {decrypted_data.Length}"); + throw new WTException($"Decrypted packet too small: {decrypted_data.Length}"); _sha256Recv.TransformBlock(_dcSession.AuthKey, 96, 32, null, 0); _sha256Recv.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length); if (!data.AsSpan(8, 16).SequenceEqual(_sha256Recv.Hash.AsSpan(8, 16))) - throw new ApplicationException("Mismatch between MsgKey & decrypted SHA256"); + throw new WTException("Mismatch between MsgKey & decrypted SHA256"); _sha256Recv.Initialize(); using var reader = new BinaryReader(new MemoryStream(decrypted_data)); var serverSalt = reader.ReadInt64(); // int64 salt @@ -415,10 +415,10 @@ namespace WTelegram var seqno = reader.ReadInt32(); // int32 msg_seqno var length = reader.ReadInt32(); // int32 message_data_length - if (length < 0 || length % 4 != 0) throw new ApplicationException($"Invalid message_data_length: {length}"); - if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new ApplicationException($"Invalid message padding length: {decrypted_data.Length - 32}-{length}"); - if (sessionId != _dcSession.Id) throw new ApplicationException($"Unexpected session ID: {sessionId} != {_dcSession.Id}"); - if ((msgId & 1) == 0) throw new ApplicationException($"msg_id is not odd: {msgId}"); + if (length < 0 || length % 4 != 0) throw new WTException($"Invalid message_data_length: {length}"); + if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new WTException($"Invalid message padding length: {decrypted_data.Length - 32}-{length}"); + if (sessionId != _dcSession.Id) throw new WTException($"Unexpected session ID: {sessionId} != {_dcSession.Id}"); + if ((msgId & 1) == 0) throw new WTException($"msg_id is not odd: {msgId}"); if (!_dcSession.CheckNewMsgId(msgId)) { Helpers.Log(3, $"{_dcSession.DcID}>Ignoring duplicate or old msg_id {msgId}"); @@ -434,7 +434,7 @@ namespace WTelegram _dcSession.Salt = serverSalt; _saltChangeCounter += 1200; // counter is decreased by KeepAlive (we have margin of 10 min) if (_saltChangeCounter >= 1800) - throw new ApplicationException("Server salt changed too often! Security issue?"); + throw new WTException("Server salt changed too often! Security issue?"); } if ((seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msgId); @@ -590,7 +590,7 @@ namespace WTelegram return; } else if (_dcSession.AuthKeyID == 0) - throw new ApplicationException($"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}"); + throw new WTException($"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}"); lock (_pendingRpcs) _pendingRpcs[_bareRpc.msgId] = _bareRpc; } @@ -664,7 +664,7 @@ namespace WTelegram else if (PullPendingRequest(badMsgNotification.bad_msg_id) is Rpc rpc) { if (_bareRpc?.msgId == badMsgNotification.bad_msg_id) _bareRpc = null; - rpc.tcs.SetException(new ApplicationException($"BadMsgNotification {badMsgNotification.error_code}")); + rpc.tcs.SetException(new WTException($"BadMsgNotification {badMsgNotification.error_code}")); } else RaiseUpdate(obj); @@ -879,7 +879,7 @@ namespace WTelegram /// First call should be with phone number
Further calls should be with the requested configuration value /// Configuration item requested to continue login, or when login is successful
/// Possible values: verification_code, name (signup), password (2FA), email & email_verification_code (email registration)
- /// + /// public async Task Login(string loginInfo) { if (_loginCfg.request == default) RunLoginAsync(loginInfo); @@ -997,7 +997,7 @@ namespace WTelegram } var mismatch = $"Current logged user {self.id} mismatched user_id or phone_number"; Helpers.Log(3, mismatch); - if (!reloginOnFailedResume) throw new ApplicationException(mismatch); + if (!reloginOnFailedResume) throw new WTException(mismatch); } catch (RpcException ex) when (reloginOnFailedResume) { @@ -1157,7 +1157,7 @@ namespace WTelegram public User LoginAlreadyDone(Auth_AuthorizationBase authorization) { if (authorization is not Auth_Authorization { user: User user }) - throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name); + throw new WTException("Failed to get Authorization: " + authorization.GetType().Name); _session.UserId = _dcSession.UserId = user.id; lock (_session) _session.Save(); return User = user; @@ -1190,7 +1190,7 @@ namespace WTelegram private async Task SendAsync(IObject msg, bool isContent, Rpc rpc = null) { - if (_reactorTask == null) throw new ApplicationException("You must connect to Telegram first"); + if (_reactorTask == null) throw new WTException("You must connect to Telegram first"); isContent &= _dcSession.AuthKeyID != 0; (long msgId, int seqno) = NewMsgId(isContent); if (rpc != null) @@ -1212,7 +1212,7 @@ namespace WTelegram if (_dcSession.AuthKeyID == 0) // send unencrypted message { - if (_bareRpc == null) throw new ApplicationException($"Shouldn't send a {msg.GetType().Name} unencrypted"); + if (_bareRpc == null) throw new WTException($"Shouldn't send a {msg.GetType().Name} unencrypted"); 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) @@ -1273,7 +1273,7 @@ namespace WTelegram internal async Task InvokeBare(IMethod request) { - if (_bareRpc != null) throw new ApplicationException("A bare request is already undergoing"); + if (_bareRpc != null) throw new WTException("A bare request is already undergoing"); retry: _bareRpc = new Rpc { type = typeof(T) }; await SendAsync(request, false, _bareRpc); @@ -1352,7 +1352,7 @@ namespace WTelegram case ReactorError: goto retry; default: - throw new ApplicationException($"{query.GetType().Name} call got a result of type {result.GetType().Name} instead of {typeof(T).Name}"); + throw new WTException($"{query.GetType().Name} call got a result of type {result.GetType().Name} instead of {typeof(T).Name}"); } } } diff --git a/src/Encryption.cs b/src/Encryption.cs index 01f8cc9..41ca816 100644 --- a/src/Encryption.cs +++ b/src/Encryption.cs @@ -23,7 +23,7 @@ namespace WTelegram { AesECB.Mode = CipherMode.ECB; AesECB.Padding = PaddingMode.Zeros; - if (AesECB.BlockSize != 128) throw new ApplicationException("AES Blocksize is not 16 bytes"); + if (AesECB.BlockSize != 128) throw new WTException("AES Blocksize is not 16 bytes"); } internal static async Task CreateAuthorizationKey(Client client, Session.DCSession session) @@ -36,9 +36,9 @@ namespace WTelegram var nonce = new Int128(RNG); var resPQ = await client.ReqPqMulti(nonce); //2) - if (resPQ.nonce != nonce) throw new ApplicationException("Nonce mismatch"); + if (resPQ.nonce != nonce) throw new WTException("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"); + if (fingerprint == 0) throw new WTException("Couldn't match any server_public_key_fingerprints"); var publicKey = PublicKeys[fingerprint]; Helpers.Log(2, $"Selected public key with fingerprint {fingerprint:X}"); //3) @@ -73,7 +73,7 @@ namespace WTelegram clearStream.Write(aes_key, 0, 32); // write aes_key as prefix for initial Sha256 computation writer.WriteTLObject(pqInnerData); int clearLength = (int)clearStream.Position - 32; // length before padding - if (clearLength > 144) throw new ApplicationException("PQInnerData too big"); + if (clearLength > 144) throw new WTException("PQInnerData too big"); byte[] clearBuffer = clearStream.GetBuffer(); RNG.GetBytes(clearBuffer, 32 + clearLength, 192 - clearLength); sha256.ComputeHash(clearBuffer, 0, 32 + 192).CopyTo(clearBuffer, 224); // append Sha256 @@ -91,22 +91,22 @@ namespace WTelegram var serverDHparams = await client.ReqDHParams(pqInnerData.nonce, pqInnerData.server_nonce, pqInnerData.p, pqInnerData.q, fingerprint, encrypted_data); //5) var localTime = DateTimeOffset.UtcNow; - if (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new ApplicationException("not server_DH_params_ok"); - if (serverDHparamsOk.nonce != nonce) throw new ApplicationException("Nonce mismatch"); - if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch"); + if (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new WTException("not server_DH_params_ok"); + if (serverDHparamsOk.nonce != nonce) throw new WTException("Nonce mismatch"); + if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch"); 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 answerReader = new BinaryReader(new MemoryStream(answer)); var answerHash = answerReader.ReadBytes(20); var answerObj = answerReader.ReadTLObject(); - if (answerObj is not ServerDHInnerData serverDHinnerData) throw new ApplicationException("not server_DH_inner_data"); + if (answerObj is not ServerDHInnerData serverDHinnerData) throw new WTException("not server_DH_inner_data"); long padding = answerReader.BaseStream.Length - answerReader.BaseStream.Position; - if (padding >= 16) throw new ApplicationException("Too much pad"); + if (padding >= 16) throw new WTException("Too much pad"); if (!Enumerable.SequenceEqual(sha1.ComputeHash(answer, 20, answer.Length - (int)padding - 20), answerHash)) - throw new ApplicationException("Answer SHA1 mismatch"); - if (serverDHinnerData.nonce != nonce) throw new ApplicationException("Nonce mismatch"); - if (serverDHinnerData.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch"); + throw new WTException("Answer SHA1 mismatch"); + if (serverDHinnerData.nonce != nonce) throw new WTException("Nonce mismatch"); + if (serverDHinnerData.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch"); var g_a = BigEndianInteger(serverDHinnerData.g_a); var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime); CheckGoodPrime(dh_prime, serverDHinnerData.g); @@ -149,15 +149,15 @@ namespace WTelegram var authKeyHash = sha1.ComputeHash(authKey); retry_id = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash); // (auth_key_aux_hash) //9) - if (setClientDHparamsAnswer is not DhGenOk dhGenOk) throw new ApplicationException("not dh_gen_ok"); - if (dhGenOk.nonce != nonce) throw new ApplicationException("Nonce mismatch"); - if (dhGenOk.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch"); + if (setClientDHparamsAnswer is not DhGenOk dhGenOk) throw new WTException("not dh_gen_ok"); + if (dhGenOk.nonce != nonce) throw new WTException("Nonce mismatch"); + if (dhGenOk.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch"); var expected_new_nonceN = new byte[32 + 1 + 8]; 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(dhGenOk.new_nonce_hash1.raw, sha1.ComputeHash(expected_new_nonceN).Skip(4))) - throw new ApplicationException("setClientDHparamsAnswer.new_nonce_hashN mismatch"); + throw new WTException("setClientDHparamsAnswer.new_nonce_hashN mismatch"); session.AuthKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12)); session.AuthKey = authKey; @@ -188,7 +188,7 @@ namespace WTelegram { Helpers.Log(2, "Verifying encryption key safety... (this should happen only once per DC)"); // check that 2^2047 <= p < 2^2048 - if (p.GetBitLength() != 2048) throw new ApplicationException("p is not 2048-bit number"); + if (p.GetBitLength() != 2048) throw new WTException("p is not 2048-bit number"); // check that g generates a cyclic subgroup of prime order (p - 1) / 2, i.e. is a quadratic residue mod p. if (g switch { @@ -200,11 +200,11 @@ namespace WTelegram 7 => (int)(p % 7) is not 3 and not 5 and not 6, _ => true, }) - throw new ApplicationException("Bad prime mod 4g"); + throw new WTException("Bad prime mod 4g"); // check whether p is a safe prime (meaning that both p and (p - 1) / 2 are prime) if (SafePrimes.Contains(p)) return; - if (!p.IsProbablePrime()) throw new ApplicationException("p is not a prime number"); - if (!((p - 1) / 2).IsProbablePrime()) throw new ApplicationException("(p - 1) / 2 is not a prime number"); + if (!p.IsProbablePrime()) throw new WTException("p is not a prime number"); + if (!((p - 1) / 2).IsProbablePrime()) throw new WTException("(p - 1) / 2 is not a prime number"); SafePrimes.Add(p); } @@ -233,7 +233,7 @@ namespace WTelegram // check that g, g_a and g_b are greater than 1 and less than dh_prime - 1. // We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well. if (g.GetBitLength() < 2048 - 64 || (dh_prime - g).GetBitLength() < 2048 - 64) - throw new ApplicationException("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}"); + throw new WTException("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}"); } public static void LoadPublicKey(string pem) @@ -298,7 +298,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB internal static byte[] AES_IGE_EncryptDecrypt(Span input, byte[] aes_key, byte[] aes_iv, bool encrypt) { - if (input.Length % 16 != 0) throw new ApplicationException("AES_IGE input size not divisible by 16"); + if (input.Length % 16 != 0) throw new WTException("AES_IGE input size not divisible by 16"); using var aesCrypto = encrypt ? AesECB.CreateEncryptor(aes_key, null) : AesECB.CreateDecryptor(aes_key, null); var output = new byte[input.Length]; @@ -396,7 +396,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB RNG.GetBytes(algo.salt1, salt1len, 32); } else - throw new ApplicationException("2FA authentication uses an unsupported algo: " + accountPassword.current_algo?.GetType().Name); + throw new WTException("2FA authentication uses an unsupported algo: " + accountPassword.current_algo?.GetType().Name); var g = new BigInteger(algo.g); var p = BigEndianInteger(algo.p); diff --git a/src/Helpers.cs b/src/Helpers.cs index a349c7a..6bc628d 100644 --- a/src/Helpers.cs +++ b/src/Helpers.cs @@ -253,4 +253,10 @@ namespace WTelegram protected override void Dispose(bool disposing) => _innerStream.Dispose(); } } + + public class WTException : ApplicationException + { + public WTException(string message) : base(message) { } + public WTException(string message, Exception innerException) : base(message, innerException) { } + } } diff --git a/src/SecretChats.cs b/src/SecretChats.cs index 53bdec0..422bc44 100644 --- a/src/SecretChats.cs +++ b/src/SecretChats.cs @@ -95,7 +95,7 @@ namespace WTelegram public void Load(Stream input) { using var reader = new BinaryReader(input, Encoding.UTF8, true); - if (reader.ReadInt32() != 0) throw new ApplicationException("Unrecognized Secrets format"); + if (reader.ReadInt32() != 0) throw new WTException("Unrecognized Secrets format"); dh = (Messages_DhConfig)reader.ReadTLObject(); if (dh?.p != null) dh_prime = BigEndianInteger(dh.p); int count = reader.ReadInt32(); @@ -128,15 +128,15 @@ namespace WTelegram { var mdhcb = await client.Messages_GetDhConfig(dh?.version ?? 0, 256); if (mdhcb is Messages_DhConfigNotModified { random: var random }) - _ = dh ?? throw new ApplicationException("DhConfigNotModified on zero version"); + _ = dh ?? throw new WTException("DhConfigNotModified on zero version"); else if (mdhcb is Messages_DhConfig dhc) { var p = BigEndianInteger(dhc.p); CheckGoodPrime(p, dhc.g); (dh, dh_prime, random, dh.random) = (dhc, p, dhc.random, null); } - else throw new ApplicationException("Unexpected DHConfig response: " + mdhcb?.GetType().Name); - if (random.Length != 256) throw new ApplicationException("Invalid DHConfig random"); + else throw new WTException("Unexpected DHConfig response: " + mdhcb?.GetType().Name); + if (random.Length != 256) throw new WTException("Invalid DHConfig random"); var salt = new byte[256]; RNG.GetBytes(salt); for (int i = 0; i < 256; i++) salt[i] ^= random[i]; @@ -146,7 +146,7 @@ namespace WTelegram /// Initiate a secret chat with the given user.
(chat must be acknowledged by remote user before being active)
/// The remote user /// Secret Chat ID - /// + /// public async Task Request(InputUserBase user) { int chat_id; @@ -164,7 +164,7 @@ namespace WTelegram CheckGoodGaAndGb(g_a, dh_prime); var ecb = await client.Messages_RequestEncryption(user, chat_id, g_a.To256Bytes()); if (ecb is not EncryptedChatWaiting ecw || ecw.id != chat_id || ecw.participant_id != chat.participant_id) - throw new ApplicationException("Invalid " + ecb?.GetType().Name); + throw new WTException("Invalid " + ecb?.GetType().Name); chat.peer.access_hash = ecw.access_hash; return chat_id; } @@ -173,7 +173,7 @@ namespace WTelegram /// If update.chat is , you might want to first make sure you want to accept this secret chat initiated by user /// Incoming requests for secret chats are automatically: accepted (), rejected () or ignored () /// if the update was handled successfully - /// + /// public async Task HandleUpdate(UpdateEncryption update, bool? acceptChatRequests = true) { try @@ -188,8 +188,8 @@ namespace WTelegram var gab = BigInteger.ModPow(g_b, a, dh_prime); chat.flags &= ~SecretChat.Flags.requestChat; SetAuthKey(chat, gab.To256Bytes()); - if (ec.key_fingerprint != chat.key_fingerprint) throw new ApplicationException("Invalid fingerprint on accepted secret chat"); - if (ec.access_hash != chat.peer.access_hash || ec.participant_id != chat.participant_id) throw new ApplicationException("Invalid peer on accepted secret chat"); + if (ec.key_fingerprint != chat.key_fingerprint) throw new WTException("Invalid fingerprint on accepted secret chat"); + if (ec.access_hash != chat.peer.access_hash || ec.participant_id != chat.participant_id) throw new WTException("Invalid peer on accepted secret chat"); await SendNotifyLayer(chat); return true; } @@ -227,7 +227,7 @@ namespace WTelegram var ecb = await client.Messages_AcceptEncryption(chat.peer, g_b.ToByteArray(true, true), chat.key_fingerprint); if (ecb is not EncryptedChat ec || ec.id != ecr.id || ec.access_hash != ecr.access_hash || ec.admin_id != ecr.admin_id || ec.key_fingerprint != chat.key_fingerprint) - throw new ApplicationException("Inconsistent accepted secret chat"); + throw new WTException("Inconsistent accepted secret chat"); await SendNotifyLayer(chat); return true; } @@ -274,7 +274,7 @@ namespace WTelegram /// Confirmation of sent message public async Task SendMessage(int chatId, DecryptedMessageBase msg, bool silent = false, InputEncryptedFileBase file = null) { - if (!chats.TryGetValue(chatId, out var chat)) throw new ApplicationException("Secret chat not found"); + if (!chats.TryGetValue(chatId, out var chat)) throw new WTException("Secret chat not found"); try { var dml = new TL.Layer23.DecryptedMessageLayer @@ -336,14 +336,14 @@ namespace WTelegram private IObject Decrypt(SecretChat chat, byte[] data, int dataLen) { if (dataLen < 32) // authKeyId+msgKey+(length+ctorNb) - throw new ApplicationException($"Encrypted packet too small: {data.Length}"); + throw new WTException($"Encrypted packet too small: {data.Length}"); var authKey = chat.authKey; long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data); if (authKeyId == chat.key_fingerprint) if (!chat.flags.HasFlag(SecretChat.Flags.commitKey)) CheckPFS(chat); else { chat.flags &= ~SecretChat.Flags.commitKey; Array.Clear(chat.salt, 0, chat.salt.Length); } else if (chat.flags.HasFlag(SecretChat.Flags.commitKey) && authKeyId == BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(chat.salt).AsSpan(12))) authKey = chat.salt; - else throw new ApplicationException($"Received a packet encrypted with unexpected key {authKeyId:X}"); + else throw new WTException($"Received a packet encrypted with unexpected key {authKeyId:X}"); int x = (int)(chat.flags & SecretChat.Flags.originator); byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, dataLen - 24), false, x, authKey, data, 8, sha256); var length = BinaryPrimitives.ReadInt32LittleEndian(decrypted_data); @@ -354,11 +354,11 @@ namespace WTelegram sha256.TransformBlock(authKey, 88 + x, 32, null, 0); sha256.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length); if (success = data.AsSpan(8, 16).SequenceEqual(sha256.Hash.AsSpan(8, 16))) - if (decrypted_data.Length - 4 - length is < 12 or > 1024) throw new ApplicationException($"Invalid MTProto2 padding length: {decrypted_data.Length - 4}-{length}"); + if (decrypted_data.Length - 4 - length is < 12 or > 1024) throw new WTException($"Invalid MTProto2 padding length: {decrypted_data.Length - 4}-{length}"); else if (chat.remoteLayer < Layer.MTProto2) chat.remoteLayer = Layer.MTProto2; } - if (!success) throw new ApplicationException("Could not decrypt message"); - if (length % 4 != 0) throw new ApplicationException($"Invalid message_data_length: {length}"); + if (!success) throw new WTException("Could not decrypt message"); + if (length % 4 != 0) throw new WTException($"Invalid message_data_length: {length}"); using var reader = new BinaryReader(new MemoryStream(decrypted_data, 4, length)); return reader.ReadTLObject(); } @@ -370,16 +370,16 @@ namespace WTelegram /// You can use the generic properties to access their fields /// May return an empty array if msg was already previously received or is not the next message in sequence. ///
May return multiple messages if missing messages are finally received (using = true)
- /// + /// public ICollection DecryptMessage(EncryptedMessageBase msg, bool fillGaps = true) { - if (!chats.TryGetValue(msg.ChatId, out var chat)) throw new ApplicationException("Secret chat not found"); + if (!chats.TryGetValue(msg.ChatId, out var chat)) throw new WTException("Secret chat not found"); try { var obj = Decrypt(chat, msg.Bytes, msg.Bytes.Length); - if (obj is not TL.Layer23.DecryptedMessageLayer dml) throw new ApplicationException("Decrypted object is not DecryptedMessageLayer"); - if (dml.random_bytes.Length < 15) throw new ApplicationException("Not enough random_bytes"); - if (((dml.out_seq_no ^ dml.in_seq_no) & 1) != 1 || ((dml.out_seq_no ^ chat.in_seq_no) & 1) != 0) throw new ApplicationException("Invalid seq_no parities"); + if (obj is not TL.Layer23.DecryptedMessageLayer dml) throw new WTException("Decrypted object is not DecryptedMessageLayer"); + if (dml.random_bytes.Length < 15) throw new WTException("Not enough random_bytes"); + if (((dml.out_seq_no ^ dml.in_seq_no) & 1) != 1 || ((dml.out_seq_no ^ chat.in_seq_no) & 1) != 0) throw new WTException("Invalid seq_no parities"); if (dml.layer > chat.remoteLayer) chat.remoteLayer = dml.layer; //Debug.WriteLine($"<\t{dml.in_seq_no}\t{dml.out_seq_no}\t\t\t\t\t\texpected:{chat.out_seq_no}/{chat.in_seq_no + 2}"); if (dml.out_seq_no <= chat.in_seq_no) return Array.Empty(); // already received message @@ -510,7 +510,7 @@ namespace WTelegram } break; // we lost, process with the larger exchange_id RequestKey case 0: break; - default: throw new ApplicationException("Invalid RequestKey"); + default: throw new WTException("Invalid RequestKey"); } var g_a = BigEndianInteger(request.g_a); var salt = new byte[256]; @@ -529,9 +529,9 @@ namespace WTelegram break; case TL.Layer23.DecryptedMessageActionAcceptKey accept: if ((chat.flags & (SecretChat.Flags.requestChat | SecretChat.Flags.renewKey | SecretChat.Flags.acceptKey)) != SecretChat.Flags.renewKey) - throw new ApplicationException("Invalid AcceptKey"); + throw new WTException("Invalid AcceptKey"); if (accept.exchange_id != chat.exchange_id) - throw new ApplicationException("AcceptKey: exchange_id mismatch"); + throw new WTException("AcceptKey: exchange_id mismatch"); var a = BigEndianInteger(chat.salt); g_b = BigEndianInteger(accept.g_b); CheckGoodGaAndGb(g_b, dh_prime); @@ -539,7 +539,7 @@ namespace WTelegram var authKey = gab.To256Bytes(); key_fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(authKey).AsSpan(12)); if (accept.key_fingerprint != key_fingerprint) - throw new ApplicationException("AcceptKey: key_fingerprint mismatch"); + throw new WTException("AcceptKey: key_fingerprint mismatch"); _ = SendMessage(chat.ChatId, new TL.Layer23.DecryptedMessageService { random_id = Helpers.RandomLong(), action = new TL.Layer23.DecryptedMessageActionCommitKey { exchange_id = accept.exchange_id, key_fingerprint = accept.key_fingerprint } }); chat.salt = chat.authKey; // A may only discard the previous key after a message encrypted with the new key has been received. @@ -548,10 +548,10 @@ namespace WTelegram break; case TL.Layer23.DecryptedMessageActionCommitKey commit: if ((chat.flags & (SecretChat.Flags.requestChat | SecretChat.Flags.renewKey | SecretChat.Flags.acceptKey)) != SecretChat.Flags.acceptKey) - throw new ApplicationException("Invalid RequestKey"); + throw new WTException("Invalid RequestKey"); key_fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(chat.salt).AsSpan(12)); if (commit.exchange_id != chat.exchange_id | commit.key_fingerprint != key_fingerprint) - throw new ApplicationException("CommitKey: data mismatch"); + throw new WTException("CommitKey: data mismatch"); chat.flags &= ~SecretChat.Flags.acceptKey; authKey = chat.authKey; SetAuthKey(chat, chat.salt); @@ -614,7 +614,7 @@ namespace WTelegram var res = md5.TransformFinalBlock(iv, 0, 32); long fingerprint = BinaryPrimitives.ReadInt64LittleEndian(md5.Hash); fingerprint ^= fingerprint >> 32; - if (encryptedFile.key_fingerprint != (int)fingerprint) throw new ApplicationException("Encrypted file fingerprint mismatch"); + if (encryptedFile.key_fingerprint != (int)fingerprint) throw new WTException("Encrypted file fingerprint mismatch"); using var decryptStream = new AES_IGE_Stream(outputStream, size, key, iv); var fileLocation = encryptedFile.ToFileLocation(); diff --git a/src/Session.cs b/src/Session.cs index 76580ec..8b06975 100644 --- a/src/Session.cs +++ b/src/Session.cs @@ -104,12 +104,12 @@ namespace WTelegram { var input = new byte[length]; if (store.Read(input, 0, length) != length) - throw new ApplicationException($"Can't read session block ({store.Position}, {length})"); + throw new WTException($"Can't read session block ({store.Position}, {length})"); using var sha256 = SHA256.Create(); using var decryptor = aes.CreateDecryptor(rgbKey, input[0..16]); var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16); if (!sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32])) - throw new ApplicationException("Integrity check failed in session loading"); + throw new WTException("Integrity check failed in session loading"); session = JsonSerializer.Deserialize(utf8Json.AsSpan(32), Helpers.JsonOptions); Helpers.Log(2, "Loaded previous session"); } @@ -124,7 +124,7 @@ namespace WTelegram catch (Exception ex) { store.Dispose(); - throw new ApplicationException($"Exception while reading session file: {ex.Message}\nUse the correct api_hash/id/key, or delete the file to start a new session", ex); + throw new WTException($"Exception while reading session file: {ex.Message}\nUse the correct api_hash/id/key, or delete the file to start a new session", ex); } } diff --git a/src/TL.Extensions.cs b/src/TL.Extensions.cs index c9205f8..71dcdc6 100644 --- a/src/TL.Extensions.cs +++ b/src/TL.Extensions.cs @@ -37,10 +37,10 @@ namespace TL public static void CollectUsersChats(this IPeerResolver structure, Dictionary users, Dictionary chats) => structure.UserOrChat(new CollectorPeer { _users = users, _chats = chats }); - public static Task Messages_GetChats(this Client _) => throw new ApplicationException("The method you're looking for is Messages_GetAllChats"); - public static Task Channels_GetChannels(this Client _) => throw new ApplicationException("The method you're looking for is Messages_GetAllChats"); - public static Task Users_GetUsers(this Client _) => throw new ApplicationException("The method you're looking for is Messages_GetAllDialogs"); - public static Task Messages_GetMessages(this Client _) => throw new ApplicationException("If you want to get the messages from a chat, use Messages_GetHistory"); + public static Task Messages_GetChats(this Client _) => throw new WTException("The method you're looking for is Messages_GetAllChats"); + public static Task Channels_GetChannels(this Client _) => throw new WTException("The method you're looking for is Messages_GetAllChats"); + public static Task Users_GetUsers(this Client _) => throw new WTException("The method you're looking for is Messages_GetAllDialogs"); + public static Task Messages_GetMessages(this Client _) => throw new WTException("If you want to get the messages from a chat, use Messages_GetHistory"); } public static class Markdown diff --git a/src/TL.Secret.cs b/src/TL.Secret.cs index 1253e9b..605f1b2 100644 --- a/src/TL.Secret.cs +++ b/src/TL.Secret.cs @@ -32,7 +32,7 @@ namespace TL public abstract class DecryptedMessageMedia : IObject { public virtual string MimeType { get; } - internal virtual (long size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new ApplicationException("Incompatible DecryptedMessageMedia"); } + internal virtual (long size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new WTelegram.WTException("Incompatible DecryptedMessageMedia"); } } /// Object describes the action to which a service message is linked. See diff --git a/src/TL.cs b/src/TL.cs index 4ec4923..97e88aa 100644 --- a/src/TL.cs +++ b/src/TL.cs @@ -28,7 +28,7 @@ namespace TL public IfFlagAttribute(int bit) => Bit = bit; } - public class RpcException : Exception + public class RpcException : WTelegram.WTException { public readonly int Code; /// The value of X in the message, -1 if no variable X was found @@ -73,7 +73,7 @@ namespace TL using (var gzipReader = new BinaryReader(new GZipStream(new MemoryStream(reader.ReadTLBytes()), CompressionMode.Decompress))) return ReadTLObject(gzipReader); if (!Layer.Table.TryGetValue(ctorNb, out var type)) - throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}"); + throw new WTelegram.WTException($"Cannot find type for ctor #{ctorNb:x}"); if (type == null) return null; // nullable ctor (class meaning is associated with null) var tlDef = type.GetCustomAttribute(); var obj = Activator.CreateInstance(type, true); @@ -153,7 +153,7 @@ namespace TL 0x997275b5 => true, 0xbc799737 => false, Layer.RpcErrorCtor => reader.ReadTLObject(Layer.RpcErrorCtor), - var value => throw new ApplicationException($"Invalid boolean value #{value:x}") + var value => throw new WTelegram.WTException($"Invalid boolean value #{value:x}") }; case TypeCode.Object: if (type.IsArray) @@ -235,7 +235,7 @@ namespace TL return array; } else - throw new ApplicationException($"Cannot deserialize {type.Name} with ctor #{ctorNb:x}"); + throw new WTelegram.WTException($"Cannot deserialize {type.Name} with ctor #{ctorNb:x}"); } internal static Dictionary ReadTLDictionary(this BinaryReader reader, Func getID) where T : class @@ -243,7 +243,7 @@ namespace TL uint ctorNb = reader.ReadUInt32(); var elementType = typeof(T); if (ctorNb != Layer.VectorCtor) - throw new ApplicationException($"Cannot deserialize Vector<{elementType.Name}> with ctor #{ctorNb:x}"); + throw new WTelegram.WTException($"Cannot deserialize Vector<{elementType.Name}> with ctor #{ctorNb:x}"); int count = reader.ReadInt32(); var dict = new Dictionary(count); for (int i = 0; i < count; i++) diff --git a/src/TlsStream.cs b/src/TlsStream.cs index 3391f67..a4b4e9f 100644 --- a/src/TlsStream.cs +++ b/src/TlsStream.cs @@ -27,7 +27,7 @@ namespace WTelegram if (await _innerStream.FullReadAsync(_tlsReadHeader, 5, ct) != 5) return 0; if (_tlsReadHeader[0] != 0x17 || _tlsReadHeader[1] != 0x03 || _tlsReadHeader[2] != 0x03) - throw new ApplicationException("Could not read frame data : Invalid TLS header"); + throw new WTException("Could not read frame data : Invalid TLS header"); _tlsFrameleft = (_tlsReadHeader[3] << 8) + _tlsReadHeader[4]; } var read = await _innerStream.ReadAsync(buffer, offset, Math.Min(count, _tlsFrameleft), ct); @@ -82,7 +82,7 @@ namespace WTelegram } } } - throw new ApplicationException("TLS Handshake failed"); + throw new WTException("TLS Handshake failed"); } static readonly byte[] TlsClientHello1 = new byte[11] {