├── .editorconfig ├── .gitignore ├── .paket ├── .gitignore └── paket.bootstrapper.exe ├── .vscode ├── launch.json └── tasks.json ├── LICENSE.txt ├── Nuget.Config ├── README.md ├── TelegramClient.sln ├── TelegramClient.sln.DotSettings ├── build.fsx ├── build.ps1 ├── paket.dependencies ├── paket.lock ├── src └── TelegramClient.Core │ ├── .editorconfig │ ├── ApiServies │ ├── AuthApiService.cs │ ├── ConnectApiService.cs │ ├── ContactsApiService.cs │ ├── Interfaces │ │ ├── IAuthApiService.cs │ │ ├── IConnectApiService.cs │ │ ├── IContactsApiService.cs │ │ ├── IMessagesApiService.cs │ │ ├── ISenderService.cs │ │ ├── IUpdatesApiService.cs │ │ └── IUploadApiService.cs │ ├── MessagesApiService.cs │ ├── SenderService.cs │ ├── UpdatesApiService.cs │ └── UploadApiService.cs │ ├── Auth │ ├── Step1_PQRequest.cs │ ├── Step2_DHExchange.cs │ └── Step3_CompleteDHExchange.cs │ ├── Client.cs │ ├── ClientFactory.cs │ ├── Exceptions │ └── MissingApiConfigurationException.cs │ ├── FactorySettings.cs │ ├── FodyWeavers.xml │ ├── Helpers │ └── BinaryHelper.cs │ ├── ITelegramClient.cs │ ├── IoC │ ├── ComponentAttribute.cs │ ├── CompositionExtensions.cs │ ├── EDependencyLifecycle.cs │ └── SingleInstanceAttribute.cs │ ├── MTProto │ ├── Crypto │ │ ├── AES.cs │ │ ├── AuthKey.cs │ │ ├── BigInteger.cs │ │ ├── Crc32.cs │ │ ├── Factorizator.cs │ │ ├── RSA.cs │ │ └── Salt.cs │ └── Serializers.cs │ ├── Network │ ├── Confirm │ │ ├── ConfirmationSendService.cs │ │ └── IConfirmationSendService.cs │ ├── Exceptions │ │ ├── AuthRestartException.cs │ │ ├── BadServerSaltException.cs │ │ ├── CloudPasswordNeededException.cs │ │ ├── DataCenterMigrationException.cs │ │ ├── DisconnectedException.cs │ │ ├── FileMigrationException.cs │ │ ├── FloodException.cs │ │ ├── InvalidPhoneCodeException.cs │ │ ├── PhoneMigrationException.cs │ │ └── UserMigrationException.cs │ ├── Interfaces │ │ ├── IMtProtoPlainSender.cs │ │ └── IMtProtoSender.cs │ ├── Recieve │ │ ├── GZipPackedHandler.cs │ │ ├── Interfaces │ │ │ ├── IGZipPackedHandler.cs │ │ │ ├── IRecievingService.cs │ │ │ ├── IResponseResultGetter.cs │ │ │ └── IResponseResultSetter.cs │ │ ├── RecievingService.cs │ │ └── ResponseResultService.cs │ ├── RecieveHandlers │ │ ├── BadMsgNotificationRecieveHandler.cs │ │ ├── BadServerSaltRecieveHandler.cs │ │ ├── FutureSaltsRecieveHandler.cs │ │ ├── Interfaces │ │ │ └── IRecieveHandler.cs │ │ ├── MsgDetailedInfoRecieveHandler.cs │ │ ├── MsgNewDetailedInfoRecieveHandler.cs │ │ ├── MsgsAckRecieveHandler.cs │ │ ├── NewSessionCreatedRecieveHandler.cs │ │ ├── PingRecieveHandler.cs │ │ ├── PongRecieveHandler.cs │ │ ├── RpcResultRecieveHandler.cs │ │ └── UpdatesRecieveHandler.cs │ ├── Send │ │ ├── MtProtoPlainSender.cs │ │ └── MtProtoSendService.cs │ └── Tcp │ │ ├── ITcpService.cs │ │ ├── ITcpTransport.cs │ │ ├── TcpMessage.cs │ │ ├── TcpService.cs │ │ └── TcpTransport.cs │ ├── Sessions │ ├── FileSessionStoreProvider.cs │ ├── ISession.cs │ ├── ISessionStore.cs │ ├── ISessionStoreProvider.cs │ ├── Session.cs │ └── SessionStore.cs │ ├── Settings │ ├── ClientSettings.cs │ └── IClientSettings.cs │ ├── TelegramClient.Core.csproj │ ├── TelegramClient.Core.csproj.DotSettings │ └── Utils │ ├── CRC32.cs │ └── Helpers.cs └── tests ├── TelegramClient.Tests ├── .gitignore ├── LogOutputTester.cs ├── TelegramClient.Tests.csproj ├── TelegramClientTests.cs ├── appsettings.json ├── data │ └── cat.jpg └── log4net.config └── TelegramClient.UnitTests ├── Framework ├── ClientSettingsMock.cs ├── ConfirmationRecieveServiceMock.cs ├── ConfirmationSendServiceMock.cs ├── ContainerExtentions.cs ├── RecieveHandlerMock.cs ├── SessionMock.cs ├── SessionStoreMock.cs ├── TcpServiceMock.cs ├── TcpTransportMock.cs └── TestBase.cs ├── Network ├── MtProtoPlainSenderTests.cs ├── MtProtoSendServiceTests.cs ├── RecieveServiceTests.cs └── TcpTransportTests.cs └── TelegramClient.UnitTests.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = crlf 3 | 4 | [*.{xml,cs}] 5 | indent_style = space -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/isua,visualstudio 2 | 3 | #!! ERROR: isua is undefined. Use list command to see defined gitignore types !!# 4 | 5 | ### VisualStudio ### 6 | ## Ignore Visual Studio temporary files, build results, and 7 | ## files generated by popular Visual Studio add-ons. 8 | 9 | # User-specific files 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | build/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | 30 | # Visual Studio 2015 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # DNX 49 | project.lock.json 50 | artifacts/ 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # NuGet Packages 149 | *.nupkg 150 | # The packages folder can be ignored because of Package Restore 151 | **/packages/* 152 | # except build/, which is used as an MSBuild target. 153 | !**/packages/build/ 154 | # Uncomment if necessary however generally it will be regenerated when needed 155 | #!**/packages/repositories.config 156 | 157 | # Windows Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Windows Store app package directory 162 | AppPackages/ 163 | 164 | # Visual Studio cache files 165 | # files ending in .cache can be ignored 166 | *.[Cc]ache 167 | # but keep track of directories ending in .cache 168 | !*.[Cc]ache/ 169 | 170 | # Others 171 | ClientBin/ 172 | [Ss]tyle[Cc]op.* 173 | ~$* 174 | *~ 175 | *.dbmdl 176 | *.dbproj.schemaview 177 | *.pfx 178 | *.publishsettings 179 | node_modules/ 180 | orleans.codegen.cs 181 | 182 | # RIA/Silverlight projects 183 | Generated_Code/ 184 | 185 | # Backup & report files from converting an old project file 186 | # to a newer Visual Studio version. Backup files are not needed, 187 | # because we have git ;-) 188 | _UpgradeReport_Files/ 189 | Backup*/ 190 | UpgradeLog*.XML 191 | UpgradeLog*.htm 192 | 193 | # SQL Server files 194 | *.mdf 195 | *.ldf 196 | 197 | # Business Intelligence projects 198 | *.rdl.data 199 | *.bim.layout 200 | *.bim_*.settings 201 | 202 | # Microsoft Fakes 203 | FakesAssemblies/ 204 | 205 | # Node.js Tools for Visual Studio 206 | .ntvs_analysis.dat 207 | 208 | # Visual Studio 6 build log 209 | *.plg 210 | 211 | # Visual Studio 6 workspace options file 212 | *.opt 213 | 214 | # Visual Studio LightSwitch build output 215 | **/*.HTMLClient/GeneratedArtifacts 216 | **/*.DesktopClient/GeneratedArtifacts 217 | **/*.DesktopClient/ModelManifest.xml 218 | **/*.Server/GeneratedArtifacts 219 | **/*.Server/ModelManifest.xml 220 | _Pvt_Extensions 221 | /packages/ 222 | /.fake/ 223 | /.idea/ 224 | -------------------------------------------------------------------------------- /.paket/.gitignore: -------------------------------------------------------------------------------- 1 | /paket.exe 2 | -------------------------------------------------------------------------------- /.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vik-borisov/TelegramClient/c967bd2f34e349529af7cbcc2ee11471a6fcbbba/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}\\src\\TelegramClient.Core\\bin\\Debug\\netcoreapp1.1\\TelegramClient.Core.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}\\src\\TelegramClient.Core", 12 | "console": "internalConsole", 13 | "stopAtEntry": false, 14 | "internalConsoleOptions": "openOnSessionStart" 15 | }, 16 | { 17 | "name": ".NET Core Attach", 18 | "type": "coreclr", 19 | "request": "attach", 20 | "processId": "${command:pickProcess}" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [ 10 | "${workspaceRoot}\\src\\TelegramClient.Core\\TelegramClient.Core.csproj" 11 | ], 12 | "isBuildCommand": true, 13 | "problemMatcher": "$msCompile" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Ilya Pirozhenko 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Nuget.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TelegramClient DEPRECATED. 2 | # Instead, use https://github.com/OpenTl/OpenTl.ClientApi 3 | 4 | [![Build status](https://ci.appveyor.com/api/projects/status/uia4ljj11m02omj3?svg=true)](https://ci.appveyor.com/project/vik_borisov/telegramclient) 5 | [![NuGet version](https://badge.fury.io/nu/telegramclientapi.svg)](https://badge.fury.io/nu/telegramclientapi) 6 | [![Telegram group](https://img.shields.io/badge/TELEGRAM-GROUP-green.svg)](https://t.me/joinchat/D1EEGBGwdrHcoNbzXALYPg) 7 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d3458fb536684c8c82553caaf821888c)](https://www.codacy.com/app/OpenTl/TelegramClient?utm_source=github.com&utm_medium=referral&utm_content=vik-borisov/TelegramClient&utm_campaign=Badge_Grade) 8 | 9 | _Unofficial_ Telegram (http://telegram.org) client library implemented in C#. Latest TL scheme supported (73 layer). We used MtProto 2.0 10 | 11 | It's a perfect fit for any developer who would like to send data directly to Telegram users or write own custom Telegram client. 12 | 13 | :star2: If you :heart: library, please star it! :star2: 14 | 15 | # How do I add this to my project? 16 | 17 | Install via NuGet 18 | 19 | ``` 20 | > Install-Package TelegramClientApi 21 | ``` 22 | ## [Quick Start](https://github.com/vik-borisov/TelegramClient/wiki/Quick-Start) 23 | 24 | ## [Available Methods](https://github.com/vik-borisov/TelegramClient/wiki/Supported-methods) 25 | 26 | ## [Migrate to version 0.4](https://github.com/vik-borisov/TelegramClient/wiki/Migrate-to-version-0.4) 27 | 28 | ## [Schema Documentation](https://opentl.github.io/OpenTl.Schema/api/index.html) 29 | 30 | ## [Roadmap](https://github.com/vik-borisov/TelegramClient/milestones) 31 | 32 | ## [FAQ](https://github.com/vik-borisov/TelegramClient/wiki/FAQ) 33 | 34 | #### Nothing helps 35 | Ask your question at gitter or create an issue in project bug tracker. 36 | 37 | **Attach following information**: 38 | 39 | * Full problem description and exception message 40 | * Stack-trace 41 | * Your code that runs in to this exception 42 | 43 | Without information listen above your issue will be closed. 44 | -------------------------------------------------------------------------------- /TelegramClient.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04D496F9-ECE3-4F26-ABF7-FD4511112142}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelegramClient.Core", "src\TelegramClient.Core\TelegramClient.Core.csproj", "{6DDAD7EF-AF3B-40EF-84DF-A364E83F9946}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{78830552-5E51-43BD-B510-7D59813B0CC9}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelegramClient.Tests", "tests\TelegramClient.Tests\TelegramClient.Tests.csproj", "{51FD7794-A861-439A-9CA3-75C4232E9CAC}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sln", "sln", "{679DFB1A-3BA6-450C-A42B-B6E3071EED37}" 15 | ProjectSection(SolutionItems) = preProject 16 | LICENSE.txt = LICENSE.txt 17 | README.md = README.md 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {6DDAD7EF-AF3B-40EF-84DF-A364E83F9946}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {6DDAD7EF-AF3B-40EF-84DF-A364E83F9946}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {6DDAD7EF-AF3B-40EF-84DF-A364E83F9946}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {6DDAD7EF-AF3B-40EF-84DF-A364E83F9946}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {51FD7794-A861-439A-9CA3-75C4232E9CAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {51FD7794-A861-439A-9CA3-75C4232E9CAC}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {51FD7794-A861-439A-9CA3-75C4232E9CAC}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {51FD7794-A861-439A-9CA3-75C4232E9CAC}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(NestedProjects) = preSolution 39 | {6DDAD7EF-AF3B-40EF-84DF-A364E83F9946} = {04D496F9-ECE3-4F26-ABF7-FD4511112142} 40 | {51FD7794-A861-439A-9CA3-75C4232E9CAC} = {78830552-5E51-43BD-B510-7D59813B0CC9} 41 | EndGlobalSection 42 | GlobalSection(ExtensibilityGlobals) = postSolution 43 | SolutionGuid = {7D2DDFF1-C8B9-45A6-98B7-94D467BAA2A9} 44 | EndGlobalSection 45 | GlobalSection(MonoDevelopProperties) = preSolution 46 | Policies = $0 47 | $0.TextStylePolicy = $1 48 | $1.inheritsSet = VisualStudio 49 | $1.inheritsScope = text/plain 50 | $1.scope = text/x-csharp 51 | $0.CSharpFormattingPolicy = $2 52 | $2.IndentSwitchBody = True 53 | $2.IndentBlocksInsideExpressions = True 54 | $2.AnonymousMethodBraceStyle = NextLine 55 | $2.PropertyBraceStyle = NextLine 56 | $2.PropertyGetBraceStyle = NextLine 57 | $2.PropertySetBraceStyle = NextLine 58 | $2.EventBraceStyle = NextLine 59 | $2.EventAddBraceStyle = NextLine 60 | $2.EventRemoveBraceStyle = NextLine 61 | $2.StatementBraceStyle = NextLine 62 | $2.ElseNewLinePlacement = NewLine 63 | $2.CatchNewLinePlacement = NewLine 64 | $2.FinallyNewLinePlacement = NewLine 65 | $2.WhileNewLinePlacement = DoNotCare 66 | $2.ArrayInitializerWrapping = DoNotChange 67 | $2.ArrayInitializerBraceStyle = NextLine 68 | $2.BeforeMethodDeclarationParentheses = False 69 | $2.BeforeMethodCallParentheses = False 70 | $2.BeforeConstructorDeclarationParentheses = False 71 | $2.NewLineBeforeConstructorInitializerColon = NewLine 72 | $2.NewLineAfterConstructorInitializerColon = SameLine 73 | $2.BeforeDelegateDeclarationParentheses = False 74 | $2.NewParentheses = False 75 | $2.SpacesBeforeBrackets = False 76 | $2.inheritsSet = Mono 77 | $2.inheritsScope = text/x-csharp 78 | $2.scope = text/x-csharp 79 | EndGlobalSection 80 | EndGlobal 81 | -------------------------------------------------------------------------------- /build.fsx: -------------------------------------------------------------------------------- 1 | // include Fake lib 2 | #r "packages/FAKE/tools/FakeLib.dll" 3 | open Fake 4 | 5 | // Properties 6 | let buildDir = FullName "./build/" 7 | 8 | let apikey = getBuildParam "apikey" 9 | let version = getBuildParam "version" 10 | 11 | Target "Clean" (fun _ -> 12 | CleanDir buildDir 13 | ) 14 | 15 | Target "Build" (fun _ -> 16 | XMLHelper.XmlPokeInnerText "./src/TelegramClient.Core/TelegramClient.Core.csproj" "/Project/PropertyGroup/Version" version 17 | 18 | DotNetCli.Restore (fun p -> p) 19 | 20 | DotNetCli.Build (fun p -> 21 | { p with 22 | Project = "./src/TelegramClient.Core/TelegramClient.Core.csproj" 23 | Configuration = "Release" 24 | }) 25 | 26 | () 27 | 28 | DotNetCli.Pack (fun p -> 29 | { p with 30 | OutputPath = buildDir 31 | Project = "./src/TelegramClient.Core/TelegramClient.Core.csproj" 32 | }) 33 | ) 34 | 35 | Target "PublishNuget" (fun _ -> 36 | Paket.Push (fun nugetParams -> 37 | { nugetParams with 38 | ApiKey = apikey 39 | WorkingDir = buildDir 40 | } 41 | ) 42 | ) 43 | 44 | Target "Default" (fun _ -> 45 | trace "Hello World from FAKE" 46 | ) 47 | 48 | // Dependencies 49 | "Clean" 50 | ==> "Build" 51 | ==> "PublishNuget" 52 | ==> "Default" 53 | 54 | // start build 55 | RunTargetOrDefault "Default" -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter (Mandatory=$true)] 3 | [string] $version, 4 | 5 | [Parameter (Mandatory=$true)] 6 | [string] $apikey 7 | ) 8 | 9 | function Check-Exit-Code{ 10 | if ($LASTEXITCODE -eq 1) { 11 | exit /b $LASTEXITCODE 12 | } 13 | } 14 | 15 | &".\.paket\paket.bootstrapper.exe" 16 | Check-Exit-Code 17 | 18 | &".\.paket\paket.exe" "restore" 19 | Check-Exit-Code 20 | 21 | &".\packages\FAKE\tools\FAKE.exe" "build.fsx" "version=$version" "apikey=$apikey" 22 | 23 | Check-Exit-Code -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | nuget FAKE 3 | -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | NUGET 2 | remote: https://www.nuget.org/api/v2 3 | FAKE (4.61.1) 4 | -------------------------------------------------------------------------------- /src/TelegramClient.Core/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = crlf 3 | 4 | [*.{xml,cs} ] 5 | indent_style = space 6 | 7 | dotnet_naming_rule.private_members_with_underscore.symbols = private_members 8 | dotnet_naming_rule.private_members_with_underscore.style = underscore_prefix 9 | dotnet_naming_rule.private_members_with_underscore.severity = suggestion 10 | 11 | dotnet_naming_symbols.private_members.applicable_kinds = field 12 | dotnet_naming_symbols.private_members.applicable_accessibilities = private 13 | dotnet_naming_symbols.private_members.required_modifiers = readonly 14 | 15 | dotnet_naming_style.underscore_prefix.capitalization = camel_case 16 | dotnet_naming_style.underscore_prefix.required_prefix = _ -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/AuthApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | using BarsGroup.CodeGuard; 11 | 12 | using OpenTl.Schema; 13 | using OpenTl.Schema.Account; 14 | using OpenTl.Schema.Auth; 15 | 16 | using TelegramClient.Core.ApiServies.Interfaces; 17 | using TelegramClient.Core.IoC; 18 | using TelegramClient.Core.Sessions; 19 | using TelegramClient.Core.Settings; 20 | 21 | using TAuthorization = OpenTl.Schema.Auth.TAuthorization; 22 | 23 | [SingleInstance(typeof(IAuthApiService))] 24 | internal class AuthApiService : IAuthApiService 25 | { 26 | public ISenderService SenderService { get; set; } 27 | 28 | public IClientSettings ClientSettings { get; set; } 29 | 30 | public ISessionStore SessionStore { get; set; } 31 | 32 | public async Task GetPasswordSetting(CancellationToken cancellationToken = default(CancellationToken)) 33 | { 34 | return await SenderService.SendRequestAsync(new RequestGetPassword(), cancellationToken).ConfigureAwait(false); 35 | } 36 | 37 | public async Task IsPhoneRegisteredAsync(string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) 38 | { 39 | Guard.That(phoneNumber, nameof(phoneNumber)).IsNotNullOrWhiteSpace(); 40 | 41 | var authCheckPhoneRequest = new RequestCheckPhone 42 | { 43 | PhoneNumber = phoneNumber 44 | }; 45 | return await SenderService.SendRequestAsync(authCheckPhoneRequest, cancellationToken).ConfigureAwait(false); 46 | } 47 | 48 | public void EnsureUserAuthorized() 49 | { 50 | if (!IsUserAuthorized()) 51 | { 52 | throw new InvalidOperationException("Authorize user first!"); 53 | } 54 | } 55 | 56 | public bool IsUserAuthorized() 57 | { 58 | return ClientSettings.Session.User != null; 59 | } 60 | 61 | public async Task MakeAuthAsync(string phoneNumber, string phoneCodeHash, string code, CancellationToken cancellationToken = default(CancellationToken)) 62 | { 63 | Guard.That(phoneNumber, nameof(phoneNumber)).IsNotNullOrWhiteSpace(); 64 | Guard.That(phoneCodeHash, nameof(phoneCodeHash)).IsNotNullOrWhiteSpace(); 65 | Guard.That(code, nameof(code)).IsNotNullOrWhiteSpace(); 66 | 67 | var request = new RequestSignIn 68 | { 69 | PhoneNumber = phoneNumber, 70 | PhoneCodeHash = phoneCodeHash, 71 | PhoneCode = code 72 | }; 73 | 74 | var result = (TAuthorization)await SenderService.SendRequestAsync(request, cancellationToken).ConfigureAwait(false); 75 | 76 | var user = result.User.Cast(); 77 | 78 | await OnUserAuthenticated(user).ConfigureAwait(false); 79 | 80 | return user; 81 | } 82 | 83 | public async Task MakeAuthWithPasswordAsync(TPassword password, string passwordStr, CancellationToken cancellationToken = default(CancellationToken)) 84 | { 85 | var passwordBytes = Encoding.UTF8.GetBytes(passwordStr); 86 | var rv = password.CurrentSalt.Concat(passwordBytes).Concat(password.CurrentSalt); 87 | 88 | byte[] passwordHash; 89 | using (var sha = SHA256.Create()) 90 | { 91 | passwordHash = sha.ComputeHash(rv.ToArray()); 92 | } 93 | 94 | var request = new RequestCheckPassword 95 | { 96 | PasswordHash = passwordHash 97 | }; 98 | var result = (TAuthorization)await SenderService.SendRequestAsync(request, cancellationToken).ConfigureAwait(false); 99 | 100 | var user = result.User.As(); 101 | 102 | await OnUserAuthenticated(user).ConfigureAwait(false); 103 | 104 | return user; 105 | } 106 | 107 | public async Task SendCodeRequestAsync(string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) 108 | { 109 | Guard.That(phoneNumber, nameof(phoneNumber)).IsNotNullOrWhiteSpace(); 110 | 111 | var request = new RequestSendCode 112 | { 113 | PhoneNumber = phoneNumber, 114 | ApiId = ClientSettings.AppId, 115 | ApiHash = ClientSettings.AppHash 116 | }; 117 | return await SenderService.SendRequestAsync(request, cancellationToken).ConfigureAwait(false); 118 | } 119 | 120 | public async Task SignUpAsync(string phoneNumber, string phoneCodeHash, string code, string firstName, string lastName, CancellationToken cancellationToken = default(CancellationToken)) 121 | { 122 | var request = new RequestSignUp 123 | { 124 | PhoneNumber = phoneNumber, 125 | PhoneCode = code, 126 | PhoneCodeHash = phoneCodeHash, 127 | FirstName = firstName, 128 | LastName = lastName 129 | }; 130 | var result = (TAuthorization)await SenderService.SendRequestAsync(request, cancellationToken).ConfigureAwait(false); 131 | 132 | var user = result.User.Cast(); 133 | 134 | await OnUserAuthenticated(user).ConfigureAwait(false); 135 | return user; 136 | } 137 | 138 | private async Task OnUserAuthenticated(TUser tlUser) 139 | { 140 | var session = ClientSettings.Session; 141 | Guard.That(session).IsNotNull(); 142 | 143 | session.User = tlUser; 144 | session.SessionExpires = int.MaxValue; 145 | 146 | await SessionStore.Save().ConfigureAwait(false); 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/ConnectApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using log4net; 9 | 10 | using OpenTl.Schema; 11 | using OpenTl.Schema.Auth; 12 | using OpenTl.Schema.Help; 13 | 14 | using TelegramClient.Core.ApiServies.Interfaces; 15 | using TelegramClient.Core.Auth; 16 | using TelegramClient.Core.IoC; 17 | using TelegramClient.Core.Network.Interfaces; 18 | using TelegramClient.Core.Network.Recieve.Interfaces; 19 | using TelegramClient.Core.Sessions; 20 | using TelegramClient.Core.Settings; 21 | 22 | [SingleInstance(typeof(IConnectApiService))] 23 | internal class ConnectApiService : IConnectApiService 24 | { 25 | private static readonly ILog Log = LogManager.GetLogger(typeof(ConnectApiService)); 26 | 27 | private TDcOption[] _dcOptions; 28 | 29 | private Timer _timer; 30 | 31 | public IClientSettings ClientSettings { get; set; } 32 | 33 | public ISessionStore SessionStore { get; set; } 34 | 35 | public IMtProtoPlainSender MtProtoPlainSender { get; set; } 36 | 37 | public IRecievingService ProtoRecieveService { get; set; } 38 | 39 | public ISenderService SendService { get; set; } 40 | 41 | public Task ConnectAsync() 42 | { 43 | return ConnectAsync(false); 44 | } 45 | 46 | public async Task LogOut() 47 | { 48 | var request = new RequestLogOut(); 49 | SendService.SendRequestAsync(request).ConfigureAwait(false); 50 | 51 | await SessionStore.Remove().ConfigureAwait(false); 52 | ClientSettings.Session.AuthKey = null; 53 | } 54 | 55 | public Task ReAuthenticateAsync() 56 | { 57 | return ConnectAsync(true); 58 | } 59 | 60 | public Task PingAsync() 61 | { 62 | return SendService.SendRequestAsync(new RequestPing()); 63 | } 64 | 65 | public async Task ReconnectToDcAsync(int dcId) 66 | { 67 | if (_dcOptions == null || !_dcOptions.Any()) 68 | { 69 | throw new InvalidOperationException($"Can't reconnect. Establish initial connection first."); 70 | } 71 | 72 | var dc = _dcOptions.First(d => d.Id == dcId); 73 | 74 | ClientSettings.Session.ServerAddress = dc.IpAddress; 75 | ClientSettings.Session.Port = dc.Port; 76 | 77 | await ConnectAsync(true).ConfigureAwait(false); 78 | } 79 | 80 | private async Task ConnectAsync(bool forceAuth) 81 | { 82 | if (ClientSettings.Session.AuthKey == null || ClientSettings.Session.AuthKey.Data.Length == 0 || forceAuth) 83 | { 84 | var result = await DoAuthentication().ConfigureAwait(false); 85 | ClientSettings.Session.AuthKey = result.AuthKey; 86 | ClientSettings.Session.TimeOffset = result.TimeOffset; 87 | 88 | await SessionStore.Save().ConfigureAwait(false); 89 | } 90 | 91 | ProtoRecieveService.StartReceiving(); 92 | 93 | 94 | //set-up layer 95 | var request = new RequestInvokeWithLayer 96 | { 97 | Layer = SchemaInfo.SchemaVersion, 98 | Query = new RequestInitConnection 99 | { 100 | ApiId = ClientSettings.AppId, 101 | AppVersion = "1.0.0", 102 | DeviceModel = "PC", 103 | LangCode = "en", 104 | LangPack = "tdesktop", 105 | SystemLangCode = "en", 106 | Query = new RequestGetConfig(), 107 | SystemVersion = "Win 10.0" 108 | } 109 | }; 110 | 111 | var response = (TConfig)await SendService.SendRequestAsync(request).ConfigureAwait(false); 112 | _dcOptions = response.DcOptions.Items.Cast().ToArray(); 113 | 114 | _timer = new Timer(_ => PingAsync(), null, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60)); 115 | } 116 | 117 | private async Task DoAuthentication() 118 | { 119 | Log.Info("Try do authentication"); 120 | 121 | var step1 = new Step1PqRequest(); 122 | var step1Result = await MtProtoPlainSender.SendAndReceive(step1.ToBytes()).ConfigureAwait(false); 123 | var step1Response = step1.FromBytes(step1Result); 124 | 125 | Log.Debug("First step is done"); 126 | 127 | var step2 = new Step2DhExchange(); 128 | var step2Result = await MtProtoPlainSender.SendAndReceive( 129 | step2.ToBytes( 130 | step1Response.Nonce, 131 | step1Response.ServerNonce, 132 | step1Response.Fingerprints, 133 | step1Response.Pq)).ConfigureAwait(false); 134 | var step2Response = step2.FromBytes(step2Result); 135 | 136 | Log.Debug("Second step is done"); 137 | 138 | var step3 = new Step3CompleteDhExchange(); 139 | var step3Result = await MtProtoPlainSender.SendAndReceive( 140 | step3.ToBytes( 141 | step2Response.Nonce, 142 | step2Response.ServerNonce, 143 | step2Response.NewNonce, 144 | step2Response.EncryptedAnswer)) 145 | .ConfigureAwait(false); 146 | var step3Response = step3.FromBytes(step3Result); 147 | 148 | Log.Debug("Third step is done"); 149 | 150 | return step3Response; 151 | } 152 | 153 | public void Dispose() 154 | { 155 | _timer?.Dispose(); 156 | ProtoRecieveService?.Dispose(); 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/ContactsApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | using OpenTl.Schema.Contacts; 7 | 8 | using TelegramClient.Core.ApiServies.Interfaces; 9 | using TelegramClient.Core.IoC; 10 | 11 | [SingleInstance(typeof(IContactsApiService))] 12 | internal class ContactsApiService : IContactsApiService 13 | { 14 | public IAuthApiService AuthApiService { get; set; } 15 | 16 | public ISenderService SenderService { get; set; } 17 | 18 | public async Task GetContactsAsync(CancellationToken cancellationToken = default(CancellationToken)) 19 | { 20 | AuthApiService.EnsureUserAuthorized(); 21 | 22 | var req = new RequestGetContacts { Hash = 0 }; 23 | 24 | return await SenderService.SendRequestAsync(req, cancellationToken).ConfigureAwait(false); 25 | } 26 | 27 | /// Serch user or chat. 28 | /// User or chat name 29 | /// Max result count 30 | /// A cancellation token 31 | /// 32 | public async Task SearchUserAsync(string q, int limit = 10, CancellationToken cancellationToken = default(CancellationToken)) 33 | { 34 | AuthApiService.EnsureUserAuthorized(); 35 | 36 | var r = new RequestSearch 37 | { 38 | Q = q, 39 | Limit = limit 40 | }; 41 | 42 | return await SenderService.SendRequestAsync(r, cancellationToken).ConfigureAwait(false); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/Interfaces/IAuthApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies.Interfaces 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | using OpenTl.Schema; 7 | using OpenTl.Schema.Account; 8 | using OpenTl.Schema.Auth; 9 | 10 | public interface IAuthApiService 11 | { 12 | Task GetPasswordSetting(CancellationToken cancellationToken = default(CancellationToken)); 13 | 14 | Task IsPhoneRegisteredAsync(string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)); 15 | 16 | bool IsUserAuthorized(); 17 | 18 | void EnsureUserAuthorized(); 19 | 20 | Task MakeAuthAsync(string phoneNumber, string phoneCodeHash, string code, CancellationToken cancellationToken = default(CancellationToken)); 21 | 22 | Task MakeAuthWithPasswordAsync(TPassword password, string passwordStr, CancellationToken cancellationToken = default(CancellationToken)); 23 | 24 | Task SendCodeRequestAsync(string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)); 25 | 26 | Task SignUpAsync(string phoneNumber, string phoneCodeHash, string code, string firstName, string lastName, CancellationToken cancellationToken = default(CancellationToken)); 27 | } 28 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/Interfaces/IConnectApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies.Interfaces 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using OpenTl.Schema; 7 | 8 | public interface IConnectApiService: IDisposable 9 | { 10 | Task ConnectAsync(); 11 | 12 | Task LogOut(); 13 | 14 | Task PingAsync(); 15 | 16 | Task ReAuthenticateAsync(); 17 | 18 | Task ReconnectToDcAsync(int dcId); 19 | } 20 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/Interfaces/IContactsApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies.Interfaces 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | using OpenTl.Schema.Contacts; 7 | 8 | public interface IContactsApiService 9 | { 10 | Task GetContactsAsync(CancellationToken cancellationToken = default(CancellationToken)); 11 | 12 | /// Serch user or chat. API: contacts.search#11f812d8 q:string limit:int = contacts.Found; By default the limit is 13 | /// 10. 14 | /// User or chat name 15 | /// Max result count 16 | /// 17 | Task SearchUserAsync(string q, int limit = 10, CancellationToken cancellationToken = default(CancellationToken)); 18 | } 19 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/Interfaces/ISenderService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies.Interfaces 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | using OpenTl.Schema; 7 | 8 | public interface ISenderService 9 | { 10 | Task SendRequestAsync(IRequest methodToExecute, CancellationToken cancellationToken = default(CancellationToken)); 11 | } 12 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/Interfaces/IUpdatesApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies.Interfaces 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | using OpenTl.Schema; 7 | using OpenTl.Schema.Updates; 8 | 9 | public delegate Task UpdateHandler(IUpdates update); 10 | 11 | public interface IUpdatesApiService 12 | { 13 | Task GetCurrentState(CancellationToken cancellationToken = default(CancellationToken)); 14 | 15 | Task GetUpdates(IState currentState, CancellationToken cancellationToken = default(CancellationToken)); 16 | 17 | event UpdateHandler RecieveUpdates; 18 | } 19 | 20 | internal interface IUpdatesApiServiceRaiser 21 | { 22 | Task OnUpdateRecieve(IUpdates message); 23 | } 24 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/Interfaces/IUploadApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies.Interfaces 2 | { 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | using OpenTl.Schema; 8 | using OpenTl.Schema.Upload; 9 | 10 | public interface IUploadApiService 11 | { 12 | Task GetFile(IInputFileLocation location, int offset = 0, CancellationToken cancellationToken = default(CancellationToken)); 13 | 14 | Task UploadFile(string name, StreamReader reader, CancellationToken cancellationToken = default(CancellationToken)); 15 | } 16 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/SenderService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | using log4net; 8 | 9 | using OpenTl.Schema; 10 | 11 | using TelegramClient.Core.ApiServies.Interfaces; 12 | using TelegramClient.Core.IoC; 13 | using TelegramClient.Core.Network.Exceptions; 14 | using TelegramClient.Core.Network.Interfaces; 15 | using TelegramClient.Core.Network.Recieve.Interfaces; 16 | 17 | [SingleInstance(typeof(ISenderService))] 18 | internal class SenderService : ISenderService 19 | { 20 | private static readonly ILog Log = LogManager.GetLogger(typeof(SenderService)); 21 | 22 | public IMtProtoSender Sender { get; set; } 23 | 24 | public async Task SendRequestAsync(IRequest methodToExecute, CancellationToken cancellationToken = default(CancellationToken)) 25 | { 26 | while (true) 27 | { 28 | Log.Debug($"Send message of the constructor {methodToExecute}"); 29 | 30 | try 31 | { 32 | return (TResult)await await Sender.SendAndWaitResponse(methodToExecute, cancellationToken).ConfigureAwait(false); 33 | } 34 | catch (BadServerSaltException) 35 | { 36 | } 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/UpdatesApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | using OpenTl.Schema; 7 | using OpenTl.Schema.Updates; 8 | 9 | using TelegramClient.Core.ApiServies.Interfaces; 10 | using TelegramClient.Core.IoC; 11 | 12 | [SingleInstance(typeof(IUpdatesApiService), typeof(IUpdatesApiServiceRaiser))] 13 | internal class UpdatesApiService : IUpdatesApiService, 14 | IUpdatesApiServiceRaiser 15 | { 16 | public IAuthApiService AuthApiService { get; set; } 17 | 18 | public ISenderService SenderService { get; set; } 19 | 20 | public async Task GetCurrentState(CancellationToken cancellationToken = default(CancellationToken)) 21 | { 22 | AuthApiService.EnsureUserAuthorized(); 23 | 24 | return await SenderService.SendRequestAsync(new RequestGetState(), cancellationToken).ConfigureAwait(false); 25 | } 26 | 27 | public async Task GetUpdates(IState currentState, CancellationToken cancellationToken = default(CancellationToken)) 28 | { 29 | AuthApiService.EnsureUserAuthorized(); 30 | 31 | var getDiffRequest = new RequestGetDifference 32 | { 33 | Pts = currentState.Pts, 34 | Qts = currentState.Qts, 35 | Date = currentState.Date 36 | }; 37 | 38 | return await SenderService.SendRequestAsync(getDiffRequest, cancellationToken).ConfigureAwait(false); 39 | } 40 | 41 | public async Task OnUpdateRecieve(IUpdates message) 42 | { 43 | if (RecieveUpdates != null) 44 | { 45 | await RecieveUpdates.Invoke(message).ConfigureAwait(false); 46 | } 47 | } 48 | 49 | public event UpdateHandler RecieveUpdates; 50 | } 51 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ApiServies/UploadApiService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.ApiServies 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | using OpenTl.Schema; 12 | using OpenTl.Schema.Auth; 13 | using OpenTl.Schema.Upload; 14 | 15 | using TelegramClient.Core.ApiServies.Interfaces; 16 | using TelegramClient.Core.IoC; 17 | using TelegramClient.Core.Network.Exceptions; 18 | using TelegramClient.Core.Settings; 19 | using TelegramClient.Core.Utils; 20 | 21 | [SingleInstance(typeof(IUploadApiService))] 22 | internal class UploadApiService : IUploadApiService 23 | { 24 | private readonly int DownloadDocumentPartSize = 128 * 1024; // 128kb for document 25 | 26 | private readonly int DownloadPhotoPartSize = 64 * 1024; // 64kb for photo 27 | 28 | public ISenderService SenderService { get; set; } 29 | 30 | public IConnectApiService ConnectApiService { get; set; } 31 | 32 | public IClientSettings ClientSettings { get; set; } 33 | 34 | public IAuthApiService AuthApiService { get; set; } 35 | 36 | public async Task GetFile(IInputFileLocation location, int offset = 0, CancellationToken cancellationToken = default(CancellationToken)) 37 | { 38 | AuthApiService.EnsureUserAuthorized(); 39 | 40 | int filePartSize; 41 | if (location is TInputDocumentFileLocation) 42 | { 43 | filePartSize = DownloadDocumentPartSize; 44 | } 45 | else 46 | { 47 | filePartSize = DownloadPhotoPartSize; 48 | } 49 | 50 | try 51 | { 52 | return await SenderService.SendRequestAsync( 53 | new RequestGetFile 54 | { 55 | Location = location, 56 | Limit = filePartSize, 57 | Offset = offset 58 | }, 59 | cancellationToken).ConfigureAwait(false); 60 | } 61 | catch (FileMigrationException ex) 62 | { 63 | var exportedAuth = (TExportedAuthorization)await SenderService.SendRequestAsync( 64 | new RequestExportAuthorization 65 | { 66 | DcId = ex.Dc 67 | }, 68 | cancellationToken).ConfigureAwait(false); 69 | 70 | var authKey = ClientSettings.Session.AuthKey; 71 | var timeOffset = ClientSettings.Session.TimeOffset; 72 | var serverAddress = ClientSettings.Session.ServerAddress; 73 | var serverPort = ClientSettings.Session.Port; 74 | 75 | await ConnectApiService.ReconnectToDcAsync(ex.Dc).ConfigureAwait(false); 76 | await SenderService.SendRequestAsync( 77 | new RequestImportAuthorization 78 | { 79 | Bytes = exportedAuth.Bytes, 80 | Id = exportedAuth.Id 81 | }, 82 | cancellationToken).ConfigureAwait(false); 83 | var result = await GetFile(location, offset, cancellationToken).ConfigureAwait(false); 84 | 85 | ClientSettings.Session.AuthKey = authKey; 86 | ClientSettings.Session.TimeOffset = timeOffset; 87 | ClientSettings.Session.ServerAddress = serverAddress; 88 | ClientSettings.Session.Port = serverPort; 89 | await ConnectApiService.ConnectAsync().ConfigureAwait(false); 90 | 91 | return result; 92 | } 93 | } 94 | 95 | public async Task UploadFile(string name, StreamReader reader, CancellationToken cancellationToken = default(CancellationToken)) 96 | { 97 | AuthApiService.EnsureUserAuthorized(); 98 | 99 | const long TenMb = 10 * 1024 * 1024; 100 | var isBigFileUpload = reader.BaseStream.Length >= TenMb; 101 | 102 | var file = await GetFile(reader).ConfigureAwait(false); 103 | var fileParts = GetFileParts(file); 104 | 105 | var partNumber = 0; 106 | var partsCount = fileParts.Count; 107 | var fileId = BitConverter.ToInt64(TlHelpers.GenerateRandomBytes(8), 0); 108 | while (fileParts.Count != 0) 109 | { 110 | var part = fileParts.Dequeue(); 111 | 112 | if (isBigFileUpload) 113 | { 114 | await SenderService.SendRequestAsync( 115 | new RequestSaveBigFilePart 116 | { 117 | FileId = fileId, 118 | FilePart = partNumber, 119 | Bytes = part, 120 | FileTotalParts = partsCount 121 | }, 122 | cancellationToken).ConfigureAwait(false); 123 | } 124 | else 125 | { 126 | await SenderService.SendRequestAsync( 127 | new RequestSaveFilePart 128 | { 129 | FileId = fileId, 130 | FilePart = partNumber, 131 | Bytes = part 132 | }, 133 | cancellationToken).ConfigureAwait(false); 134 | } 135 | 136 | partNumber++; 137 | } 138 | 139 | if (isBigFileUpload) 140 | { 141 | return new TInputFileBig 142 | { 143 | Id = fileId, 144 | Name = name, 145 | Parts = partsCount 146 | }; 147 | } 148 | 149 | return new TInputFile 150 | { 151 | Id = fileId, 152 | Name = name, 153 | Parts = partsCount, 154 | Md5Checksum = GetFileHash(file) 155 | }; 156 | } 157 | 158 | private static async Task GetFile(StreamReader reader) 159 | { 160 | var file = new byte[reader.BaseStream.Length]; 161 | 162 | using (reader) 163 | { 164 | await reader.BaseStream.ReadAsync(file, 0, (int)reader.BaseStream.Length).ConfigureAwait(false); 165 | } 166 | 167 | return file; 168 | } 169 | 170 | private static string GetFileHash(byte[] data) 171 | { 172 | string md5Checksum; 173 | using (var md5 = MD5.Create()) 174 | { 175 | var hash = md5.ComputeHash(data); 176 | var hashResult = new StringBuilder(hash.Length * 2); 177 | 178 | foreach (var t in hash) 179 | { 180 | hashResult.Append(t.ToString("x2")); 181 | } 182 | 183 | md5Checksum = hashResult.ToString(); 184 | } 185 | 186 | return md5Checksum; 187 | } 188 | 189 | private static Queue GetFileParts(byte[] file) 190 | { 191 | var fileParts = new Queue(); 192 | 193 | const int MaxFilePart = 512 * 1024; 194 | 195 | using (var stream = new MemoryStream(file)) 196 | { 197 | while (stream.Position != stream.Length) 198 | { 199 | if (stream.Length - stream.Position > MaxFilePart) 200 | { 201 | var temp = new byte[MaxFilePart]; 202 | stream.Read(temp, 0, MaxFilePart); 203 | fileParts.Enqueue(temp); 204 | } 205 | else 206 | { 207 | var length = stream.Length - stream.Position; 208 | var temp = new byte[length]; 209 | stream.Read(temp, 0, (int)length); 210 | fileParts.Enqueue(temp); 211 | } 212 | } 213 | } 214 | 215 | return fileParts; 216 | } 217 | } 218 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Auth/Step1_PQRequest.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Auth 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | using TelegramClient.Core.MTProto; 9 | using TelegramClient.Core.MTProto.Crypto; 10 | 11 | public class Step1Response 12 | { 13 | public byte[] Nonce { get; set; } 14 | 15 | public byte[] ServerNonce { get; set; } 16 | 17 | public BigInteger Pq { get; set; } 18 | 19 | public List Fingerprints { get; set; } 20 | } 21 | 22 | public class Step1PqRequest 23 | { 24 | private readonly byte[] _nonce; 25 | 26 | public Step1PqRequest() 27 | { 28 | _nonce = new byte[16]; 29 | } 30 | 31 | public Step1Response FromBytes(byte[] bytes) 32 | { 33 | var fingerprints = new List(); 34 | 35 | using (var memoryStream = new MemoryStream(bytes, false)) 36 | { 37 | using (var binaryReader = new BinaryReader(memoryStream)) 38 | { 39 | const int responseConstructorNumber = 0x05162463; 40 | var responseCode = binaryReader.ReadInt32(); 41 | if (responseCode != responseConstructorNumber) 42 | { 43 | throw new InvalidOperationException($"invalid response code: {responseCode}"); 44 | } 45 | 46 | var nonceFromServer = binaryReader.ReadBytes(16); 47 | 48 | if (!nonceFromServer.SequenceEqual(_nonce)) 49 | { 50 | throw new InvalidOperationException("invalid nonce from server"); 51 | } 52 | 53 | var serverNonce = binaryReader.ReadBytes(16); 54 | 55 | var pqbytes = Serializers.Bytes.Read(binaryReader); 56 | var pq = new BigInteger(1, pqbytes); 57 | 58 | var vectorId = binaryReader.ReadInt32(); 59 | const int vectorConstructorNumber = 0x1cb5c415; 60 | if (vectorId != vectorConstructorNumber) 61 | { 62 | throw new InvalidOperationException($"Invalid vector constructor number {vectorId}"); 63 | } 64 | 65 | var fingerprintCount = binaryReader.ReadInt32(); 66 | for (var i = 0; i < fingerprintCount; i++) 67 | { 68 | var fingerprint = binaryReader.ReadBytes(8); 69 | fingerprints.Add(fingerprint); 70 | } 71 | 72 | return new Step1Response 73 | { 74 | Fingerprints = fingerprints, 75 | Nonce = _nonce, 76 | Pq = pq, 77 | ServerNonce = serverNonce 78 | }; 79 | } 80 | } 81 | } 82 | 83 | public byte[] ToBytes() 84 | { 85 | new Random().NextBytes(_nonce); 86 | const int ConstructorNumber = 0x60469778; 87 | 88 | using (var memoryStream = new MemoryStream()) 89 | { 90 | using (var binaryWriter = new BinaryWriter(memoryStream)) 91 | { 92 | binaryWriter.Write(ConstructorNumber); 93 | binaryWriter.Write(_nonce); 94 | } 95 | 96 | return memoryStream.ToArray(); 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Auth/Step2_DHExchange.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Auth 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | using TelegramClient.Core.MTProto; 8 | using TelegramClient.Core.MTProto.Crypto; 9 | 10 | public class Step2Response 11 | { 12 | public byte[] Nonce { get; set; } 13 | 14 | public byte[] ServerNonce { get; set; } 15 | 16 | public byte[] NewNonce { get; set; } 17 | 18 | public byte[] EncryptedAnswer { get; set; } 19 | } 20 | 21 | public class Step2DhExchange 22 | { 23 | private readonly byte[] _newNonce; 24 | 25 | public Step2DhExchange() 26 | { 27 | _newNonce = new byte[32]; 28 | } 29 | 30 | public Step2Response FromBytes(byte[] response) 31 | { 32 | using (var responseStream = new MemoryStream(response, false)) 33 | { 34 | using (var responseReader = new BinaryReader(responseStream)) 35 | { 36 | var responseCode = responseReader.ReadUInt32(); 37 | 38 | if (responseCode == 0x79cb045d) 39 | { 40 | throw new InvalidOperationException("server_DH_params_fail: TODO"); 41 | } 42 | 43 | if (responseCode != 0xd0e8075c) 44 | { 45 | throw new InvalidOperationException($"invalid response code: {responseCode}"); 46 | } 47 | 48 | var nonceFromServer = responseReader.ReadBytes(16); 49 | 50 | // TODO:! 51 | /* 52 | if (!nonceFromServer.SequenceEqual(nonce)) 53 | { 54 | logger.debug("invalid nonce from server"); 55 | return null; 56 | } 57 | */ 58 | 59 | var serverNonceFromServer = responseReader.ReadBytes(16); 60 | 61 | // TODO: ! 62 | /* 63 | if (!serverNonceFromServer.SequenceEqual(serverNonce)) 64 | { 65 | logger.error("invalid server nonce from server"); 66 | return null; 67 | } 68 | */ 69 | 70 | var encryptedAnswer = Serializers.Bytes.Read(responseReader); 71 | 72 | return new Step2Response 73 | { 74 | EncryptedAnswer = encryptedAnswer, 75 | ServerNonce = serverNonceFromServer, 76 | Nonce = nonceFromServer, 77 | NewNonce = _newNonce 78 | }; 79 | } 80 | } 81 | } 82 | 83 | public byte[] ToBytes(byte[] nonce, byte[] serverNonce, List fingerprints, BigInteger pq) 84 | { 85 | new Random().NextBytes(_newNonce); 86 | 87 | var pqPair = Factorizator.Factorize(pq); 88 | 89 | byte[] reqDhParamsBytes; 90 | 91 | using (var pqInnerData = new MemoryStream(255)) 92 | { 93 | using (var pqInnerDataWriter = new BinaryWriter(pqInnerData)) 94 | { 95 | pqInnerDataWriter.Write(0x83c95aec); // pq_inner_data 96 | Serializers.Bytes.Write(pqInnerDataWriter, pq.ToByteArrayUnsigned()); 97 | Serializers.Bytes.Write(pqInnerDataWriter, pqPair.Min.ToByteArrayUnsigned()); 98 | Serializers.Bytes.Write(pqInnerDataWriter, pqPair.Max.ToByteArrayUnsigned()); 99 | pqInnerDataWriter.Write(nonce); 100 | pqInnerDataWriter.Write(serverNonce); 101 | pqInnerDataWriter.Write(_newNonce); 102 | 103 | byte[] ciphertext = null; 104 | byte[] targetFingerprint = null; 105 | foreach (var fingerprint in fingerprints) 106 | { 107 | pqInnerData.TryGetBuffer(out var buffer); 108 | ciphertext = Rsa.Encrypt(BitConverter.ToString(fingerprint).Replace("-", string.Empty), buffer.Array, 0, (int)pqInnerData.Position); 109 | if (ciphertext != null) 110 | { 111 | targetFingerprint = fingerprint; 112 | break; 113 | } 114 | } 115 | 116 | if (ciphertext == null) 117 | { 118 | throw new InvalidOperationException( 119 | string.Format( 120 | "not found valid key for fingerprints: {0}", 121 | string.Join(", ", fingerprints))); 122 | } 123 | 124 | using (var reqDhParams = new MemoryStream(1024)) 125 | { 126 | using (var reqDhParamsWriter = new BinaryWriter(reqDhParams)) 127 | { 128 | reqDhParamsWriter.Write(0xd712e4be); // req_dh_params 129 | reqDhParamsWriter.Write(nonce); 130 | reqDhParamsWriter.Write(serverNonce); 131 | Serializers.Bytes.Write(reqDhParamsWriter, pqPair.Min.ToByteArrayUnsigned()); 132 | Serializers.Bytes.Write(reqDhParamsWriter, pqPair.Max.ToByteArrayUnsigned()); 133 | reqDhParamsWriter.Write(targetFingerprint); 134 | Serializers.Bytes.Write(reqDhParamsWriter, ciphertext); 135 | 136 | reqDhParamsBytes = reqDhParams.ToArray(); 137 | } 138 | } 139 | } 140 | 141 | return reqDhParamsBytes; 142 | } 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Client.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core 2 | { 3 | using System; 4 | 5 | using Castle.Windsor; 6 | 7 | using TelegramClient.Core.ApiServies.Interfaces; 8 | using TelegramClient.Core.IoC; 9 | 10 | [SingleInstance(typeof(ITelegramClient))] 11 | internal class Client : ITelegramClient 12 | { 13 | public ISenderService SendService { get; set; } 14 | 15 | public IUpdatesApiService UpdatesService { get; set; } 16 | 17 | public IConnectApiService ConnectService { get; set; } 18 | 19 | public IAuthApiService AuthService { get; set; } 20 | 21 | public IMessagesApiService MessagesService { get; set; } 22 | 23 | public IContactsApiService ContactsService { get; set; } 24 | 25 | public IUploadApiService UploadService { get; set; } 26 | 27 | private IWindsorContainer Container { get; set; } 28 | 29 | public void Dispose() 30 | { 31 | Container?.Dispose(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ClientFactory.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | 9 | using BarsGroup.CodeGuard; 10 | 11 | using Castle.MicroKernel.Registration; 12 | using Castle.Windsor; 13 | 14 | using TelegramClient.Core.IoC; 15 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 16 | using TelegramClient.Core.Sessions; 17 | using TelegramClient.Core.Settings; 18 | 19 | public static class ClientFactory 20 | { 21 | public static async Task BuildClient(IFactorySettings factorySettings) 22 | { 23 | var container = RegisterDependency(); 24 | 25 | await FillSettings(container, factorySettings).ConfigureAwait(false); 26 | 27 | return container.Resolve(); 28 | } 29 | 30 | private static async Task FillSettings(IWindsorContainer container, IFactorySettings factorySettings) 31 | { 32 | Guard.That(factorySettings.Id).IsPositive(); 33 | Guard.That(factorySettings.Hash).IsNotNullOrWhiteSpace(); 34 | Guard.That(factorySettings.ServerAddress).IsNotNullOrWhiteSpace(); 35 | Guard.That(factorySettings.ServerPort).IsPositive(); 36 | 37 | var settings = container.Resolve(); 38 | 39 | settings.AppId = factorySettings.Id; 40 | settings.AppHash = factorySettings.Hash; 41 | 42 | container.Register(Component.For().Instance(factorySettings.StoreProvider)); 43 | 44 | var sessionStore = container.Resolve(); 45 | 46 | settings.Session = await TryLoadOrCreateNew(sessionStore, factorySettings).ConfigureAwait(false); 47 | } 48 | 49 | private static IWindsorContainer RegisterDependency() 50 | { 51 | var container = new WindsorContainer(); 52 | 53 | container.RegisterAttibuteRegistration(typeof(ClientFactory).GetTypeInfo().Assembly); 54 | 55 | container.Register( 56 | Component.For>().UsingFactoryMethod( 57 | kernel => 58 | { 59 | var handlerMap = new Dictionary(); 60 | 61 | var allHandlers = kernel.ResolveAll(); 62 | foreach (var handler in allHandlers.ToArray()) 63 | { 64 | foreach (var handleCode in handler.HandleCodes) 65 | { 66 | handlerMap[handleCode] = handler; 67 | } 68 | } 69 | 70 | return handlerMap; 71 | })); 72 | 73 | return container; 74 | } 75 | 76 | private static async Task TryLoadOrCreateNew(ISessionStore sessionStore, IFactorySettings factorySettings) 77 | { 78 | var session = await sessionStore.Load().ConfigureAwait(false) ?? Session.Create(); 79 | 80 | session.ServerAddress = factorySettings.ServerAddress; 81 | session.Port = factorySettings.ServerPort; 82 | 83 | return session; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Exceptions/MissingApiConfigurationException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Exceptions 2 | { 3 | using System; 4 | 5 | public class MissingApiConfigurationException : Exception 6 | { 7 | public const string InfoUrl = "https://github.com/sochix/TLSharp#quick-configuration"; 8 | 9 | internal MissingApiConfigurationException(string invalidParamName) : 10 | base($"Your {invalidParamName} setting is missing. Adjust the configuration first, see {InfoUrl}") 11 | { 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/FactorySettings.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core 2 | { 3 | using TelegramClient.Core.Sessions; 4 | 5 | public interface IFactorySettings 6 | { 7 | int Id { get; set; } 8 | 9 | string Hash { get; set; } 10 | 11 | string ServerAddress { get; set; } 12 | 13 | int ServerPort { get; set; } 14 | 15 | ISessionStoreProvider StoreProvider { get; set; } 16 | } 17 | 18 | public class FactorySettings : IFactorySettings 19 | { 20 | public int Id { get; set; } 21 | 22 | public string Hash { get; set; } 23 | 24 | public string ServerAddress { get; set; } 25 | 26 | public int ServerPort { get; set; } 27 | 28 | public ISessionStoreProvider StoreProvider { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/TelegramClient.Core/Helpers/BinaryHelper.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Helpers 2 | { 3 | using System; 4 | using System.IO; 5 | 6 | internal static class BinaryHelper 7 | { 8 | public static byte[] WriteBytes(Action action) 9 | { 10 | using (var memoryStream = new MemoryStream()) 11 | { 12 | using (var binaryWriter = new BinaryWriter(memoryStream)) 13 | { 14 | action(binaryWriter); 15 | } 16 | 17 | return memoryStream.ToArray(); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/ITelegramClient.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("TelegramClient.UnitTests")] 4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 5 | 6 | namespace TelegramClient.Core 7 | { 8 | using System; 9 | 10 | using TelegramClient.Core.ApiServies.Interfaces; 11 | 12 | public interface ITelegramClient : IDisposable 13 | { 14 | /// Send custom messages 15 | ISenderService SendService { get; } 16 | 17 | /// Automatic and manual updates 18 | IUpdatesApiService UpdatesService { get; } 19 | 20 | /// Connecting to the server 21 | IConnectApiService ConnectService { get; } 22 | 23 | /// Registration and authentication 24 | IAuthApiService AuthService { get; } 25 | 26 | /// Messages and chats 27 | IMessagesApiService MessagesService { get; } 28 | 29 | /// Working with contacts 30 | IContactsApiService ContactsService { get; } 31 | 32 | /// Working with files 33 | IUploadApiService UploadService { get; } 34 | } 35 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/IoC/ComponentAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.IoC 2 | { 3 | using System; 4 | 5 | using BarsGroup.CodeGuard; 6 | 7 | /// 8 | /// Marks the decorated class as a component that will be available from the service locator / component 9 | /// container. 10 | /// 11 | [AttributeUsage(AttributeTargets.Class)] 12 | public abstract class ComponentAttribute : Attribute 13 | { 14 | public EDependencyLifecycle Lifecycle { get; } 15 | 16 | /// Type to use for the component registration. 17 | public Type[] RegisterAs { get; } 18 | 19 | /// 20 | /// Initializes a new instance of the class, marking the decorated class as a 21 | /// component that will be available from the service locator / component container using the specified 22 | /// type. 23 | /// 24 | /// The type to use to register the decorated component. 25 | /// The lifecycle of dependency 26 | protected ComponentAttribute(Type[] registerAs, EDependencyLifecycle lifecycle) 27 | { 28 | Guard.That(registerAs).IsNotEmpty(); 29 | 30 | RegisterAs = registerAs; 31 | Lifecycle = lifecycle; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/IoC/CompositionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.IoC 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | 8 | using Castle.MicroKernel.Registration; 9 | using Castle.Windsor; 10 | 11 | /// 12 | /// Provides automatic component registration by scanning assemblies and types for those that have the 13 | /// annotation. 14 | /// 15 | public static class CompositionExtensions 16 | { 17 | /// Registers the components found in the given assemblies. 18 | internal static void RegisterAttibuteRegistration(this IWindsorContainer builder, params Assembly[] assemblies) 19 | { 20 | RegisterComponents(builder, assemblies.SelectMany(x => x.GetTypes())); 21 | } 22 | 23 | /// Registers the components found in the given set of types. 24 | private static void RegisterComponents(this IWindsorContainer builder, IEnumerable types) 25 | { 26 | var supportedTypes = types.Where(t => t.GetTypeInfo().GetCustomAttribute(true) != null).ToArray(); 27 | 28 | foreach (var type in supportedTypes) 29 | { 30 | var attibute = type.GetTypeInfo().GetCustomAttribute(true); 31 | builder.Register(Component.For(attibute.RegisterAs).ImplementedBy(type).RegisterComponents(attibute.Lifecycle)); 32 | } 33 | } 34 | 35 | private static ComponentRegistration RegisterComponents(this ComponentRegistration builder, EDependencyLifecycle lifecycle) 36 | { 37 | switch (lifecycle) 38 | { 39 | case EDependencyLifecycle.Singleton: 40 | builder.LifestyleSingleton(); 41 | break; 42 | default: 43 | throw new ArgumentOutOfRangeException(nameof(lifecycle), lifecycle, null); 44 | } 45 | 46 | return builder; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/IoC/EDependencyLifecycle.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.IoC 2 | { 3 | public enum EDependencyLifecycle 4 | { 5 | Singleton 6 | } 7 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/IoC/SingleInstanceAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.IoC 2 | { 3 | using System; 4 | 5 | public class SingleInstanceAttribute : ComponentAttribute 6 | { 7 | public SingleInstanceAttribute(params Type[] registerAs) : base(registerAs, EDependencyLifecycle.Singleton) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/MTProto/Crypto/AuthKey.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.MTProto.Crypto 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Security.Cryptography; 6 | 7 | public class AuthKey 8 | { 9 | private readonly ulong _auxHash; 10 | 11 | public byte[] Data { get; } 12 | 13 | public ulong Id { get; } 14 | 15 | public AuthKey(BigInteger gab) 16 | { 17 | Data = gab.ToByteArrayUnsigned(); 18 | using (var hash = SHA1.Create()) 19 | { 20 | using (var hashStream = new MemoryStream(hash.ComputeHash(Data), false)) 21 | { 22 | using (var hashReader = new BinaryReader(hashStream)) 23 | { 24 | _auxHash = hashReader.ReadUInt64(); 25 | hashReader.ReadBytes(4); 26 | Id = hashReader.ReadUInt64(); 27 | } 28 | } 29 | } 30 | } 31 | 32 | public AuthKey(byte[] data) 33 | { 34 | Data = data; 35 | using (var hash = SHA1.Create()) 36 | { 37 | using (var hashStream = new MemoryStream(hash.ComputeHash(Data), false)) 38 | { 39 | using (var hashReader = new BinaryReader(hashStream)) 40 | { 41 | _auxHash = hashReader.ReadUInt64(); 42 | hashReader.ReadBytes(4); 43 | Id = hashReader.ReadUInt64(); 44 | } 45 | } 46 | } 47 | } 48 | 49 | public byte[] CalcNewNonceHash(byte[] newNonce, int number) 50 | { 51 | using (var stream = new MemoryStream(100)) 52 | { 53 | using (var bufferWriter = new BinaryWriter(stream)) 54 | { 55 | bufferWriter.Write(newNonce); 56 | bufferWriter.Write((byte)number); 57 | bufferWriter.Write(_auxHash); 58 | using (var sha1 = SHA1.Create()) 59 | { 60 | stream.TryGetBuffer(out var buffer); 61 | 62 | var hash = sha1.ComputeHash(buffer.Array, 0, (int)stream.Position); 63 | var newNonceHash = new byte[16]; 64 | Array.Copy(hash, 4, newNonceHash, 0, 16); 65 | return newNonceHash; 66 | } 67 | } 68 | } 69 | } 70 | 71 | public override string ToString() 72 | { 73 | return string.Format("(Key: {0}, KeyId: {1}, AuxHash: {2})", Data, Id, _auxHash); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/MTProto/Crypto/Crc32.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.MTProto.Crypto 2 | { 3 | using System.Security.Cryptography; 4 | 5 | public class Crc32 : HashAlgorithm 6 | { 7 | public const uint DefaultPolynomial = 0xedb88320u; 8 | 9 | public const uint DefaultSeed = 0xffffffffu; 10 | 11 | private static uint[] _defaultTable; 12 | 13 | private readonly uint _seed; 14 | 15 | private readonly uint[] _table; 16 | 17 | private uint _hash; 18 | 19 | public override int HashSize => 32; 20 | 21 | public Crc32() 22 | { 23 | _table = InitializeTable(DefaultPolynomial); 24 | _seed = DefaultSeed; 25 | _hash = _seed; 26 | } 27 | 28 | public Crc32(uint polynomial, uint seed) 29 | { 30 | _table = InitializeTable(polynomial); 31 | _seed = seed; 32 | _hash = seed; 33 | } 34 | 35 | public static uint Compute(byte[] buffer) 36 | { 37 | return ~CalculateHash(InitializeTable(DefaultPolynomial), DefaultSeed, buffer, 0, buffer.Length); 38 | } 39 | 40 | public static uint Compute(uint seed, byte[] buffer) 41 | { 42 | return ~CalculateHash(InitializeTable(DefaultPolynomial), seed, buffer, 0, buffer.Length); 43 | } 44 | 45 | public static uint Compute(uint polynomial, uint seed, byte[] buffer) 46 | { 47 | return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); 48 | } 49 | 50 | public override void Initialize() 51 | { 52 | _hash = _seed; 53 | } 54 | 55 | protected override void HashCore(byte[] buffer, int start, int length) 56 | { 57 | _hash = CalculateHash(_table, _hash, buffer, start, length); 58 | } 59 | 60 | /// Возвращает хеш в BigEndian 61 | /// 62 | protected override byte[] HashFinal() 63 | { 64 | var hashBuffer = UInt32ToBigEndianBytes(~_hash); 65 | return hashBuffer; 66 | } 67 | 68 | private static uint CalculateHash(uint[] table, uint seed, byte[] buffer, int start, int size) 69 | { 70 | var crc = seed; 71 | for (var i = start; i < size; i++) 72 | unchecked 73 | { 74 | crc = (crc >> 8) ^ table[buffer[i] ^ (crc & 0xff)]; 75 | } 76 | 77 | return crc; 78 | } 79 | 80 | private static uint[] InitializeTable(uint polynomial) 81 | { 82 | if (polynomial == DefaultPolynomial && _defaultTable != null) 83 | { 84 | return _defaultTable; 85 | } 86 | 87 | var createTable = new uint[256]; 88 | for (var i = 0; i < 256; i++) 89 | { 90 | var entry = (uint)i; 91 | for (var j = 0; j < 8; j++) 92 | if ((entry & 1) == 1) 93 | { 94 | entry = (entry >> 1) ^ polynomial; 95 | } 96 | else 97 | { 98 | entry = entry >> 1; 99 | } 100 | 101 | createTable[i] = entry; 102 | } 103 | 104 | if (polynomial == DefaultPolynomial) 105 | { 106 | _defaultTable = createTable; 107 | } 108 | 109 | return createTable; 110 | } 111 | 112 | private byte[] UInt32ToBigEndianBytes(uint x) 113 | { 114 | return new[] 115 | { 116 | (byte)((x >> 24) & 0xff), 117 | (byte)((x >> 16) & 0xff), 118 | (byte)((x >> 8) & 0xff), 119 | (byte)(x & 0xff) 120 | }; 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/MTProto/Crypto/Factorizator.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.MTProto.Crypto 2 | { 3 | using System; 4 | 5 | public class FactorizedPair 6 | { 7 | private readonly BigInteger _p; 8 | 9 | private readonly BigInteger _q; 10 | 11 | public BigInteger Min => _p.Min(_q); 12 | 13 | public BigInteger Max => _p.Max(_q); 14 | 15 | public FactorizedPair(BigInteger p, BigInteger q) 16 | { 17 | _p = p; 18 | _q = q; 19 | } 20 | 21 | public override string ToString() 22 | { 23 | return string.Format("P: {0}, Q: {1}", _p, _q); 24 | } 25 | } 26 | 27 | public static class Factorizator 28 | { 29 | private static readonly Random Random = new Random(); 30 | 31 | public static FactorizedPair Factorize(BigInteger pq) 32 | { 33 | if (pq.BitLength < 64) 34 | { 35 | var pqlong = pq.LongValue; 36 | var divisor = FindSmallMultiplierLopatin(pqlong); 37 | return new FactorizedPair(BigInteger.ValueOf(divisor), BigInteger.ValueOf(pqlong / divisor)); 38 | } 39 | 40 | // TODO: port pollard factorization 41 | throw new InvalidOperationException("pq too long; TODO: port the pollard algo"); 42 | 43 | // logger.error("pq too long; TODO: port the pollard algo"); 44 | // return null; 45 | } 46 | 47 | private static long FindSmallMultiplierLopatin(long what) 48 | { 49 | long g = 0; 50 | for (var i = 0; i < 3; i++) 51 | { 52 | var q = (Random.Next(128) & 15) + 17; 53 | long x = Random.Next(1000000000) + 1, y = x; 54 | var lim = 1 << (i + 18); 55 | for (var j = 1; j < lim; j++) 56 | { 57 | long a = x, b = x, c = q; 58 | while (b != 0) 59 | { 60 | if ((b & 1) != 0) 61 | { 62 | c += a; 63 | if (c >= what) 64 | { 65 | c -= what; 66 | } 67 | } 68 | 69 | a += a; 70 | if (a >= what) 71 | { 72 | a -= what; 73 | } 74 | 75 | b >>= 1; 76 | } 77 | 78 | x = c; 79 | var z = x < y 80 | ? y - x 81 | : x - y; 82 | g = Gcd(z, what); 83 | if (g != 1) 84 | { 85 | break; 86 | } 87 | 88 | if ((j & (j - 1)) == 0) 89 | { 90 | y = x; 91 | } 92 | } 93 | 94 | if (g > 1) 95 | { 96 | break; 97 | } 98 | } 99 | 100 | var p = what / g; 101 | return Math.Min(p, g); 102 | } 103 | 104 | private static long Gcd(long a, long b) 105 | { 106 | while (a != 0 && b != 0) 107 | { 108 | while ((b & 1) == 0) 109 | b >>= 1; 110 | while ((a & 1) == 0) 111 | a >>= 1; 112 | if (a > b) 113 | { 114 | a -= b; 115 | } 116 | else 117 | { 118 | b -= a; 119 | } 120 | } 121 | 122 | return b == 0 123 | ? a 124 | : b; 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/MTProto/Crypto/RSA.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.MTProto.Crypto 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Security.Cryptography; 7 | 8 | internal class RsaServerKey 9 | { 10 | private readonly BigInteger _e; 11 | 12 | private readonly BigInteger _m; 13 | 14 | private string _fingerprint; 15 | 16 | public RsaServerKey(string fingerprint, BigInteger m, BigInteger e) 17 | { 18 | _fingerprint = fingerprint; 19 | _m = m; 20 | _e = e; 21 | } 22 | 23 | public byte[] Encrypt(byte[] data, int offset, int length) 24 | { 25 | using (var buffer = new MemoryStream(255)) 26 | using (var writer = new BinaryWriter(buffer)) 27 | { 28 | using (var sha1 = SHA1.Create()) 29 | { 30 | var hashsum = sha1.ComputeHash(data, offset, length); 31 | writer.Write(hashsum); 32 | } 33 | 34 | buffer.Write(data, offset, length); 35 | if (length < 235) 36 | { 37 | var padding = new byte[235 - length]; 38 | new Random().NextBytes(padding); 39 | buffer.Write(padding, 0, padding.Length); 40 | } 41 | 42 | var ciphertext = new BigInteger(1, buffer.ToArray()).ModPow(_e, _m).ToByteArrayUnsigned(); 43 | 44 | if (ciphertext.Length == 256) 45 | { 46 | return ciphertext; 47 | } 48 | 49 | { 50 | var paddedCiphertext = new byte[256]; 51 | var padding = 256 - ciphertext.Length; 52 | for (var i = 0; i < padding; i++) 53 | { 54 | paddedCiphertext[i] = 0; 55 | } 56 | ciphertext.CopyTo(paddedCiphertext, padding); 57 | return paddedCiphertext; 58 | } 59 | } 60 | } 61 | } 62 | 63 | public class Rsa 64 | { 65 | private static readonly Dictionary ServerKeys = new Dictionary 66 | { 67 | { 68 | "216be86c022bb4c3", 69 | new RsaServerKey( 70 | "216be86c022bb4c3", 71 | new BigInteger( 72 | "00C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F91F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD15A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F", 73 | 16), 74 | new BigInteger("010001", 16)) 75 | } 76 | }; 77 | 78 | public static byte[] Encrypt(string fingerprint, byte[] data, int offset, int length) 79 | { 80 | var fingerprintLower = fingerprint.ToLower(); 81 | if (!ServerKeys.ContainsKey(fingerprintLower)) 82 | { 83 | return null; 84 | } 85 | 86 | var key = ServerKeys[fingerprintLower]; 87 | 88 | return key.Encrypt(data, offset, length); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/MTProto/Crypto/Salt.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.MTProto.Crypto 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | public class Salt : IComparable 7 | { 8 | public int ValidSince { get; } 9 | 10 | public int ValidUntil { get; } 11 | 12 | public ulong Value { get; } 13 | 14 | public Salt(int validSince, int validUntil, ulong salt) 15 | { 16 | ValidSince = validSince; 17 | ValidUntil = validUntil; 18 | Value = salt; 19 | } 20 | 21 | public int CompareTo(Salt other) 22 | { 23 | return ValidUntil.CompareTo(other.ValidSince); 24 | } 25 | } 26 | 27 | public class SaltCollection 28 | { 29 | private SortedSet _salts; 30 | 31 | public int Count => _salts.Count; 32 | 33 | public void Add(Salt salt) 34 | { 35 | _salts.Add(salt); 36 | } 37 | 38 | // TODO: get actual salt and other... 39 | } 40 | 41 | public class GetFutureSaltsResponse 42 | { 43 | public ulong RequestId { get; } 44 | 45 | public int Now { get; } 46 | 47 | public SaltCollection Salts { get; } 48 | 49 | public GetFutureSaltsResponse(ulong requestId, int now) 50 | { 51 | RequestId = requestId; 52 | Now = now; 53 | } 54 | 55 | public void AddSalt(Salt salt) 56 | { 57 | Salts.Add(salt); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/MTProto/Serializers.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.MTProto 2 | { 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | 7 | public class Serializers 8 | { 9 | public static string VectorToString(List list) 10 | { 11 | var tokens = new string[list.Count]; 12 | for (var i = 0; i < list.Count; i++) 13 | tokens[i] = list[i].ToString(); 14 | return "[" + string.Join(", ", tokens) + "]"; 15 | } 16 | 17 | public static class Bytes 18 | { 19 | public static byte[] Read(BinaryReader binaryReader) 20 | { 21 | var firstByte = binaryReader.ReadByte(); 22 | int len, padding; 23 | if (firstByte == 254) 24 | { 25 | len = binaryReader.ReadByte() | (binaryReader.ReadByte() << 8) | (binaryReader.ReadByte() << 16); 26 | padding = len % 4; 27 | } 28 | else 29 | { 30 | len = firstByte; 31 | padding = (len + 1) % 4; 32 | } 33 | 34 | var data = binaryReader.ReadBytes(len); 35 | if (padding > 0) 36 | { 37 | padding = 4 - padding; 38 | binaryReader.ReadBytes(padding); 39 | } 40 | 41 | return data; 42 | } 43 | 44 | public static BinaryWriter Write(BinaryWriter binaryWriter, byte[] data) 45 | { 46 | int padding; 47 | if (data.Length < 254) 48 | { 49 | padding = (data.Length + 1) % 4; 50 | if (padding != 0) 51 | { 52 | padding = 4 - padding; 53 | } 54 | 55 | binaryWriter.Write((byte)data.Length); 56 | binaryWriter.Write(data); 57 | } 58 | else 59 | { 60 | padding = data.Length % 4; 61 | if (padding != 0) 62 | { 63 | padding = 4 - padding; 64 | } 65 | 66 | binaryWriter.Write((byte)254); 67 | binaryWriter.Write((byte)data.Length); 68 | binaryWriter.Write((byte)(data.Length >> 8)); 69 | binaryWriter.Write((byte)(data.Length >> 16)); 70 | binaryWriter.Write(data); 71 | } 72 | 73 | for (var i = 0; i < padding; i++) 74 | { 75 | binaryWriter.Write((byte)0); 76 | } 77 | 78 | return binaryWriter; 79 | } 80 | } 81 | 82 | public static class String 83 | { 84 | public static string Read(BinaryReader reader) 85 | { 86 | var data = Bytes.Read(reader); 87 | return Encoding.UTF8.GetString(data, 0, data.Length); 88 | } 89 | 90 | public static BinaryWriter Write(BinaryWriter writer, string str) 91 | { 92 | return Bytes.Write(writer, Encoding.UTF8.GetBytes(str)); 93 | } 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Confirm/ConfirmationSendService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Confirm 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | using log4net; 11 | 12 | using OpenTl.Schema; 13 | 14 | using TelegramClient.Core.IoC; 15 | using TelegramClient.Core.Network.Interfaces; 16 | 17 | [SingleInstance(typeof(IConfirmationSendService))] 18 | internal class ConfirmationSendService : IConfirmationSendService 19 | { 20 | private static readonly ILog Log = LogManager.GetLogger(typeof(ConfirmationSendService)); 21 | 22 | private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); 23 | 24 | private readonly ConcurrentQueue _waitSendConfirmation = new ConcurrentQueue(); 25 | 26 | public IMtProtoSender MtProtoSender { get; set; } 27 | 28 | public void AddForSend(long messageId) 29 | { 30 | _waitSendConfirmation.Enqueue(messageId); 31 | 32 | SendAllMessagesFromQueue().ConfigureAwait(false); 33 | } 34 | 35 | public async Task SendAllMessagesFromQueue() 36 | { 37 | await _semaphoreSlim.WaitAsync().ContinueWith( 38 | async _ => 39 | { 40 | if (!_waitSendConfirmation.IsEmpty) 41 | { 42 | try 43 | { 44 | await SendFromQueue().ConfigureAwait(false); 45 | } 46 | catch (Exception ex) 47 | { 48 | Log.Error("Failed to send confim message", ex); 49 | } 50 | finally 51 | { 52 | _semaphoreSlim.Release(); 53 | } 54 | } 55 | else 56 | { 57 | _semaphoreSlim.Release(); 58 | } 59 | }).ConfigureAwait(false); 60 | } 61 | 62 | private async Task SendFromQueue() 63 | { 64 | while (!_waitSendConfirmation.IsEmpty) 65 | { 66 | var msgs = new HashSet(); 67 | while (!_waitSendConfirmation.IsEmpty) 68 | { 69 | _waitSendConfirmation.TryDequeue(out var item); 70 | msgs.Add(item); 71 | } 72 | 73 | try 74 | { 75 | Log.Debug($"Sending confirmation for messages {string.Join(",", msgs.Select(m => m.ToString()))}"); 76 | 77 | var message = new TMsgsAck 78 | { 79 | MsgIds = new TVector(msgs.ToArray()) 80 | }; 81 | 82 | await MtProtoSender.Send(message, CancellationToken.None); 83 | } 84 | catch (Exception e) 85 | { 86 | Log.Error("Sending confirmation for messages failed", e); 87 | } 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Confirm/IConfirmationSendService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Confirm 2 | { 3 | internal interface IConfirmationSendService 4 | { 5 | void AddForSend(long messageId); 6 | } 7 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/AuthRestartException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | using System; 4 | 5 | public class AuthRestartException : Exception 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/BadServerSaltException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | using System; 4 | 5 | internal class BadServerSaltException : Exception 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/CloudPasswordNeededException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | using System; 4 | 5 | public class CloudPasswordNeededException : Exception 6 | { 7 | internal CloudPasswordNeededException(string msg) : base(msg) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/DataCenterMigrationException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | using System; 4 | 5 | internal abstract class DataCenterMigrationException : Exception 6 | { 7 | private const string ReportMessage = 8 | " See: https://github.com/sochix/TLSharp#i-get-a-xxxmigrationexception-or-a-migrate_x-error"; 9 | 10 | internal int Dc { get; } 11 | 12 | protected DataCenterMigrationException(string msg, int dc) : base(msg + ReportMessage) 13 | { 14 | Dc = dc; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/DisconnectedException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | using System; 4 | 5 | public class DisconnectedException : Exception 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/FileMigrationException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | internal class FileMigrationException : DataCenterMigrationException 4 | { 5 | internal FileMigrationException(int dc) 6 | : base($"File located on a different DC: {dc}.", dc) 7 | { 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/FloodException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | using System; 4 | 5 | public class FloodException : Exception 6 | { 7 | public TimeSpan TimeToWait { get; } 8 | 9 | internal FloodException(TimeSpan timeToWait) 10 | : base( 11 | $"Flood prevention. Telegram now requires your program to do requests again only after {timeToWait.TotalSeconds} seconds have passed ({nameof(TimeToWait)} property)." + 12 | " If you think the culprit of this problem may lie in TLSharp's implementation, open a Github issue please.") 13 | { 14 | TimeToWait = timeToWait; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/InvalidPhoneCodeException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | using System; 4 | 5 | public class InvalidPhoneCodeException : Exception 6 | { 7 | internal InvalidPhoneCodeException(string msg) : base(msg) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/PhoneMigrationException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | internal class PhoneMigrationException : DataCenterMigrationException 4 | { 5 | internal PhoneMigrationException(int dc) 6 | : base($"Phone number registered to a different DC: {dc}.", dc) 7 | { 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Exceptions/UserMigrationException.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Exceptions 2 | { 3 | internal class UserMigrationException : DataCenterMigrationException 4 | { 5 | internal UserMigrationException(int dc) 6 | : base($"User located on a different DC: {dc}.", dc) 7 | { 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Interfaces/IMtProtoPlainSender.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Interfaces 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | internal interface IMtProtoPlainSender 7 | { 8 | Task SendAndReceive(byte[] data, CancellationToken cancellationToken = default(CancellationToken)); 9 | } 10 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Interfaces/IMtProtoSender.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Interfaces 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | using OpenTl.Schema; 7 | 8 | internal interface IMtProtoSender 9 | { 10 | Task Send(IObject obj, CancellationToken cancellationToken); 11 | 12 | Task> SendAndWaitResponse(IObject obj, CancellationToken cancellationToken); 13 | } 14 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Recieve/GZipPackedHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Recieve 2 | { 3 | using System.IO; 4 | using System.IO.Compression; 5 | 6 | using log4net; 7 | 8 | using Newtonsoft.Json; 9 | 10 | using OpenTl.Schema; 11 | using OpenTl.Schema.Serialization; 12 | 13 | using TelegramClient.Core.IoC; 14 | using TelegramClient.Core.Network.Recieve.Interfaces; 15 | 16 | [SingleInstance(typeof(IGZipPackedHandler))] 17 | internal class GZipPackedHandler : IGZipPackedHandler 18 | { 19 | private static readonly ILog Log = LogManager.GetLogger(typeof(GZipPackedHandler)); 20 | 21 | public IObject HandleGZipPacked(TgZipPacked obj) 22 | { 23 | using (var decompressStream = new MemoryStream()) 24 | { 25 | using (var stream = new MemoryStream(obj.PackedData)) 26 | using (var zipStream = new GZipStream(stream, CompressionMode.Decompress)) 27 | { 28 | zipStream.CopyTo(decompressStream); 29 | } 30 | 31 | decompressStream.Position = 0; 32 | 33 | using (var reader = new BinaryReader(decompressStream)) 34 | { 35 | var unzippedObj = Serializer.DeserializeObject(reader); 36 | 37 | if (Log.IsDebugEnabled) 38 | { 39 | var jObject = JsonConvert.SerializeObject(unzippedObj); 40 | Log.Debug($"Recived Gzip message {unzippedObj}: {jObject}"); 41 | } 42 | 43 | return unzippedObj; 44 | } 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Recieve/Interfaces/IGZipPackedHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Recieve.Interfaces 2 | { 3 | using OpenTl.Schema; 4 | 5 | internal interface IGZipPackedHandler 6 | { 7 | IObject HandleGZipPacked(TgZipPacked obj); 8 | } 9 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Recieve/Interfaces/IRecievingService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Recieve.Interfaces 2 | { 3 | using System; 4 | 5 | internal interface IRecievingService : IDisposable 6 | { 7 | void StartReceiving(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Recieve/Interfaces/IResponseResultGetter.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Recieve.Interfaces 2 | { 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | internal interface IResponseResultGetter 7 | { 8 | Task Receive(long requestId, CancellationToken cancellationToken); 9 | } 10 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Recieve/Interfaces/IResponseResultSetter.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Recieve.Interfaces 2 | { 3 | using System; 4 | 5 | internal interface IResponseResultSetter 6 | { 7 | void ReturnException(long requestId, Exception exception); 8 | 9 | void ReturnException(Exception exception); 10 | 11 | void ReturnResult(long requestId, object obj); 12 | } 13 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Recieve/RecievingService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Recieve 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | using log4net; 10 | 11 | using Newtonsoft.Json; 12 | 13 | using OpenTl.Schema; 14 | using OpenTl.Schema.Help; 15 | using OpenTl.Schema.Serialization; 16 | 17 | using TelegramClient.Core.IoC; 18 | using TelegramClient.Core.MTProto.Crypto; 19 | using TelegramClient.Core.Network.Confirm; 20 | using TelegramClient.Core.Network.Interfaces; 21 | using TelegramClient.Core.Network.Recieve.Interfaces; 22 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 23 | using TelegramClient.Core.Network.Tcp; 24 | using TelegramClient.Core.Settings; 25 | using TelegramClient.Core.Utils; 26 | 27 | [SingleInstance(typeof(IRecievingService))] 28 | internal class RecievingService : IRecievingService 29 | { 30 | private static readonly ILog Log = LogManager.GetLogger(typeof(RecievingService)); 31 | 32 | private CancellationTokenSource _recievingTokenSource; 33 | 34 | public ITcpTransport TcpTransport { get; set; } 35 | 36 | public IClientSettings ClientSettings { get; set; } 37 | 38 | public IConfirmationSendService ConfirmationSendService { get; set; } 39 | 40 | public IDictionary RecieveHandlersMap { get; set; } 41 | 42 | public IGZipPackedHandler ZipPackedHandler { get; set; } 43 | 44 | public IMtProtoSender Sender { get; set; } 45 | 46 | public void Dispose() 47 | { 48 | _recievingTokenSource?.Dispose(); 49 | TcpTransport?.Dispose(); 50 | } 51 | 52 | public void StartReceiving() 53 | { 54 | if (_recievingTokenSource == null) 55 | { 56 | _recievingTokenSource = new CancellationTokenSource(); 57 | StartRecievingTask(_recievingTokenSource.Token); 58 | } 59 | } 60 | 61 | private Tuple DecodeMessage(byte[] body) 62 | { 63 | byte[] message; 64 | long remoteMessageId; 65 | 66 | using (var inputStream = new MemoryStream(body)) 67 | using (var inputReader = new BinaryReader(inputStream)) 68 | { 69 | if (inputReader.BaseStream.Length < 8) 70 | { 71 | throw new InvalidOperationException("Can\'t decode packet"); 72 | } 73 | 74 | var remoteAuthKeyId = inputReader.ReadUInt64(); // TODO: check auth key id 75 | var msgKey = inputReader.ReadBytes(16); // TODO: check msg_key correctness 76 | var keyData = TlHelpers.CalcKey(ClientSettings.Session.AuthKey.Data, msgKey, false); 77 | 78 | var plaintext = AES.DecryptAes( 79 | keyData, 80 | inputReader.ReadBytes((int)(inputStream.Length - inputStream.Position))); 81 | 82 | using (var plaintextStream = new MemoryStream(plaintext)) 83 | using (var plaintextReader = new BinaryReader(plaintextStream)) 84 | { 85 | var remoteSalt = plaintextReader.ReadUInt64(); 86 | var remoteSessionId = plaintextReader.ReadUInt64(); 87 | remoteMessageId = plaintextReader.ReadInt64(); 88 | plaintextReader.ReadInt32(); 89 | var msgLen = plaintextReader.ReadInt32(); 90 | message = plaintextReader.ReadBytes(msgLen); 91 | } 92 | } 93 | 94 | return Tuple.Create(message, remoteMessageId); 95 | } 96 | 97 | private void ProcessReceivedMessage(byte[] message) 98 | { 99 | var obj = Serializer.DeserializeObject(message); 100 | 101 | ProcessReceivedMessage(obj); 102 | } 103 | 104 | private void ProcessReceivedMessage(IObject obj) 105 | { 106 | if (Log.IsDebugEnabled) 107 | { 108 | var jObject = JsonConvert.SerializeObject(obj); 109 | Log.Debug($"Try handle response for object: {obj} \n{jObject}"); 110 | } 111 | 112 | switch (obj) 113 | { 114 | case var o when RecieveHandlersMap.TryGetValue(o.GetType(), out var handler): 115 | Log.Debug($"Handler found - {handler}"); 116 | handler.HandleResponce(obj); 117 | break; 118 | case TMsgContainer container: 119 | foreach (var containerMessage in container.Messages) 120 | { 121 | ProcessReceivedMessage(containerMessage.Body); 122 | ConfirmationSendService.AddForSend(containerMessage.MsgId); 123 | } 124 | 125 | break; 126 | case TgZipPacked zipPacked: 127 | var unzippedData = ZipPackedHandler.HandleGZipPacked(zipPacked); 128 | ProcessReceivedMessage(unzippedData); 129 | break; 130 | default: 131 | if (Log.IsErrorEnabled) 132 | { 133 | var jObject = JsonConvert.SerializeObject(obj); 134 | Log.Error($"Cannot handle object: {obj} \n{jObject}"); 135 | } 136 | 137 | break; 138 | } 139 | } 140 | 141 | private async Task StartRecievingTask(CancellationToken cancellationToken) 142 | { 143 | while (!cancellationToken.IsCancellationRequested) 144 | { 145 | try 146 | { 147 | var recieveData = await TcpTransport.Receieve().ConfigureAwait(false); 148 | 149 | var decodedData = DecodeMessage(recieveData); 150 | 151 | Log.Debug($"Receive message with remote id: {decodedData.Item2}"); 152 | 153 | ProcessReceivedMessage(decodedData.Item1); 154 | 155 | ConfirmationSendService.AddForSend(decodedData.Item2); 156 | } 157 | catch (Exception e) 158 | { 159 | Log.Error("Receive message failed. Reconnecting", e); 160 | 161 | var request = new RequestInvokeWithLayer 162 | { 163 | Layer = SchemaInfo.SchemaVersion, 164 | Query = new RequestInitConnection 165 | { 166 | ApiId = ClientSettings.AppId, 167 | AppVersion = "1.0.0", 168 | DeviceModel = "PC", 169 | LangCode = "en", 170 | LangPack = "tdesktop", 171 | Query = new RequestGetConfig(), 172 | SystemLangCode = "en", 173 | SystemVersion = "Win 10.0" 174 | } 175 | }; 176 | 177 | try 178 | { 179 | await TcpTransport.Disconnect().ConfigureAwait(false); 180 | 181 | await Sender.SendAndWaitResponse(request, CancellationToken.None).ConfigureAwait(false); 182 | } 183 | catch 184 | { 185 | Log.Error("Failed to reconnect", e); 186 | } 187 | } 188 | } 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Recieve/ResponseResultService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Recieve 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using log4net; 9 | 10 | using TelegramClient.Core.IoC; 11 | using TelegramClient.Core.Network.Recieve.Interfaces; 12 | 13 | [SingleInstance(typeof(IResponseResultGetter), typeof(IResponseResultSetter))] 14 | internal class ResponseResultService : IResponseResultGetter, 15 | IResponseResultSetter 16 | { 17 | private static readonly ILog Log = LogManager.GetLogger(typeof(ResponseResultService)); 18 | 19 | private readonly ConcurrentDictionary)> _resultCallbacks = new ConcurrentDictionary)>(); 20 | 21 | public Task Receive(long requestId, CancellationToken cancellationToken) 22 | { 23 | var tcs = new TaskCompletionSource(); 24 | 25 | if (cancellationToken != default(CancellationToken)) 26 | { 27 | cancellationToken.Register( 28 | () => 29 | { 30 | if (!tcs.Task.IsCompleted) 31 | { 32 | tcs.TrySetCanceled(cancellationToken); 33 | } 34 | }); 35 | } 36 | 37 | var timer = new Timer( _ => 38 | { 39 | if (!tcs.Task.IsCompleted) 40 | { 41 | Log.Warn($"Message response result timed out for messageid '{requestId}'"); 42 | 43 | _resultCallbacks.TryRemove(requestId, out var _); 44 | 45 | tcs.TrySetCanceled(cancellationToken); 46 | } 47 | }, null, TimeSpan.FromMinutes(1), TimeSpan.Zero); 48 | 49 | _resultCallbacks[requestId] = (timer, tcs); 50 | return tcs.Task; 51 | } 52 | 53 | public void ReturnException(long requestId, Exception exception) 54 | { 55 | if (_resultCallbacks.TryGetValue(requestId, out var data)) 56 | { 57 | (Timer timer, TaskCompletionSource callback) = data; 58 | timer.Dispose(); 59 | 60 | callback.TrySetException(exception); 61 | 62 | Log.Error($"Request was processed with error", exception); 63 | 64 | _resultCallbacks.TryRemove(requestId, out var response); 65 | } 66 | else 67 | { 68 | Log.Error($"Callback for request with Id {requestId} wasn't found"); 69 | } 70 | } 71 | 72 | public void ReturnException(Exception exception) 73 | { 74 | Log.Error($"All requests was processed with error", exception); 75 | 76 | foreach ((Timer timer, TaskCompletionSource callback) in _resultCallbacks.Values) 77 | { 78 | timer.Dispose(); 79 | callback.TrySetException(exception); 80 | } 81 | } 82 | 83 | public void ReturnResult(long requestId, object obj) 84 | { 85 | if (_resultCallbacks.TryGetValue(requestId, out var data)) 86 | { 87 | (Timer timer, TaskCompletionSource callback) = data; 88 | timer.Dispose(); 89 | 90 | callback.TrySetResult(obj); 91 | 92 | _resultCallbacks.TryRemove(requestId, out var _); 93 | } 94 | else 95 | { 96 | Log.Error($"Callback for request with Id {requestId} wasn't found"); 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/BadMsgNotificationRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using OpenTl.Schema; 8 | 9 | using TelegramClient.Core.IoC; 10 | using TelegramClient.Core.Network.Recieve.Interfaces; 11 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 12 | 13 | [SingleInstance(typeof(IRecieveHandler))] 14 | internal class BadMsgNotificationRecieveHandler : IRecieveHandler 15 | { 16 | private static readonly ILog Log = LogManager.GetLogger(typeof(BadMsgNotificationRecieveHandler)); 17 | 18 | public Type[] HandleCodes { get; } = { typeof(TBadMsgNotification) }; 19 | 20 | public IResponseResultSetter ResponseResultSetter { get; set; } 21 | 22 | public void HandleResponce(IObject obj) 23 | { 24 | var message = obj.Cast(); 25 | 26 | Exception exception; 27 | switch (message.ErrorCode) 28 | { 29 | case 16: 30 | exception = new InvalidOperationException( 31 | "msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original message had waited too long on the client to be transmitted)"); 32 | break; 33 | case 17: 34 | exception = new InvalidOperationException( 35 | "msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)"); 36 | break; 37 | case 18: 38 | exception = new InvalidOperationException( 39 | "incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)"); 40 | break; 41 | case 19: 42 | exception = new InvalidOperationException( 43 | "container msg_id is the same as msg_id of a previously received message (this must never happen)"); 44 | break; 45 | case 20: 46 | exception = new InvalidOperationException( 47 | "message too old, and it cannot be verified whether the server has received a message with this msg_id or not"); 48 | break; 49 | case 32: 50 | exception = new InvalidOperationException( 51 | "msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)"); 52 | break; 53 | case 33: 54 | exception = new InvalidOperationException( 55 | " msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)"); 56 | break; 57 | case 34: 58 | exception = new InvalidOperationException( 59 | "an even msg_seqno expected (irrelevant message), but odd received"); 60 | break; 61 | case 35: 62 | exception = new InvalidOperationException("odd msg_seqno expected (relevant message), but even received"); 63 | break; 64 | case 48: 65 | exception = new InvalidOperationException( 66 | "incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)"); 67 | break; 68 | case 64: 69 | exception = new InvalidOperationException("invalid container"); 70 | break; 71 | default: 72 | exception = new NotImplementedException("This should never happens"); 73 | break; 74 | } 75 | 76 | Log.Error($"Handle a bad message notification for request id = {message.BadMsgId}", exception); 77 | 78 | ResponseResultSetter.ReturnException(message.BadMsgId, exception); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/BadServerSaltRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using OpenTl.Schema; 8 | 9 | using TelegramClient.Core.IoC; 10 | using TelegramClient.Core.Network.Exceptions; 11 | using TelegramClient.Core.Network.Recieve.Interfaces; 12 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 13 | using TelegramClient.Core.Settings; 14 | 15 | [SingleInstance(typeof(IRecieveHandler))] 16 | internal class BadServerSaltRecieveHandler : IRecieveHandler 17 | { 18 | private static readonly ILog Log = LogManager.GetLogger(typeof(BadServerSaltRecieveHandler)); 19 | 20 | public Type[] HandleCodes { get; } = { typeof(TBadServerSalt) }; 21 | 22 | public IResponseResultSetter ResponseResultSetter { get; set; } 23 | 24 | public IClientSettings ClientSettings { get; set; } 25 | 26 | public void HandleResponce(IObject obj) 27 | { 28 | var message = obj.Cast(); 29 | 30 | Log.Info($"Bad server sault detected! message id = {message.BadMsgId} "); 31 | 32 | ClientSettings.Session.Salt = message.NewServerSalt; 33 | 34 | ResponseResultSetter.ReturnException(message.BadMsgId, new BadServerSaltException()); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/FutureSaltsRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using OpenTl.Schema; 8 | 9 | using TelegramClient.Core.IoC; 10 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 11 | 12 | [SingleInstance(typeof(IRecieveHandler))] 13 | internal class FutureSaltsRecieveHandler : IRecieveHandler 14 | { 15 | private static readonly ILog Log = LogManager.GetLogger(typeof(FutureSaltsRecieveHandler)); 16 | 17 | public Type[] HandleCodes { get; } = { typeof(TFutureSalts) }; 18 | 19 | public void HandleResponce(IObject obj) 20 | { 21 | var message = obj.Cast(); 22 | 23 | Log.Debug($"Handle Future Salts for request {message.ReqMsgId}"); 24 | 25 | throw new NotImplementedException("The future sault does not supported yet"); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/Interfaces/IRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers.Interfaces 2 | { 3 | using System; 4 | 5 | using OpenTl.Schema; 6 | 7 | public interface IRecieveHandler 8 | { 9 | Type[] HandleCodes { get; } 10 | 11 | void HandleResponce(IObject obj); 12 | } 13 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/MsgDetailedInfoRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using OpenTl.Schema; 8 | 9 | using TelegramClient.Core.IoC; 10 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 11 | 12 | [SingleInstance(typeof(IRecieveHandler))] 13 | internal class MsgDetailedInfoRecieveHandler : IRecieveHandler 14 | { 15 | private static readonly ILog Log = LogManager.GetLogger(typeof(PingRecieveHandler)); 16 | 17 | public Type[] HandleCodes { get; } = { typeof(TMsgDetailedInfo) }; 18 | 19 | public void HandleResponce(IObject obj) 20 | { 21 | Log.Debug("Handle MsgDetailedInfo"); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/MsgNewDetailedInfoRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using OpenTl.Schema; 8 | 9 | using TelegramClient.Core.IoC; 10 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 11 | 12 | [SingleInstance(typeof(IRecieveHandler))] 13 | public class MsgNewDetailedInfoRecieveHandler : IRecieveHandler 14 | { 15 | private static readonly ILog Log = LogManager.GetLogger(typeof(MsgNewDetailedInfoRecieveHandler)); 16 | 17 | public Type[] HandleCodes { get; } = { typeof(TMsgNewDetailedInfo) }; 18 | 19 | public void HandleResponce(IObject obj) 20 | { 21 | Log.Debug("Handle a TMsgNewDetailedInfo"); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/MsgsAckRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using Newtonsoft.Json; 8 | 9 | using OpenTl.Schema; 10 | 11 | using TelegramClient.Core.IoC; 12 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 13 | 14 | [SingleInstance(typeof(IRecieveHandler))] 15 | internal class MsgsAckRecieveHandler : IRecieveHandler 16 | { 17 | private static readonly ILog Log = LogManager.GetLogger(typeof(MsgsAckRecieveHandler)); 18 | 19 | public Type[] HandleCodes { get; } = { typeof(TMsgsAck) }; 20 | 21 | public void HandleResponce(IObject obj) 22 | { 23 | Log.Debug("Handle a messages ack"); 24 | 25 | if (Log.IsDebugEnabled) 26 | { 27 | var message = obj.Cast(); 28 | 29 | var jMessages = JsonConvert.SerializeObject(message.MsgIds.Items); 30 | Log.Debug($"Receiving confirmation of the messages: {jMessages}"); 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/NewSessionCreatedRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using OpenTl.Schema; 8 | 9 | using TelegramClient.Core.IoC; 10 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 11 | 12 | [SingleInstance(typeof(IRecieveHandler))] 13 | internal class NewSessionCreatedRecieveHandler : IRecieveHandler 14 | { 15 | private static readonly ILog Log = LogManager.GetLogger(typeof(NewSessionCreatedRecieveHandler)); 16 | 17 | public Type[] HandleCodes { get; } = { typeof(TNewSessionCreated) }; 18 | 19 | public void HandleResponce(IObject obj) 20 | { 21 | Log.Debug("Handle a new session was created"); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/PingRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using OpenTl.Schema; 8 | 9 | using TelegramClient.Core.IoC; 10 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 11 | 12 | [SingleInstance(typeof(IRecieveHandler))] 13 | internal class PingRecieveHandler : IRecieveHandler 14 | { 15 | private static readonly ILog Log = LogManager.GetLogger(typeof(PingRecieveHandler)); 16 | 17 | public Type[] HandleCodes { get; } = { typeof(RequestPing) }; 18 | 19 | public void HandleResponce(IObject obj) 20 | { 21 | Log.Debug("Handle ping"); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/PongRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using Newtonsoft.Json; 8 | 9 | using OpenTl.Schema; 10 | 11 | using TelegramClient.Core.IoC; 12 | using TelegramClient.Core.Network.Confirm; 13 | using TelegramClient.Core.Network.Recieve.Interfaces; 14 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 15 | 16 | [SingleInstance(typeof(IRecieveHandler))] 17 | internal class PongRecieveHandler : IRecieveHandler 18 | { 19 | private static readonly ILog Log = LogManager.GetLogger(typeof(PongRecieveHandler)); 20 | 21 | public Type[] HandleCodes { get; } = { typeof(TPong) }; 22 | 23 | public IResponseResultSetter ResponseResultSetter { get; set; } 24 | 25 | public void HandleResponce(IObject obj) 26 | { 27 | var message = obj.Cast(); 28 | 29 | if (Log.IsDebugEnabled) 30 | { 31 | var jMessages = JsonConvert.SerializeObject(message); 32 | Log.Debug($"Handle pong for request = {jMessages}"); 33 | } 34 | 35 | ResponseResultSetter.ReturnResult(message.MsgId, obj); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/RpcResultRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | using System.Text.RegularExpressions; 5 | 6 | using log4net; 7 | 8 | using OpenTl.Schema; 9 | 10 | using TelegramClient.Core.Exceptions; 11 | using TelegramClient.Core.IoC; 12 | using TelegramClient.Core.Network.Exceptions; 13 | using TelegramClient.Core.Network.Recieve.Interfaces; 14 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 15 | 16 | [SingleInstance(typeof(IRecieveHandler))] 17 | internal class RpcResultRecieveHandler : IRecieveHandler 18 | { 19 | private static readonly ILog Log = LogManager.GetLogger(typeof(RpcResultRecieveHandler)); 20 | 21 | public Type[] HandleCodes { get; } = { typeof(TRpcResult) }; 22 | 23 | public IResponseResultSetter ResponseResultSetter { get; set; } 24 | 25 | public IGZipPackedHandler ZipPackedHandler { get; set; } 26 | 27 | public void HandleResponce(IObject obj) 28 | { 29 | Log.Debug("Handle RpcResult"); 30 | 31 | var message = obj.Cast(); 32 | 33 | Log.Debug($"Process RpcResult with request id = '{message.ReqMsgId}'"); 34 | 35 | switch (message.Result) 36 | { 37 | case TRpcError error: 38 | HandleRpcError(message.ReqMsgId, error); 39 | break; 40 | 41 | case TgZipPacked zipPacked: 42 | var result = ZipPackedHandler.HandleGZipPacked(zipPacked); 43 | ResponseResultSetter.ReturnResult(message.ReqMsgId, result); 44 | break; 45 | 46 | default: 47 | ResponseResultSetter.ReturnResult(message.ReqMsgId, message.Result); 48 | break; 49 | } 50 | } 51 | 52 | private void HandleRpcError(long messageReqMsgId, TRpcError error) 53 | { 54 | // rpc_error 55 | 56 | Log.Warn($"Recieve error from server: {error.ErrorMessage}"); 57 | 58 | Exception exception; 59 | switch (error.ErrorMessage) 60 | { 61 | case var floodMessage when floodMessage.StartsWith("FLOOD_WAIT_"): 62 | var floodMessageTime = Regex.Match(floodMessage, @"\d+").Value; 63 | var seconds = int.Parse(floodMessageTime); 64 | exception = new FloodException(TimeSpan.FromSeconds(seconds)); 65 | break; 66 | 67 | case var phoneMigrate when phoneMigrate.StartsWith("PHONE_MIGRATE_"): 68 | var phoneMigrateDcNumber = Regex.Match(phoneMigrate, @"\d+").Value; 69 | var phoneMigrateDcIdx = int.Parse(phoneMigrateDcNumber); 70 | exception = new PhoneMigrationException(phoneMigrateDcIdx); 71 | break; 72 | 73 | case var fileMigrate when fileMigrate.StartsWith("FILE_MIGRATE_"): 74 | var fileMigrateDcNumber = Regex.Match(fileMigrate, @"\d+").Value; 75 | var fileMigrateDcIdx = int.Parse(fileMigrateDcNumber); 76 | exception = new FileMigrationException(fileMigrateDcIdx); 77 | break; 78 | 79 | case var userMigrate when userMigrate.StartsWith("USER_MIGRATE_"): 80 | var userMigrateDcNumber = Regex.Match(userMigrate, @"\d+").Value; 81 | var userMigrateDcIdx = int.Parse(userMigrateDcNumber); 82 | exception = new UserMigrationException(userMigrateDcIdx); 83 | break; 84 | 85 | case "PHONE_CODE_INVALID": 86 | exception = new InvalidPhoneCodeException("The numeric code used to authenticate does not match the numeric code sent by SMS/Telegram"); 87 | break; 88 | 89 | case "SESSION_PASSWORD_NEEDED": 90 | exception = new CloudPasswordNeededException("This Account has Cloud Password !"); 91 | break; 92 | 93 | case "AUTH_RESTART": 94 | ResponseResultSetter.ReturnException(new AuthRestartException()); 95 | return; 96 | 97 | default: 98 | exception = new InvalidOperationException(error.ErrorMessage); 99 | break; 100 | } 101 | 102 | ResponseResultSetter.ReturnException(messageReqMsgId, exception); 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/RecieveHandlers/UpdatesRecieveHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.RecieveHandlers 2 | { 3 | using System; 4 | 5 | using log4net; 6 | 7 | using Newtonsoft.Json; 8 | 9 | using OpenTl.Schema; 10 | 11 | using TelegramClient.Core.ApiServies.Interfaces; 12 | using TelegramClient.Core.IoC; 13 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 14 | 15 | [SingleInstance(typeof(IRecieveHandler))] 16 | internal class UpdatesRecieveHandler : IRecieveHandler 17 | { 18 | private static readonly ILog Log = LogManager.GetLogger(typeof(UpdatesRecieveHandler)); 19 | 20 | public Type[] HandleCodes { get; } = 21 | { 22 | typeof(TUpdateShortMessage), 23 | typeof(TUpdateShortSentMessage), 24 | typeof(TUpdatesTooLong), 25 | typeof(TUpdateShortChatMessage), 26 | typeof(TUpdateShort), 27 | typeof(TUpdatesCombined), 28 | typeof(TUpdates) 29 | }; 30 | 31 | public IUpdatesApiServiceRaiser UpdateRaiser { get; set; } 32 | 33 | public void HandleResponce(IObject obj) 34 | { 35 | if (Log.IsDebugEnabled) 36 | { 37 | var jUpdate = JsonConvert.SerializeObject(obj); 38 | Log.Debug($"Recieve Updates \n{jUpdate}"); 39 | } 40 | 41 | UpdateRaiser.OnUpdateRecieve(obj.Cast()); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Send/MtProtoPlainSender.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Send 2 | { 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | using log4net; 8 | 9 | using TelegramClient.Core.Helpers; 10 | using TelegramClient.Core.IoC; 11 | using TelegramClient.Core.Network.Interfaces; 12 | using TelegramClient.Core.Network.Tcp; 13 | using TelegramClient.Core.Settings; 14 | 15 | [SingleInstance(typeof(IMtProtoPlainSender))] 16 | internal class MtProtoPlainSender : IMtProtoPlainSender 17 | { 18 | private static readonly ILog Log = LogManager.GetLogger(typeof(MtProtoPlainSender)); 19 | 20 | public ITcpTransport TcpTransport { get; set; } 21 | 22 | public IClientSettings ClientSettings { get; set; } 23 | 24 | public async Task SendAndReceive(byte[] data, CancellationToken cancellationToken = default(CancellationToken)) 25 | { 26 | var preparedPacket = PrepareToSend(data); 27 | 28 | await TcpTransport.Send(preparedPacket, cancellationToken).ConfigureAwait(false); 29 | 30 | var result = await TcpTransport.Receieve().ConfigureAwait(false); 31 | 32 | return ProcessReceivedMessage(result); 33 | } 34 | 35 | private byte[] PrepareToSend(byte[] data) 36 | { 37 | var newMessageId = ClientSettings.Session.GenerateMsgId(); 38 | Log.Debug($"Send message with id : {newMessageId}"); 39 | 40 | return BinaryHelper.WriteBytes( 41 | writer => 42 | { 43 | writer.Write((long)0); 44 | writer.Write(newMessageId); 45 | writer.Write(data.Length); 46 | writer.Write(data); 47 | }); 48 | } 49 | 50 | private static byte[] ProcessReceivedMessage(byte[] recievedMessage) 51 | { 52 | using (var memoryStream = new MemoryStream(recievedMessage)) 53 | using (var binaryReader = new BinaryReader(memoryStream)) 54 | { 55 | var authKeyid = binaryReader.ReadInt64(); 56 | var messageId = binaryReader.ReadInt64(); 57 | var messageLength = binaryReader.ReadInt32(); 58 | 59 | Log.Debug($"Recieve message with id : {messageId}"); 60 | 61 | var response = binaryReader.ReadBytes(messageLength); 62 | 63 | return response; 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Send/MtProtoSendService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Send 2 | { 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | using log4net; 8 | 9 | using OpenTl.Schema; 10 | using OpenTl.Schema.Serialization; 11 | 12 | using TelegramClient.Core.IoC; 13 | using TelegramClient.Core.MTProto.Crypto; 14 | using TelegramClient.Core.Network.Interfaces; 15 | using TelegramClient.Core.Network.Recieve.Interfaces; 16 | using TelegramClient.Core.Network.Tcp; 17 | using TelegramClient.Core.Sessions; 18 | using TelegramClient.Core.Settings; 19 | using TelegramClient.Core.Utils; 20 | 21 | [SingleInstance(typeof(IMtProtoSender))] 22 | internal class MtProtoSendService : IMtProtoSender 23 | { 24 | private static readonly ILog Log = LogManager.GetLogger(typeof(MtProtoSendService)); 25 | 26 | public ITcpTransport TcpTransport { get; set; } 27 | 28 | public IClientSettings ClientSettings { get; set; } 29 | 30 | public ISessionStore SessionStore { get; set; } 31 | 32 | public IResponseResultGetter ResponseResultGetter { get; set; } 33 | 34 | 35 | public async Task Send(IObject obj, CancellationToken cancellationToken) 36 | { 37 | Log.Debug($"Send object {obj}"); 38 | 39 | (byte[] preparedData, long mesId) = await PrepareToSend(obj).ConfigureAwait(false); 40 | 41 | await TcpTransport.Send(preparedData, cancellationToken).ConfigureAwait(false); 42 | 43 | await SessionStore.Save().ConfigureAwait(false); 44 | 45 | return mesId; 46 | } 47 | 48 | public async Task> SendAndWaitResponse(IObject obj, CancellationToken cancellationToken) 49 | { 50 | Log.Debug($"Send object and wait for a response {obj}"); 51 | 52 | (byte[] preparedData, long mesId) = await PrepareToSend(obj).ConfigureAwait(false); 53 | 54 | var responseTask = ResponseResultGetter.Receive(mesId, cancellationToken); 55 | 56 | await TcpTransport.Send(preparedData, cancellationToken).ConfigureAwait(false); 57 | 58 | await SessionStore.Save().ConfigureAwait(false); 59 | 60 | return responseTask; 61 | } 62 | 63 | private static MemoryStream MakeMemory(int len) 64 | { 65 | return new MemoryStream(new byte[len], 0, len, true, true); 66 | } 67 | 68 | private async Task<(byte[], long)> PrepareToSend(IObject obj) 69 | { 70 | var packet = Serializer.SerializeObject(obj); 71 | 72 | (long mesId, int seqNo) = await ClientSettings.Session.GenerateMsgIdAndSeqNo(obj is IRequest).ConfigureAwait(false); 73 | 74 | Log.Debug($"Send message with Id = {mesId} and seqNo = {seqNo}"); 75 | 76 | byte[] msgKey; 77 | byte[] ciphertext; 78 | var randomPaddingLenght = TlHelpers.GenerateRandomInt(1024 / 16) * 16; 79 | 80 | using (var plaintextPacket = MakeMemory(8 + 8 + 8 + 4 + 4 + packet.Length + randomPaddingLenght)) 81 | { 82 | using (var plaintextWriter = new BinaryWriter(plaintextPacket)) 83 | { 84 | plaintextWriter.Write(ClientSettings.Session.Salt); 85 | plaintextWriter.Write(ClientSettings.Session.Id); 86 | plaintextWriter.Write(mesId); 87 | plaintextWriter.Write(seqNo); 88 | plaintextWriter.Write(packet.Length); 89 | plaintextWriter.Write(packet); 90 | 91 | plaintextWriter.Write(TlHelpers.GenerateRandomBytes(randomPaddingLenght)); 92 | 93 | plaintextPacket.TryGetBuffer(out var buffer); 94 | 95 | var authKey = ClientSettings.Session.AuthKey.Data; 96 | msgKey = TlHelpers.CalcMsgKey(authKey, buffer.Array); 97 | 98 | var key = TlHelpers.CalcKey(authKey, msgKey, true); 99 | 100 | ciphertext = AES.EncryptAes(key, buffer.Array); 101 | } 102 | } 103 | 104 | using (var ciphertextPacket = MakeMemory(8 + 16 + ciphertext.Length)) 105 | { 106 | using (var writer = new BinaryWriter(ciphertextPacket)) 107 | { 108 | writer.Write(ClientSettings.Session.AuthKey.Id); 109 | writer.Write(msgKey); 110 | writer.Write(ciphertext); 111 | 112 | return (ciphertextPacket.ToArray(), mesId); 113 | } 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Tcp/ITcpService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Tcp 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | internal interface ITcpService : IDisposable 8 | { 9 | Task Disconnect(); 10 | 11 | Task Read(byte[] buffer, int offset, int count, CancellationToken cancellationToken); 12 | 13 | Task Send(byte[] encodedMessage, CancellationToken cancellationToken); 14 | } 15 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Tcp/ITcpTransport.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Tcp 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | internal interface ITcpTransport : IDisposable 8 | { 9 | Task Disconnect(); 10 | 11 | Task Receieve(); 12 | 13 | Task Send(byte[] packet, CancellationToken cancellationToken); 14 | } 15 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Tcp/TcpMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Tcp 2 | { 3 | using System; 4 | using System.IO; 5 | 6 | using BarsGroup.CodeGuard; 7 | 8 | using TelegramClient.Core.Utils; 9 | 10 | internal class TcpMessage 11 | { 12 | public int SequneceNumber { get; } 13 | 14 | public byte[] Body { get; } 15 | 16 | public TcpMessage(int seqNumber, byte[] body) 17 | { 18 | Guard.That(body, nameof(body)).IsNotNull(); 19 | 20 | SequneceNumber = seqNumber; 21 | Body = body; 22 | } 23 | 24 | public static TcpMessage Decode(byte[] body) 25 | { 26 | Guard.That(body, nameof(body)).IsNotNull(); 27 | Guard.That(body.Length, nameof(body.Length)).IsGreaterThan(12); 28 | 29 | using (var memoryStream = new MemoryStream(body)) 30 | { 31 | using (var binaryReader = new BinaryReader(memoryStream)) 32 | { 33 | var packetLength = binaryReader.ReadInt32(); 34 | 35 | if (packetLength < 12) 36 | { 37 | throw new InvalidOperationException($"invalid packet length: {packetLength}"); 38 | } 39 | 40 | var seq = binaryReader.ReadInt32(); 41 | var packet = binaryReader.ReadBytes(packetLength - 12); 42 | var checksum = binaryReader.ReadInt32(); 43 | 44 | var crc32 = new Crc32(); 45 | crc32.SlurpBlock(body, 0, packetLength - 4); 46 | var validChecksum = crc32.Crc32Result; 47 | 48 | if (checksum != validChecksum) 49 | { 50 | throw new InvalidOperationException("invalid checksum! skip"); 51 | } 52 | 53 | return new TcpMessage(seq, packet); 54 | } 55 | } 56 | } 57 | 58 | public byte[] Encode() 59 | { 60 | using (var memoryStream = new MemoryStream()) 61 | { 62 | using (var binaryWriter = new BinaryWriter(memoryStream)) 63 | { 64 | // https://core.telegram.org/mtproto#tcp-transport 65 | /* 66 | 4 length bytes are added at the front 67 | (to include the length, the sequence number, and CRC32; always divisible by 4) 68 | and 4 bytes with the packet sequence number within this TCP connection 69 | (the first packet sent is numbered 0, the next one 1, etc.), 70 | and 4 CRC32 bytes at the end (length, sequence number, and payload together). 71 | */ 72 | binaryWriter.Write(Body.Length + 12); 73 | binaryWriter.Write(SequneceNumber); 74 | binaryWriter.Write(Body); 75 | var crc32 = new Crc32(); 76 | 77 | memoryStream.TryGetBuffer(out var buffer); 78 | crc32.SlurpBlock(buffer.Array, 0, 8 + Body.Length); 79 | 80 | binaryWriter.Write(crc32.Crc32Result); 81 | 82 | var transportPacket = memoryStream.ToArray(); 83 | 84 | // Debug.WriteLine("Tcp packet #{0}\n{1}", SequneceNumber, BitConverter.ToString(transportPacket)); 85 | 86 | return transportPacket; 87 | } 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Tcp/TcpService.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Tcp 2 | { 3 | using System; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | using TelegramClient.Core.IoC; 10 | using TelegramClient.Core.Network.Exceptions; 11 | using TelegramClient.Core.Settings; 12 | 13 | [SingleInstance(typeof(ITcpService))] 14 | internal class TcpService : ITcpService 15 | { 16 | private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); 17 | 18 | private TcpClient _tcpClient; 19 | 20 | public IClientSettings ClientSettings { get; set; } 21 | 22 | private async Task Connect() 23 | { 24 | _tcpClient = new TcpClient(); 25 | 26 | await _tcpClient.ConnectAsync(ClientSettings.Session.ServerAddress, ClientSettings.Session.Port).ConfigureAwait(false); 27 | 28 | _tcpClient.GetStream().ReadTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds; 29 | } 30 | 31 | public Task Disconnect() 32 | { 33 | if (_tcpClient != null) 34 | { 35 | _tcpClient?.Dispose(); 36 | _tcpClient = null; 37 | } 38 | 39 | return Task.CompletedTask; 40 | } 41 | 42 | public void Dispose() 43 | { 44 | Dispose(true); 45 | GC.SuppressFinalize(this); 46 | } 47 | 48 | private bool IsTcpClientConnected() 49 | { 50 | if (_tcpClient == null || !_tcpClient.Connected || 51 | _tcpClient.Client == null || !_tcpClient.Client.Connected) 52 | { 53 | return false; 54 | } 55 | 56 | var endpoint = (IPEndPoint)_tcpClient.Client.RemoteEndPoint; 57 | var session = ClientSettings.Session; 58 | 59 | if (endpoint.Address.ToString() != session.ServerAddress || endpoint.Port != session.Port) 60 | { 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | public async Task Read(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 68 | { 69 | await CheckConnectionState().ConfigureAwait(false); 70 | 71 | return await _tcpClient.GetStream().ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); 72 | } 73 | 74 | public async Task Send(byte[] encodedMessage, CancellationToken cancellationToken) 75 | { 76 | await CheckConnectionState().ConfigureAwait(false); 77 | 78 | await _tcpClient.GetStream().WriteAsync(encodedMessage, 0, encodedMessage.Length, cancellationToken).ConfigureAwait(false); 79 | } 80 | 81 | private async Task CheckConnectionState() 82 | { 83 | if (!IsTcpClientConnected()) 84 | { 85 | await _semaphore.WaitAsync().ConfigureAwait(false); 86 | 87 | try 88 | { 89 | if (!IsTcpClientConnected()) 90 | { 91 | var previouslyConnected = _tcpClient != null; 92 | 93 | await Disconnect().ConfigureAwait(false); 94 | 95 | await Connect().ConfigureAwait(false); 96 | 97 | if (previouslyConnected) 98 | { 99 | throw new DisconnectedException(); 100 | } 101 | } 102 | } 103 | finally 104 | { 105 | _semaphore.Release(); 106 | } 107 | } 108 | } 109 | 110 | private void Dispose(bool disposing) 111 | { 112 | if (disposing) 113 | { 114 | _semaphore?.Dispose(); 115 | _tcpClient?.Dispose(); 116 | } 117 | } 118 | 119 | ~TcpService() 120 | { 121 | Dispose(false); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Network/Tcp/TcpTransport.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Network.Tcp 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using log4net; 9 | 10 | using NullGuard; 11 | 12 | using TelegramClient.Core.IoC; 13 | using TelegramClient.Core.Utils; 14 | 15 | [SingleInstance(typeof(ITcpTransport))] 16 | internal class TcpTransport : ITcpTransport 17 | { 18 | private static readonly ILog Log = LogManager.GetLogger(typeof(TcpTransport)); 19 | 20 | private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); 21 | 22 | private readonly ConcurrentQueue<(byte[], TaskCompletionSource, CancellationToken)> _messageQueue = 23 | new ConcurrentQueue<(byte[], TaskCompletionSource, CancellationToken)>(); 24 | 25 | private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); 26 | 27 | private int _messageSequenceNumber; 28 | 29 | public ITcpService TcpService { get; set; } 30 | 31 | public async Task Disconnect() 32 | { 33 | _messageSequenceNumber = 0; 34 | await TcpService.Disconnect().ConfigureAwait(false); 35 | } 36 | 37 | public void Dispose() 38 | { 39 | Dispose(true); 40 | GC.SuppressFinalize(this); 41 | } 42 | 43 | public async Task Receieve() 44 | { 45 | var cancellationToken = default(CancellationToken); 46 | var packetLengthBytes = new byte[4]; 47 | var readLenghtBytes = await TcpService.Read(packetLengthBytes, 0, 4, cancellationToken).ConfigureAwait(false); 48 | 49 | if (readLenghtBytes != 4) 50 | { 51 | throw new InvalidOperationException("Couldn't read the packet length"); 52 | } 53 | 54 | var packetLength = BitConverter.ToInt32(packetLengthBytes, 0); 55 | 56 | var seqBytes = new byte[4]; 57 | var readSeqBytes = await TcpService.Read(seqBytes, 0, 4, cancellationToken).ConfigureAwait(false); 58 | 59 | if (readSeqBytes != 4) 60 | { 61 | throw new InvalidOperationException("Couldn't read the sequence"); 62 | } 63 | 64 | var mesSeqNo = BitConverter.ToInt32(seqBytes, 0); 65 | 66 | Log.Debug($"Recieve message with seq_no {mesSeqNo}"); 67 | 68 | if (packetLength < 12) 69 | { 70 | throw new InvalidOperationException("Invalid packet length"); 71 | } 72 | 73 | var readBytes = 0; 74 | var body = new byte[packetLength - 12]; 75 | var neededToRead = packetLength - 12; 76 | 77 | do 78 | { 79 | var bodyByte = new byte[packetLength - 12]; 80 | var availableBytes = await TcpService.Read(bodyByte, 0, neededToRead, cancellationToken).ConfigureAwait(false); 81 | 82 | neededToRead -= availableBytes; 83 | Buffer.BlockCopy(bodyByte, 0, body, readBytes, availableBytes); 84 | readBytes += availableBytes; 85 | } 86 | while (readBytes < packetLength - 12); 87 | 88 | var crcBytes = new byte[4]; 89 | var readCrcBytes = await TcpService.Read(crcBytes, 0, 4, cancellationToken).ConfigureAwait(false); 90 | if (readCrcBytes != 4) 91 | { 92 | throw new InvalidOperationException("Couldn't read the crc"); 93 | } 94 | 95 | var checksum = BitConverter.ToInt32(crcBytes, 0); 96 | 97 | var rv = new byte[packetLengthBytes.Length + seqBytes.Length + body.Length]; 98 | 99 | Buffer.BlockCopy(packetLengthBytes, 0, rv, 0, packetLengthBytes.Length); 100 | Buffer.BlockCopy(seqBytes, 0, rv, packetLengthBytes.Length, seqBytes.Length); 101 | Buffer.BlockCopy(body, 0, rv, packetLengthBytes.Length + seqBytes.Length, body.Length); 102 | var crc32 = new Crc32(); 103 | crc32.SlurpBlock(rv, 0, rv.Length); 104 | var validChecksum = crc32.Crc32Result; 105 | 106 | if (checksum != validChecksum) 107 | { 108 | throw new InvalidOperationException("invalid checksum! skip"); 109 | } 110 | 111 | return body; 112 | } 113 | 114 | public Task Send(byte[] packet, CancellationToken cancellationToken) 115 | { 116 | var tcs = new TaskCompletionSource(); 117 | 118 | PushToQueue(packet, tcs, cancellationToken); 119 | 120 | return tcs.Task; 121 | } 122 | 123 | public async Task SendPacket(byte[] packet, CancellationToken cancelationToken) 124 | { 125 | var messageSequenceNumber = _messageSequenceNumber++; 126 | 127 | Log.Debug($"Sending message with seq_no {messageSequenceNumber}"); 128 | 129 | var tcpMessage = new TcpMessage(messageSequenceNumber, packet); 130 | 131 | var encodedMessage = tcpMessage.Encode(); 132 | 133 | await TcpService.Send(encodedMessage, cancelationToken).ConfigureAwait(false); 134 | } 135 | 136 | private void Dispose(bool disposing) 137 | { 138 | if (disposing) 139 | { 140 | _cancellationTokenSource?.Cancel(); 141 | TcpService?.Dispose(); 142 | } 143 | } 144 | 145 | private void PushToQueue(byte[] packet, TaskCompletionSource tcs, CancellationToken cancellationToken) 146 | { 147 | _messageQueue.Enqueue((packet, tcs, cancellationToken)); 148 | SendAllMessagesFromQueue().ConfigureAwait(false); 149 | } 150 | 151 | private async Task SendAllMessagesFromQueue() 152 | { 153 | await _semaphoreSlim.WaitAsync().ConfigureAwait(false); 154 | if (!_messageQueue.IsEmpty) 155 | { 156 | await SendFromQueue().ContinueWith(task => _semaphoreSlim.Release()).ConfigureAwait(false); 157 | } 158 | else 159 | { 160 | _semaphoreSlim.Release(); 161 | } 162 | } 163 | 164 | private async Task SendFromQueue() 165 | { 166 | while (!_messageQueue.IsEmpty) 167 | { 168 | _messageQueue.TryDequeue(out var item); 169 | (byte[] message, TaskCompletionSource tcs, CancellationToken token) = item; 170 | 171 | try 172 | { 173 | await SendPacket(message, token).ConfigureAwait(false); 174 | tcs.SetResult(true); 175 | } 176 | catch (Exception e) 177 | { 178 | Log.Error("Failed to process the message", e); 179 | 180 | tcs.SetException(e); 181 | } 182 | } 183 | } 184 | 185 | ~TcpTransport() 186 | { 187 | Dispose(false); 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Sessions/FileSessionStoreProvider.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Sessions 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using log4net; 9 | 10 | using NullGuard; 11 | 12 | public class FileSessionStoreProvider : ISessionStoreProvider 13 | { 14 | private static readonly ILog Log = LogManager.GetLogger(typeof(FileSessionStoreProvider)); 15 | 16 | private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); 17 | 18 | private readonly string _sessionFile; 19 | 20 | private FileStream _fileStream; 21 | 22 | public FileSessionStoreProvider(string sessionTag) 23 | { 24 | _sessionFile = $"{sessionTag}.dat"; 25 | } 26 | 27 | [return:AllowNull] 28 | public async Task LoadSession() 29 | { 30 | Log.Debug($"Load session for sessionTag = {_sessionFile}"); 31 | 32 | await EnsureStreamOpen(); 33 | 34 | var buffer = new byte[2048]; 35 | 36 | await _semaphore.WaitAsync(); 37 | 38 | _fileStream.Position = 0; 39 | 40 | if (_fileStream.Length == 0) 41 | { 42 | _semaphore.Release(); 43 | 44 | return null; 45 | } 46 | 47 | await _fileStream.ReadAsync(buffer, 0, 2048).ConfigureAwait(false); 48 | 49 | _semaphore.Release(); 50 | 51 | return buffer; 52 | } 53 | 54 | public Task RemoveSession() 55 | { 56 | if (File.Exists(_sessionFile)) 57 | { 58 | _fileStream.Dispose(); 59 | _fileStream = null; 60 | 61 | File.Delete(_sessionFile); 62 | } 63 | 64 | return Task.FromResult(true); 65 | } 66 | 67 | public async Task SaveSession(byte[] session) 68 | { 69 | Log.Debug($"Save session into {_sessionFile}"); 70 | 71 | await EnsureStreamOpen(); 72 | 73 | await _semaphore.WaitAsync().ConfigureAwait(false); 74 | 75 | _fileStream.Position = 0; 76 | await _fileStream.WriteAsync(session, 0, session.Length).ConfigureAwait(false); 77 | await _fileStream.FlushAsync().ConfigureAwait(false); 78 | 79 | _semaphore.Release(); 80 | } 81 | 82 | private async Task EnsureStreamOpen() 83 | { 84 | if (_fileStream == null) 85 | { 86 | await _semaphore.WaitAsync().ConfigureAwait(false); 87 | 88 | if (_fileStream == null) 89 | { 90 | _fileStream = new FileStream(_sessionFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); 91 | } 92 | 93 | _semaphore.Release(); 94 | } 95 | } 96 | 97 | public void Dispose() 98 | { 99 | _semaphore?.Dispose(); 100 | _fileStream?.Dispose(); 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Sessions/ISession.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Sessions 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using OpenTl.Schema; 7 | 8 | using TelegramClient.Core.MTProto.Crypto; 9 | 10 | internal interface ISession 11 | { 12 | string ServerAddress { get; set; } 13 | 14 | int Port { get; set; } 15 | 16 | AuthKey AuthKey { get; set; } 17 | 18 | ulong Id { get; set; } 19 | 20 | long Salt { get; set; } 21 | 22 | int TimeOffset { get; set; } 23 | 24 | int SessionExpires { get; set; } 25 | 26 | TUser User { get; set; } 27 | 28 | long GenerateMsgId(); 29 | 30 | Task<(long, int)> GenerateMsgIdAndSeqNo(bool confirmed); 31 | 32 | byte[] ToBytes(); 33 | } 34 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Sessions/ISessionStore.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Sessions 2 | { 3 | using System.Threading.Tasks; 4 | 5 | internal interface ISessionStore 6 | { 7 | Task Load(); 8 | 9 | Task Remove(); 10 | 11 | Task Save(); 12 | } 13 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Sessions/ISessionStoreProvider.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Sessions 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | public interface ISessionStoreProvider : IDisposable 7 | { 8 | Task LoadSession(); 9 | 10 | Task RemoveSession(); 11 | 12 | Task SaveSession(byte[] session); 13 | } 14 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Sessions/Session.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Sessions 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using OpenTl.Schema; 9 | using OpenTl.Schema.Serialization; 10 | 11 | using TelegramClient.Core.MTProto; 12 | using TelegramClient.Core.MTProto.Crypto; 13 | 14 | internal class Session : ISession 15 | { 16 | private static readonly Random Random = new Random(); 17 | 18 | private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); 19 | 20 | private int _msgIdInc; 21 | 22 | public string ServerAddress { get; set; } 23 | 24 | public int Port { get; set; } 25 | 26 | public AuthKey AuthKey { get; set; } 27 | 28 | public ulong Id { get; set; } 29 | 30 | public long Salt { get; set; } 31 | 32 | public int TimeOffset { get; set; } 33 | 34 | public int SessionExpires { get; set; } 35 | 36 | public TUser User { get; set; } 37 | 38 | private int SessionSeqNo { get; set; } 39 | 40 | public static Session Create() 41 | { 42 | return new Session 43 | { 44 | Id = ((ulong)Random.Next() << 32) | (ulong)Random.Next() 45 | }; 46 | } 47 | 48 | public static Session FromBytes(byte[] buffer) 49 | { 50 | if (buffer == null) 51 | { 52 | return null; 53 | } 54 | 55 | using (var stream = new MemoryStream(buffer)) 56 | using (var reader = new BinaryReader(stream)) 57 | { 58 | var id = reader.ReadUInt64(); 59 | var sequence = reader.ReadInt32(); 60 | var salt = reader.ReadInt64(); 61 | var timeOffset = reader.ReadInt32(); 62 | var serverAddress = Serializers.String.Read(reader); 63 | var port = reader.ReadInt32(); 64 | 65 | var isAuthExsist = reader.ReadInt32() == 1; 66 | var sessionExpires = 0; 67 | TUser tlUser = null; 68 | if (isAuthExsist) 69 | { 70 | sessionExpires = reader.ReadInt32(); 71 | tlUser = (TUser)Serializer.DeserializeObject(reader); 72 | } 73 | 74 | var authData = Serializers.Bytes.Read(reader); 75 | 76 | return new Session 77 | { 78 | AuthKey = new AuthKey(authData), 79 | Id = id, 80 | Salt = salt, 81 | TimeOffset = timeOffset, 82 | SessionSeqNo = sequence, 83 | SessionExpires = sessionExpires, 84 | User = tlUser, 85 | ServerAddress = serverAddress, 86 | Port = port 87 | }; 88 | } 89 | } 90 | 91 | public long GenerateMsgId() 92 | { 93 | if (_msgIdInc >= 4194303 - 4) 94 | { 95 | _msgIdInc = 0; 96 | } 97 | else 98 | { 99 | _msgIdInc += 4; 100 | } 101 | 102 | var seconds = (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds; 103 | 104 | var newMessageId = 105 | ((seconds / 1000 + TimeOffset) << 32) | 106 | ((seconds % 1000) << 22) | 107 | _msgIdInc; 108 | 109 | return newMessageId; 110 | } 111 | 112 | public async Task<(long, int)> GenerateMsgIdAndSeqNo(bool confirmed) 113 | { 114 | await _semaphoreSlim.WaitAsync().ConfigureAwait(false); 115 | 116 | try 117 | { 118 | return (GenerateMsgId(), GenerateSeqNo(confirmed)); 119 | 120 | } 121 | finally 122 | { 123 | _semaphoreSlim.Release(); 124 | } 125 | } 126 | 127 | public byte[] ToBytes() 128 | { 129 | using (var stream = new MemoryStream()) 130 | using (var writer = new BinaryWriter(stream)) 131 | { 132 | writer.Write(Id); 133 | writer.Write(SessionSeqNo); 134 | writer.Write(Salt); 135 | writer.Write(TimeOffset); 136 | Serializers.String.Write(writer, ServerAddress); 137 | writer.Write(Port); 138 | 139 | if (User != null) 140 | { 141 | writer.Write(1); 142 | writer.Write(SessionExpires); 143 | var data = Serializer.SerializeObject(User); 144 | writer.Write(data); 145 | } 146 | else 147 | { 148 | writer.Write(0); 149 | } 150 | 151 | if (AuthKey != null) 152 | { 153 | Serializers.Bytes.Write(writer, AuthKey.Data); 154 | } 155 | 156 | return stream.ToArray(); 157 | } 158 | } 159 | 160 | private int GenerateSeqNo(bool confirmed) 161 | { 162 | return confirmed 163 | ? SessionSeqNo++ * 2 + 1 164 | : SessionSeqNo * 2; 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Sessions/SessionStore.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Sessions 2 | { 3 | using System.Threading.Tasks; 4 | 5 | using TelegramClient.Core.IoC; 6 | using TelegramClient.Core.Settings; 7 | 8 | [SingleInstance(typeof(ISessionStore))] 9 | internal class SessionStore : ISessionStore 10 | { 11 | public IClientSettings ClientSettings { get; set; } 12 | 13 | public ISessionStoreProvider StoreProvider { get; set; } 14 | 15 | public async Task Load() 16 | { 17 | var data = await StoreProvider.LoadSession().ConfigureAwait(false); 18 | return Session.FromBytes(data); 19 | } 20 | 21 | public async Task Remove() 22 | { 23 | await StoreProvider.RemoveSession().ConfigureAwait(false); 24 | } 25 | 26 | public async Task Save() 27 | { 28 | var session = ClientSettings.Session.ToBytes(); 29 | await StoreProvider.SaveSession(session).ConfigureAwait(false); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Settings/ClientSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Settings 2 | { 3 | using TelegramClient.Core.IoC; 4 | using TelegramClient.Core.Sessions; 5 | 6 | [SingleInstance(typeof(IClientSettings))] 7 | internal class ClientSettings : IClientSettings 8 | { 9 | public int AppId { get; set; } 10 | 11 | public string AppHash { get; set; } 12 | 13 | public ISession Session { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/Settings/IClientSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Settings 2 | { 3 | using TelegramClient.Core.Sessions; 4 | 5 | internal interface IClientSettings 6 | { 7 | int AppId { get; set; } 8 | 9 | string AppHash { get; set; } 10 | 11 | ISession Session { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/TelegramClient.Core/TelegramClient.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard1.6;net46 4 | 5 | 6 | Unofficial Telegram client API library 7 | 0.1.0 8 | TelegramClientApi 9 | Viktor Borisov 10 | 11 | Unoffician Telegram client api library 12 | This is a unofficial Telegram client api library 13 | telegram api client 14 | https://github.com/vik-borisov/TelegramClient 15 | https://github.com/vik-borisov/TelegramClient 16 | 17 | 18 | 19 | 3.2.43 20 | 21 | 22 | 23 | 24 | 2.0.8 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/TelegramClient.Core/TelegramClient.Core.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /src/TelegramClient.Core/Utils/Helpers.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Core.Utils 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | 7 | using BarsGroup.CodeGuard; 8 | 9 | using OpenTl.Schema; 10 | 11 | using TelegramClient.Core.MTProto.Crypto; 12 | 13 | internal class TlHelpers 14 | { 15 | private static readonly Random Random = new Random(); 16 | 17 | public static AesKeyData CalcKey(byte[] authKey, byte[] msgKey, bool client) 18 | { 19 | Guard.That(authKey.Length, nameof(authKey)).IsEqual(256); 20 | Guard.That(msgKey.Length, nameof(msgKey)).IsEqual(16); 21 | 22 | var x = client 23 | ? 0 24 | : 8; 25 | 26 | //sha256_a = SHA256 (msg_key + substr (auth_key, x, 36)); 27 | var sha256ASource = msgKey.Concat(authKey.Skip(x).Take(36)).ToArray(); 28 | var sha256A = Sha256(sha256ASource); 29 | 30 | //sha256_b = SHA256 (substr (auth_key, 40+x, 36) + msg_key); 31 | var sha256BSource = authKey.Skip(40 + x).Take(36).Concat(msgKey).ToArray(); 32 | var sha256B = Sha256(sha256BSource); 33 | 34 | //aes_key = substr (sha256_a, 0, 8) + substr (sha256_b, 8, 16) + substr (sha256_a, 24, 8); 35 | var aesKey = sha256A.Take(8).Concat(sha256B.Skip(8).Take(16)).Concat(sha256A.Skip(24).Take(8)).ToArray(); 36 | 37 | //aes_iv = substr (sha256_b, 0, 8) + substr (sha256_a, 8, 16) + substr (sha256_b, 24, 8); 38 | var aesIv = sha256B.Take(8).Concat(sha256A.Skip(8).Take(16)).Concat(sha256B.Skip(24).Take(8)).ToArray(); 39 | 40 | return new AesKeyData(aesKey, aesIv); 41 | } 42 | 43 | public static byte[] CalcMsgKey(byte[] authKey, byte[] data) 44 | { 45 | //msg_key_large = SHA256 (substr (auth_key, 88+0, 32) + plaintext + random_padding); 46 | var msgKeyLarge = Sha256(authKey.Skip(88).Take(32).Concat(data).ToArray()); 47 | 48 | //msg_key = substr (msg_key_large, 8, 16); 49 | return msgKeyLarge.Skip(8).Take(16).ToArray(); 50 | } 51 | 52 | public static byte[] GenerateRandomBytes(int num) 53 | { 54 | var data = new byte[num]; 55 | Random.NextBytes(data); 56 | return data; 57 | } 58 | 59 | public static int GenerateRandomInt(int maxLengh) 60 | { 61 | return Random.Next(maxLengh); 62 | } 63 | 64 | public static long GenerateRandomLong() 65 | { 66 | var rand = ((long)Random.Next() << 32) | Random.Next(); 67 | return rand; 68 | } 69 | 70 | /// Generate with random long numbers 71 | /// Length of list 72 | /// Returns a instance of with random long numbers 73 | /// TODO: Move to TlHelpers? 74 | public static TVector GenerateRandomTVectorLong(int length) 75 | { 76 | var randomIds = new TVector(); 77 | for (var i = 0; i < length; i++) 78 | { 79 | randomIds.Items.Add(GenerateRandomLong()); 80 | } 81 | 82 | return randomIds; 83 | } 84 | 85 | private static byte[] Sha256(byte[] data) 86 | { 87 | using (var sha1 = SHA256.Create()) 88 | { 89 | return sha1.ComputeHash(data); 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /tests/TelegramClient.Tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.debug.json 2 | -------------------------------------------------------------------------------- /tests/TelegramClient.Tests/LogOutputTester.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.Tests 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | using log4net; 8 | using log4net.Appender; 9 | using log4net.Config; 10 | using log4net.Core; 11 | using log4net.Layout; 12 | using log4net.Repository.Hierarchy; 13 | 14 | using TelegramClient.Core; 15 | 16 | using Xunit.Abstractions; 17 | 18 | public class TestOutputAppender : AppenderSkeleton 19 | { 20 | private readonly ITestOutputHelper _xunitTestOutputHelper; 21 | 22 | public TestOutputAppender(ITestOutputHelper xunitTestOutputHelper) 23 | { 24 | _xunitTestOutputHelper = xunitTestOutputHelper; 25 | Name = "TestOutputAppender"; 26 | Layout = new PatternLayout("%-5p %d %5rms %-22.22c{1} %-18.18M - %m%n"); 27 | } 28 | 29 | protected override void Append(LoggingEvent loggingEvent) 30 | { 31 | _xunitTestOutputHelper.WriteLine(RenderLoggingEvent(loggingEvent)); 32 | } 33 | } 34 | 35 | public class LogOutputTester : IDisposable 36 | { 37 | private readonly TestOutputAppender _appender; 38 | 39 | private readonly IAppenderAttachable _attachable; 40 | 41 | protected LogOutputTester(ITestOutputHelper output) 42 | { 43 | var repo = LogManager.GetRepository(typeof(ITelegramClient).GetTypeInfo().Assembly); 44 | XmlConfigurator.Configure(repo, new FileInfo("log4net.config")); 45 | 46 | var root = ((Hierarchy)repo).Root; 47 | _attachable = root; 48 | 49 | _appender = new TestOutputAppender(output); 50 | _attachable?.AddAppender(_appender); 51 | } 52 | 53 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 54 | public void Dispose() 55 | { 56 | _attachable.RemoveAppender(_appender); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /tests/TelegramClient.Tests/TelegramClient.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net46 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Always 13 | 14 | 15 | Always 16 | 17 | 18 | Always 19 | 20 | 21 | Always 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {6ddad7ef-af3b-40ef-84df-a364e83f9946} 30 | TelegramClient.Core 31 | 32 | 33 | 34 | 35 | 2.0.8 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/TelegramClient.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApiHash": "", 3 | "ApiId": "", 4 | "serverAddress": "", 5 | "serverPort": "", 6 | "NumberToAuthenticate": "", 7 | "CodeToAuthenticate": "", 8 | "PasswordToAuthenticate": "", 9 | "NotRegisteredNumberToSignUp": "", 10 | "NumberToSendMessage": "", 11 | "UserNameToSendMessage": "", 12 | "NumberToGetUserFull": "", 13 | "NumberToAddToChat": "" 14 | } -------------------------------------------------------------------------------- /tests/TelegramClient.Tests/data/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vik-borisov/TelegramClient/c967bd2f34e349529af7cbcc2ee11471a6fcbbba/tests/TelegramClient.Tests/data/cat.jpg -------------------------------------------------------------------------------- /tests/TelegramClient.Tests/log4net.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/ClientSettingsMock.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using System; 4 | 5 | using Moq; 6 | 7 | using TelegramClient.Core.Sessions; 8 | using TelegramClient.Core.Settings; 9 | 10 | internal static class ClientSettingsMock 11 | { 12 | public static Mock BuildClientSettings(this Mock mock, Func appIdFunc = null, Func appHashFunc = null) 13 | { 14 | if (appHashFunc == null) 15 | { 16 | appHashFunc = () => string.Empty; 17 | } 18 | 19 | if (appIdFunc == null) 20 | { 21 | appIdFunc = () => 0; 22 | } 23 | 24 | mock 25 | .Setup(service => service.AppHash) 26 | .Returns(appHashFunc); 27 | 28 | mock 29 | .Setup(service => service.AppId) 30 | .Returns(appIdFunc); 31 | 32 | return mock; 33 | } 34 | 35 | public static Mock AttachSession(this Mock mock, Func sessionFunc) 36 | { 37 | mock 38 | .Setup(service => service.Session) 39 | .Returns(sessionFunc); 40 | 41 | return mock; 42 | } 43 | 44 | public static Mock Create() 45 | { 46 | return new Mock(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/ConfirmationRecieveServiceMock.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Moq; 7 | 8 | using TelegramClient.Core.Network.Confirm; 9 | 10 | internal static class ConfirmationRecieveServiceMock 11 | { 12 | public static Mock BuildWaitForConfirm(this Mock mock, Func waitForConfirmFunc) 13 | { 14 | mock.Setup(service => service.WaitForConfirm(It.IsAny())) 15 | .Returns(waitForConfirmFunc); 16 | 17 | return mock; 18 | } 19 | 20 | public static Mock BuildConfirmRequest(this Mock mock, Action confirmRequestFunc) 21 | { 22 | mock.Setup(service => service.ConfirmRequest(It.IsAny())) 23 | .Callback(confirmRequestFunc); 24 | 25 | return mock; 26 | } 27 | 28 | public static Mock BuildRequestWithException(this Mock mock, Action confirmRequestFunc) 29 | { 30 | mock.Setup(service => service.RequestWithException(It.IsAny(), It.IsAny())) 31 | .Callback(confirmRequestFunc); 32 | 33 | return mock; 34 | } 35 | 36 | public static Mock Create() 37 | { 38 | return new Mock(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/ConfirmationSendServiceMock.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Moq; 7 | 8 | using TelegramClient.Core.Network.Confirm; 9 | 10 | internal static class ConfirmationSendServiceMock 11 | { 12 | public static Mock BuildAddForSend(this Mock mock, Action addForSendFunc) 13 | { 14 | mock.Setup(service => service.AddForSend(It.IsAny())) 15 | .Callback(addForSendFunc); 16 | 17 | return mock; 18 | } 19 | 20 | public static Mock Create() 21 | { 22 | return new Mock(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/ContainerExtentions.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using Autofac; 4 | 5 | using Moq; 6 | 7 | internal static class ContainerExtentions 8 | { 9 | public static void RegisterType(this TestBase testBase) 10 | { 11 | testBase.ContainerBuilder.RegisterType().SingleInstance().PropertiesAutowired(); 12 | } 13 | 14 | public static void RegisterType(this TestBase testBase) 15 | where TImpl : TService 16 | { 17 | testBase.ContainerBuilder.RegisterType().As().SingleInstance().PropertiesAutowired(); 18 | } 19 | 20 | public static void RegisterInstance(this TestBase testBase, TImpl service) 21 | where TService : class 22 | where TImpl : TService 23 | { 24 | testBase.ContainerBuilder.RegisterInstance(service); 25 | } 26 | 27 | public static void RegisterInstance(this TestBase testBase, TImpl service) 28 | where TImpl : class 29 | { 30 | testBase.ContainerBuilder.RegisterInstance(service); 31 | } 32 | 33 | public static void RegisterMock(this TestBase testBase, Mock mock) 34 | where TObject : class 35 | { 36 | testBase.RegisterInstance(mock); 37 | testBase.RegisterInstance(mock.Object); 38 | } 39 | 40 | public static TService Resolve(this TestBase testBase) 41 | { 42 | return testBase.Container.Resolve(); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/RecieveHandlerMock.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | using Moq; 8 | 9 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 10 | using TelegramClient.Core.Sessions; 11 | 12 | internal static class RecieveHandlerMock 13 | { 14 | public static Mock BuildRecieveHandler(this Mock mock, uint[] code) 15 | { 16 | mock 17 | .Setup(recieveHandler => recieveHandler.HandleCodes) 18 | .Returns(code); 19 | 20 | return mock; 21 | } 22 | 23 | public static Mock BuildHandleResponce(this Mock mock, Func callback) 24 | { 25 | mock 26 | .Setup(recieveHandler => recieveHandler.HandleResponce(It.IsAny())) 27 | .Returns(callback); 28 | 29 | return mock; 30 | } 31 | 32 | public static Mock Create() 33 | { 34 | return new Mock(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/SessionMock.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using System; 4 | 5 | using Moq; 6 | 7 | using TelegramClient.Core.MTProto.Crypto; 8 | using TelegramClient.Core.Sessions; 9 | 10 | internal static class SessionMock 11 | { 12 | private static readonly Random Random = new Random(); 13 | 14 | public static Mock BuildGenerateMesId(this Mock mock, Func getNewMessageIdFunc) 15 | { 16 | mock 17 | .Setup(service => service.GenerateMsgId()) 18 | .Returns(getNewMessageIdFunc); 19 | 20 | return mock; 21 | } 22 | 23 | public static Mock BuildGenerateMessageSeqNo(this Mock mock, Func> generateMessageSeqNoFunc) 24 | { 25 | mock 26 | .Setup(service => service.GenerateMsgIdAndSeqNo(It.IsAny())) 27 | .Returns(generateMessageSeqNoFunc); 28 | 29 | return mock; 30 | } 31 | 32 | public static Mock BuildSession(this Mock mock, ulong sessionId, ulong salt, byte[] authKeyData) 33 | { 34 | mock 35 | .Setup(session => session.AuthKey) 36 | .Returns(new AuthKey(authKeyData)); 37 | 38 | mock 39 | .Setup(session => session.Salt) 40 | .Returns(salt); 41 | 42 | mock 43 | .Setup(session => session.Id) 44 | .Returns(sessionId); 45 | 46 | return mock; 47 | } 48 | 49 | public static byte[] GenerateAuthKeyData() 50 | { 51 | var key = new byte[256]; 52 | for (var i = 0; i < 256; i++) 53 | { 54 | key[i] = (byte)Random.Next(255); 55 | } 56 | 57 | return key; 58 | } 59 | 60 | public static Mock Create() 61 | { 62 | return new Mock(); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/SessionStoreMock.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using System; 4 | 5 | using Moq; 6 | 7 | using TelegramClient.Core.Sessions; 8 | 9 | internal static class SessionStoreMock 10 | { 11 | public static Mock BuildLoad(this Mock mock, Func returnsFunc) 12 | { 13 | mock 14 | .Setup(store => store.Load(It.IsAny())) 15 | .Returns(returnsFunc); 16 | 17 | return mock; 18 | } 19 | 20 | public static Mock BuildSave(this Mock mock, Action callbackFunc) 21 | { 22 | mock 23 | .Setup(store => store.Save()) 24 | .Callback(callbackFunc); 25 | 26 | return mock; 27 | } 28 | 29 | public static Mock Create() 30 | { 31 | return new Mock(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/TcpServiceMock.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | 7 | using Moq; 8 | 9 | using TelegramClient.Core.Network.Tcp; 10 | 11 | internal static class TcpServiceMock 12 | { 13 | public static Mock BuildSend(this Mock mock, Action callback = null, Func returnTask = null) 14 | { 15 | if (returnTask == null) 16 | { 17 | returnTask = () => Task.Delay(1); 18 | } 19 | 20 | if (callback == null) 21 | { 22 | callback = bytes => {}; 23 | } 24 | 25 | mock 26 | .Setup(service => service.Send(It.IsAny())) 27 | .Callback(callback) 28 | .Returns(returnTask); 29 | 30 | return mock; 31 | } 32 | 33 | public static Mock BuildReceieve(this Mock mock, int seqNumber, byte[] body) 34 | { 35 | var message = new TcpMessage(seqNumber, body); 36 | var memoryStream = new MemoryStream(message.Encode()); 37 | 38 | mock.BuildReceieve(() => Task.FromResult((Stream)memoryStream)); 39 | 40 | return mock; 41 | } 42 | 43 | public static Mock BuildReceieve(this Mock mock, Func> returns) 44 | { 45 | mock 46 | .Setup(service => service.Receieve()) 47 | .Returns(returns); 48 | 49 | return mock; 50 | } 51 | 52 | public static Mock Create() 53 | { 54 | return new Mock(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/TcpTransportMock.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | using Autofac; 9 | 10 | using Moq; 11 | 12 | using TelegramClient.Core.Network.RecieveHandlers.Interfaces; 13 | using TelegramClient.Core.Network.Tcp; 14 | 15 | internal static class TcpTransportMock 16 | { 17 | public static Mock BuildSend(this Mock mock, Action callback = null) 18 | { 19 | if (callback == null) 20 | { 21 | callback = bytes => { }; 22 | } 23 | 24 | mock 25 | .Setup(service => service.Send(It.IsAny())) 26 | .Callback(callback); 27 | 28 | return mock; 29 | } 30 | 31 | public static void RegisterAdapterForHandler(this TestBase testBase) 32 | { 33 | testBase.ContainerBuilder.RegisterAdapter, Dictionary>(handlers => 34 | { 35 | var handlerMap = new Dictionary(); 36 | foreach (var handler in handlers.ToArray()) 37 | { 38 | foreach (var handleCode in handler.HandleCodes) 39 | { 40 | handlerMap[handleCode] = handler; 41 | } 42 | } 43 | 44 | return handlerMap; 45 | }); 46 | } 47 | 48 | public static Mock BuildReceieve(this Mock mock, Func> returns) 49 | { 50 | mock 51 | .Setup(service => service.Receieve()) 52 | .Returns(returns); 53 | 54 | return mock; 55 | } 56 | 57 | public static Mock BuildReceieve(this Mock mock, out TaskCompletionSource tsc) 58 | { 59 | var source = tsc = new TaskCompletionSource(); 60 | var infiniteWait = new TaskCompletionSource(); 61 | 62 | return mock.BuildReceieve(() => !source.Task.IsCompleted && !source.Task.IsFaulted && !source.Task.IsCanceled ? source.Task : infiniteWait.Task); 63 | } 64 | 65 | public static Mock Create() 66 | { 67 | return new Mock(); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Framework/TestBase.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Framework 2 | { 3 | using Autofac; 4 | 5 | public abstract class TestBase 6 | { 7 | public ContainerBuilder ContainerBuilder { get; } = new ContainerBuilder(); 8 | 9 | private IContainer _container; 10 | public IContainer Container => _container ?? (_container = ContainerBuilder.Build()); 11 | } 12 | } -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Network/MtProtoPlainSenderTests.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Network 2 | { 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | 6 | using Moq; 7 | 8 | using TelegramClient.Core.Network; 9 | using TelegramClient.Core.Network.Tcp; 10 | using TelegramClient.UnitTests.Framework; 11 | 12 | using Xunit; 13 | 14 | public class MtProtoPlainSenderTests: TestBase 15 | { 16 | [Fact] 17 | public void SendAndRecive_SimpleCall_NotThrows() 18 | { 19 | var sendData = new byte[] { 123, 214 }; 20 | const long SendMessageId = 1234; 21 | 22 | var receiveData = new byte[] { 123, 214 }; 23 | const long ReciveMessageId = 12; 24 | const long AuthKeyId = 12123123; 25 | 26 | var mTcpTransport = TcpTransportMock.Create(); 27 | AddSendHandler(mTcpTransport, SendMessageId, sendData); 28 | AddReceiveHandler(mTcpTransport, AuthKeyId, ReciveMessageId, receiveData); 29 | 30 | this.RegisterMock(mTcpTransport); 31 | 32 | var mSession = SessionMock.Create().BuildGenerateMesId(() => SendMessageId); 33 | var mClientSettings = ClientSettingsMock.Create().AttachSession(() => mSession.Object); 34 | this.RegisterMock(mClientSettings); 35 | 36 | 37 | 38 | this.RegisterType(); 39 | 40 | // --- 41 | 42 | var mtProtoPlainSender = this.Resolve(); 43 | var sendTask = mtProtoPlainSender.SendAndReceive(sendData); 44 | 45 | // -- 46 | 47 | Assert.Equal(receiveData, sendTask.Result); 48 | mTcpTransport.Verify(transport => transport.Send(It.IsAny()), Times.Once); 49 | } 50 | 51 | private void AddSendHandler(Mock mock, long messageId, byte[] sendData) 52 | { 53 | mock 54 | .BuildSend( 55 | bytes => 56 | { 57 | using (var memoryStream = new MemoryStream(bytes)) 58 | { 59 | using (var binaryReader = new BinaryReader(memoryStream)) 60 | { 61 | Assert.Equal(0, binaryReader.ReadInt64()); 62 | Assert.Equal(messageId, binaryReader.ReadInt64()); 63 | Assert.Equal(sendData.Length, binaryReader.ReadInt32()); 64 | Assert.Equal(sendData, binaryReader.ReadBytes(sendData.Length)); 65 | } 66 | } 67 | }); 68 | } 69 | 70 | private void AddReceiveHandler(Mock mock, long authKeyId, long messageId, byte[] reciveData) 71 | { 72 | mock 73 | .BuildReceieve(() => 74 | { 75 | using (var memoryStream = new MemoryStream()) 76 | { 77 | using (var binaryWriter = new BinaryWriter(memoryStream)) 78 | { 79 | binaryWriter.Write(authKeyId); 80 | binaryWriter.Write(messageId); 81 | binaryWriter.Write(reciveData.Length); 82 | binaryWriter.Write(reciveData); 83 | 84 | return Task.FromResult(memoryStream.ToArray()); 85 | } 86 | } 87 | }); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Network/MtProtoSendServiceTests.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Network 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Autofac; 7 | 8 | using Moq; 9 | 10 | using OpenTl.Schema; 11 | using OpenTl.Schema.Serialization; 12 | 13 | using TelegramClient.Core.Helpers; 14 | using TelegramClient.Core.MTProto.Crypto; 15 | using TelegramClient.Core.Network; 16 | using TelegramClient.Core.Network.Tcp; 17 | using TelegramClient.Core.Settings; 18 | using TelegramClient.Core.Utils; 19 | using TelegramClient.UnitTests.Framework; 20 | 21 | using Xunit; 22 | 23 | 24 | public class MtProtoSendServiceTests : TestBase 25 | { 26 | 27 | [Fact] 28 | public async Task Send_SimpleCall_NotThrows() 29 | { 30 | const ulong SendMessageId = 1234; 31 | var authKey = SessionMock.GenerateAuthKeyData(); 32 | ulong sessionId = 1231231; 33 | ulong salt = 5432111; 34 | var seqNo = 123; 35 | var mSession = SessionMock.Create() 36 | .BuildGenerateMessageSeqNo(confirm => Tuple.Create(SendMessageId, seqNo)) 37 | .BuildSession(sessionId, salt, authKey); 38 | 39 | var request = new RequestPing(); 40 | 41 | var mTcpTransport = TcpTransportMock.Create(); 42 | AddSendHandler(mTcpTransport, request); 43 | 44 | this.RegisterMock(mTcpTransport); 45 | 46 | var mClientSettings = ClientSettingsMock.Create().AttachSession(() => mSession.Object); 47 | this.RegisterMock(mClientSettings); 48 | 49 | var mConfirmRecieve = ConfirmationRecieveServiceMock.Create() 50 | .BuildWaitForConfirm(messageId => 51 | { 52 | Assert.Equal(SendMessageId, messageId); 53 | return Task.FromResult(true); 54 | }); 55 | this.RegisterMock(mConfirmRecieve); 56 | 57 | this.RegisterType(); 58 | 59 | // --- 60 | 61 | var mtProtoPlainSender = this.Resolve(); 62 | var sendResult = mtProtoPlainSender.Send(request); 63 | 64 | await sendResult.Result.Item1; 65 | // -- 66 | 67 | mTcpTransport.Verify(transport => transport.Send(It.IsAny()), Times.Once); 68 | 69 | mConfirmRecieve.Verify(store => store.WaitForConfirm(It.IsAny()), Times.Once); 70 | } 71 | 72 | private void AddSendHandler(Mock mock, IRequest request) 73 | { 74 | mock 75 | .BuildSend( 76 | bytes => 77 | { 78 | var session = Container.Resolve().Session; 79 | 80 | byte[] plainText = null; 81 | 82 | BinaryHelper.ReadBytes( 83 | bytes, 84 | reader => 85 | { 86 | Assert.Equal(session.AuthKey.Id, reader.ReadUInt64()); 87 | var msgKey = reader.ReadBytes(16); 88 | 89 | var cipherText = reader.ReadBytes(bytes.Length - 8 - 16); 90 | 91 | plainText = AES.DecryptAes(TlHelpers.CalcKey(session.AuthKey.Data, msgKey, true), cipherText); 92 | }); 93 | 94 | Assert.NotNull(plainText); 95 | 96 | BinaryHelper.ReadBytes( 97 | plainText, 98 | reader => 99 | { 100 | Assert.Equal(session.Salt, reader.ReadUInt64()); 101 | Assert.Equal(session.Id, reader.ReadUInt64()); 102 | Assert.Equal(0, reader.ReadInt32()); 103 | 104 | var packetLength = reader.ReadInt32(); 105 | Assert.True(packetLength > 0); 106 | 107 | var requestBytes = Serializer.SerializeObject(request); 108 | Assert.Equal(requestBytes, reader.ReadBytes(packetLength)); 109 | }); 110 | }); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Network/RecieveServiceTests.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Network 2 | { 3 | using System.IO; 4 | using System.Threading; 5 | 6 | using Autofac; 7 | 8 | using Moq; 9 | 10 | using OpenTl.Schema; 11 | using OpenTl.Schema.Serialization; 12 | 13 | using TelegramClient.Core.Helpers; 14 | using TelegramClient.Core.MTProto.Crypto; 15 | using TelegramClient.Core.Network.Recieve; 16 | using TelegramClient.Core.Settings; 17 | using TelegramClient.Core.Utils; 18 | using TelegramClient.UnitTests.Framework; 19 | 20 | using Xunit; 21 | 22 | 23 | public class RecieveServiceTests : TestBase 24 | { 25 | 26 | [Fact] 27 | public void Recieve_SimpleCall_NotThrows() 28 | { 29 | const long RequestMessageId = 1234; 30 | var authKeyData = SessionMock.GenerateAuthKeyData(); 31 | const ulong sessionId = 123456; 32 | const ulong salt = 654321; 33 | uint[] rpcResponceCode = {0xf35c6d01}; 34 | 35 | var sendUser = new TDialog 36 | { 37 | ReadInboxMaxId = 132, 38 | Peer = new TPeerUser { UserId = 123 }, 39 | NotifySettings = new TPeerNotifySettingsEmpty(), 40 | Draft = new TDraftMessageEmpty() 41 | }; 42 | 43 | var mSession = SessionMock.Create().BuildSession(sessionId, salt, authKeyData); 44 | this.RegisterMock(mSession); 45 | 46 | var mClientSettings = ClientSettingsMock.Create().AttachSession(() => mSession.Object); 47 | this.RegisterMock(mClientSettings); 48 | 49 | var mTcpTransport = TcpTransportMock.Create().BuildReceieve(out var tsc); 50 | this.RegisterMock(mTcpTransport); 51 | 52 | var mConfrimSendService = ConfirmationSendServiceMock.Create().BuildAddForSend(messageId => Assert.Equal(RequestMessageId, messageId)); 53 | this.RegisterMock(mConfrimSendService); 54 | 55 | var mRecieveHandler = RecieveHandlerMock.Create().BuildRecieveHandler(rpcResponceCode).BuildHandleResponce( 56 | (code, reader) => 57 | { 58 | Assert.Equal(RequestMessageId, reader.ReadInt64()); 59 | return null; 60 | }); 61 | this.RegisterMock(mRecieveHandler); 62 | this.RegisterAdapterForHandler(); 63 | 64 | this.RegisterType(); 65 | 66 | // --- 67 | var rpcResult = new TRpcResult{ReqMsgId = RequestMessageId, Result = sendUser}; 68 | var recieveData = EncodePacket(Serializer.SerializeObject(rpcResult), RequestMessageId); 69 | 70 | var mtProtoPlainSender = this.Resolve(); 71 | mtProtoPlainSender.StartReceiving(); 72 | Thread.Sleep(500); 73 | 74 | tsc.SetResult(recieveData); 75 | 76 | Thread.Sleep(500); 77 | 78 | // -- 79 | mRecieveHandler.Verify(recieveService => recieveService.HandleResponce(It.IsAny()), Times.Once); 80 | mConfrimSendService.Verify(recieveService => recieveService.AddForSend(It.IsAny()), Times.Once); 81 | } 82 | 83 | private byte[] EncodePacket(byte[] packet, ulong messageId) 84 | { 85 | var session = Container.Resolve().Session; 86 | var plainTextStream = BinaryHelper.WriteBytes( 87 | 8 + 8 + 8 + 4 + 4 + packet.Length, 88 | writer => 89 | { 90 | writer.Write(session.Salt); 91 | writer.Write(session.Id); 92 | writer.Write(messageId); 93 | writer.Write(0); 94 | writer.Write(packet.Length); 95 | writer.Write(packet); 96 | }); 97 | 98 | var plainText = plainTextStream.GetBytesWithBuffer(); 99 | plainTextStream.Dispose(); 100 | 101 | var msgKey = TlHelpers.CalcMsgKey(plainText); 102 | var ciphertext = AES.EncryptAes(TlHelpers.CalcKey(session.AuthKey.Data, msgKey, false), plainText); 103 | 104 | var chipedTextStream = BinaryHelper.WriteBytes( 105 | 8 + 16 + ciphertext.Length, 106 | writer => 107 | { 108 | writer.Write(session.AuthKey.Id); 109 | writer.Write(msgKey); 110 | writer.Write(ciphertext); 111 | }); 112 | var result = chipedTextStream.ToArray(); 113 | chipedTextStream.Dispose(); 114 | 115 | return result; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/Network/TcpTransportTests.cs: -------------------------------------------------------------------------------- 1 | namespace TelegramClient.UnitTests.Network 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | using Moq; 8 | 9 | using TelegramClient.Core.Network; 10 | using TelegramClient.Core.Network.Tcp; 11 | using TelegramClient.UnitTests.Framework; 12 | 13 | using Xunit; 14 | 15 | public class TcpTransportTests : TestBase 16 | { 17 | [Fact] 18 | public void Send_TcpServiceSendCalled_NotThrows() 19 | { 20 | var task = Task.Delay(1); 21 | const ulong SendMessageId = 1234; 22 | 23 | var mTcpService = TcpServiceMock.Create().BuildSend(returnTask: () => task); 24 | this.RegisterMock(mTcpService); 25 | 26 | var seqNo = 1; 27 | 28 | var mSession = SessionMock.Create() 29 | .BuildGenerateMessageSeqNo(confirm => Tuple.Create(SendMessageId, seqNo)); 30 | var mClientSettings = ClientSettingsMock.Create().AttachSession(() => mSession.Object); 31 | this.RegisterMock(mClientSettings); 32 | 33 | this.RegisterType(); 34 | 35 | // --- 36 | 37 | var transport = this.Resolve(); 38 | transport.Send(new byte[1]); 39 | Thread.Sleep(1000); 40 | // -- 41 | 42 | mTcpService.Verify(service => service.Send(It.IsAny()), Times.Once); 43 | } 44 | 45 | [Fact] 46 | public void Send_ValidTcpMessage_NotThrows() 47 | { 48 | var data = new byte[] { 124, 123 }; 49 | 50 | var mTcpService = TcpServiceMock.Create().BuildSend( 51 | bytes => 52 | { 53 | var message = TcpMessage.Decode(bytes); 54 | Assert.Equal(0, message.SequneceNumber); 55 | Assert.Equal(data, message.Body); 56 | 57 | }); 58 | 59 | this.RegisterMock(mTcpService); 60 | 61 | this.RegisterType(); 62 | 63 | // --- 64 | 65 | var transport = this.Resolve(); 66 | transport.Send(data); 67 | } 68 | 69 | [Fact] 70 | public void Receieve_TcpServiceReceieveCalled_NotThrows() 71 | { 72 | var data = new byte[] { 123, 123 }; 73 | 74 | var mTcpService = TcpServiceMock.Create().BuildReceieve(1, data); 75 | this.RegisterMock(mTcpService); 76 | 77 | this.RegisterType(); 78 | 79 | // --- 80 | 81 | var transport = this.Resolve(); 82 | var sendTask = transport.Receieve(); 83 | 84 | // -- 85 | mTcpService.Verify(service => service.Receieve(), Times.Once); 86 | Assert.Equal(data, sendTask.Result); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/TelegramClient.UnitTests/TelegramClient.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net46;netcoreapp1.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15.0.0 14 | 15 | 16 | 17 | 2.2.0 18 | 19 | 20 | 2.2.0 21 | 22 | 23 | --------------------------------------------------------------------------------