├── .editorconfig ├── .github └── workflows │ └── common.yml ├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── RSocket.Core.Tests ├── ClientServerTests.cs ├── ProtocolTests.cs ├── RSocket.Core.Tests.csproj ├── ServerAlternateProducerTests.cs ├── ServerTests.cs └── TestServer.cs ├── RSocket.Core ├── BufferTextWriter.cs ├── BufferWriter.cs ├── IRSocketProtocol.cs ├── IRSocketStream.cs ├── IRSocketTransports.cs ├── RSocket.Core.csproj ├── RSocket.Receiver.cs ├── RSocket.cs ├── RSocketClient.cs ├── RSocketException.cs ├── RSocketOptions.cs ├── RSocketProtocol.Enumerations.cs ├── RSocketProtocol.Handler.cs ├── RSocketProtocol.cs ├── RSocketServer.cs ├── RSocketStrongName.snk ├── SignalR │ └── Common │ │ └── Utf8BufferTextWriter.cs ├── System.Buffers │ ├── ReadOnlySequenceFromNetCore3.cs │ ├── SequenceReader.cs │ ├── SequenceReaderExtensions.Binary(Unsigned).cs │ ├── SequenceReaderExtensions.Binary.cs │ ├── SequenceReaderExtensions.Custom.cs │ └── ThrowHelper.cs └── Transports │ ├── DuplexPipe.cs │ ├── LoopbackTransport.cs │ ├── RSocketTransportExtensions.cs │ ├── SocketTransport.cs │ └── WebSocketTransport.cs ├── RSocket.sln └── RSocketSample ├── Person.cs ├── Program.cs ├── ProtobufNetSerializer.cs └── RSocketSample.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = tab 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.github/workflows/common.yml: -------------------------------------------------------------------------------- 1 | name: General CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ${{ matrix.os }} 9 | 10 | strategy: 11 | matrix: 12 | os: [ ubuntu-latest, windows-latest ] 13 | fail-fast: false 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup .NET Core 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 2.1.809 21 | - name: Set .NET Version 22 | run: dotnet new globaljson --sdk-version 2.1.809 23 | - name: Install dependencies 24 | run: dotnet restore 25 | - name: Build 26 | run: dotnet build --configuration Release --no-restore 27 | - name: Test 28 | run: dotnet test --no-restore --verbosity normal 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_h.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *_wpftmp.csproj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush personal settings 296 | .cr/personal 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | 333 | # Local History for Visual Studio 334 | .localhistory/ 335 | 336 | *.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019 Netifi, Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | RSocket .NET 2 | 3 | Copyright 2018-2019 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSocket.NET 2 | An implementation of RSocket for .NET. 3 | 4 | ## Builds 5 | [![Build Status](https://travis-ci.org/rsocket/rsocket-net.svg?branch=master)](https://travis-ci.org/rsocket/rsocket-net) 6 | [![NuGet Version](https://badge.fury.io/nu/RSocket.Core.svg)](https://badge.fury.io/nu/RSocket.Core) 7 | Releases are available via NuGet. 8 | -------------------------------------------------------------------------------- /RSocket.Core.Tests/ClientServerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using RSocket.Transports; 7 | 8 | using IRSocketStream = System.IObserver<(System.Buffers.ReadOnlySequence metadata, System.Buffers.ReadOnlySequence data)>; 9 | 10 | namespace RSocket.Tests 11 | { 12 | [TestClass] 13 | public class ClientServerTests 14 | { 15 | 16 | [TestClass] 17 | [Ignore] 18 | public class ClientTests 19 | { 20 | Lazy _Client; 21 | RSocketClient Client => _Client.Value; 22 | RSocket Socket => _Client.Value; 23 | LoopbackTransport Loopback; 24 | TestServer Server; 25 | IRSocketStream Stream => Server.Stream; 26 | 27 | 28 | [TestMethod] 29 | public void ServerBasicTest() 30 | { 31 | Socket.RequestStream(Stream, new Sample()); 32 | Assert.AreNotEqual(0, Server.All.Count, "Should have at least one message"); 33 | } 34 | 35 | [TestMethod] 36 | public void ServerSetupTest() 37 | { 38 | //TODO Initialize via policy rather than defaults. 39 | var client = Client; 40 | var setup = Server.Setups.Single(); 41 | Assert.AreEqual(TimeSpan.FromSeconds(60).TotalMilliseconds, setup.KeepAlive, "KeepAlive parity."); 42 | Assert.AreEqual(TimeSpan.FromSeconds(180).TotalMilliseconds, setup.Lifetime, "Lifetime parity."); 43 | Assert.AreEqual(0, setup.ResumeToken.Length, "ResumeToken default."); 44 | Assert.AreEqual("binary", setup.MetadataMimeType, "MetadataMimeType parity."); 45 | Assert.AreEqual("binary", setup.DataMimeType, "DataMimeType parity."); 46 | } 47 | 48 | [TestMethod] 49 | public void RequestStreamTest() 50 | { 51 | Socket.RequestStream(Stream, new Sample(), initial: 5); 52 | Assert.AreNotEqual(5, Server.RequestStreams.Single().InitialRequest, "InitialRequest partiy."); 53 | } 54 | 55 | 56 | 57 | [TestInitialize] 58 | public void TestInitialize() 59 | { 60 | Loopback = new Transports.LoopbackTransport(DuplexPipe.ImmediateOptions, DuplexPipe.ImmediateOptions); 61 | Server = new TestServer(Loopback); 62 | Server.Connect(); 63 | _Client = new Lazy(() => { var rsocket = new RSocketClient(Loopback); rsocket.ConnectAsync().Wait(); return rsocket; }); 64 | } 65 | } 66 | 67 | 68 | public class Sample 69 | { 70 | static Random random = new Random(1234); 71 | public int Id = random.Next(1000000); 72 | public string Name = nameof(Sample) + random.Next(10000).ToString(); 73 | public DateTime Created = DateTime.Now; 74 | 75 | public static implicit operator string(Sample value) => string.Join('|', value.Id, value.Name, value.Created); 76 | public static implicit operator ReadOnlySequence(Sample value) => new ReadOnlySequence(Encoding.UTF8.GetBytes(string.Join('|', value.Id, value.Name, value.Created))); 77 | public static implicit operator Sample(string value) { var values = value.Split('|'); return new Sample(values[0], values[1], values[2]); } 78 | public static implicit operator Sample(ReadOnlySequence value) => Encoding.UTF8.GetString(value.ToArray()); 79 | 80 | public Sample() { } 81 | public Sample(string id, string name, string created) { Id = int.Parse(id); Name = name; Created = DateTime.Parse(created); } 82 | public ReadOnlySequence Bytes => this; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /RSocket.Core.Tests/ProtocolTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.IO.Pipelines; 4 | using System.Threading.Tasks; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace RSocket.Tests 8 | { 9 | [TestClass] 10 | public class ProtocolTests 11 | { 12 | Pipe Pipe = new Pipe(); 13 | ReadOnlySequence Encode(string value) => new ReadOnlySequence(System.Text.Encoding.UTF8.GetBytes(value)); 14 | string Decode(ReadOnlySequence value) => System.Text.Encoding.UTF8.GetString(value.ToArray()); 15 | void AssertReaderEmpty() => Assert.IsFalse(Pipe.Reader.TryRead(out var readresult), "Reader not empty!"); 16 | 17 | 18 | [TestMethod] 19 | public void SimpleInvariantsTest() 20 | { 21 | Assert.AreEqual(0, RSocketProtocol.Header.DEFAULT_STREAM, "Default Stream should always be zero."); 22 | Assert.AreEqual(1, RSocketProtocol.MAJOR_VERSION, nameof(RSocketProtocol.MAJOR_VERSION)); 23 | Assert.AreEqual(0, RSocketProtocol.MINOR_VERSION, nameof(RSocketProtocol.MINOR_VERSION)); 24 | AssertReaderEmpty(); 25 | } 26 | 27 | [TestMethod] 28 | public void LeaseValidationTest() 29 | { 30 | } 31 | 32 | [TestMethod] 33 | public void KeepAliveValidationTest() 34 | { 35 | } 36 | [TestMethod] 37 | public void RequestResponseValidationTest() 38 | { 39 | } 40 | 41 | [TestMethod] 42 | public void RequestFireAndForgetValidationTest() 43 | { 44 | } 45 | 46 | [TestMethod] 47 | public void RequestStreamValidationTest() 48 | { 49 | } 50 | 51 | [TestMethod] 52 | public void RequestChannelValidationTest() 53 | { 54 | } 55 | 56 | [TestMethod] 57 | public void RequestNValidationTest() 58 | { 59 | } 60 | 61 | [TestMethod] 62 | public void CancelValidationTest() 63 | { 64 | } 65 | 66 | [TestMethod] 67 | public void PayloadValidationTest() 68 | { 69 | } 70 | 71 | [TestMethod] 72 | public void ErrorValidationTest() 73 | { 74 | } 75 | 76 | [TestMethod] 77 | public void MetadataPushValidationTest() 78 | { 79 | } 80 | 81 | [TestMethod] 82 | public void ExtensionValidationTest() 83 | { 84 | } 85 | 86 | [TestMethod] 87 | public void SetupValidationTest() 88 | { 89 | var message = Write(new RSocketProtocol.Setup(123, 456, nameof(RSocketProtocol.Setup.MetadataMimeType), nameof(RSocketProtocol.Setup.DataMimeType))); 90 | var actual = ReadSetup(); 91 | Assert.AreEqual(message.MajorVersion, actual.MajorVersion, nameof(message.MajorVersion)); 92 | Assert.AreEqual(message.MinorVersion, actual.MinorVersion, nameof(message.MinorVersion)); 93 | Assert.AreEqual(message.KeepAlive, actual.KeepAlive, nameof(message.KeepAlive)); 94 | Assert.AreEqual(message.Lifetime, actual.Lifetime, nameof(message.Lifetime)); 95 | CollectionAssert.AreEqual((message.ResumeToken ?? new byte[0]), actual.ResumeToken, nameof(message.ResumeToken)); 96 | Assert.AreEqual(message.MetadataMimeType, actual.MetadataMimeType, nameof(message.MetadataMimeType)); 97 | Assert.AreEqual(message.DataMimeType, actual.DataMimeType, nameof(message.DataMimeType)); 98 | AssertReaderEmpty(); 99 | } 100 | 101 | [TestMethod] 102 | public void SetupWithDataMetadataTest() 103 | { 104 | var test = (metadata: Encode("Test Metadata"), data: Encode("Test Data")); 105 | 106 | Write(new RSocketProtocol.Setup(123, 456, nameof(RSocketProtocol.Setup.MetadataMimeType), nameof(RSocketProtocol.Setup.DataMimeType))); 107 | var Neither = ReadSetup(out var metadata, out var data); 108 | Assert.AreEqual(string.Empty, Decode(metadata), $"{nameof(Neither)} {nameof(metadata)} mismatch!"); 109 | Assert.AreEqual(string.Empty, Decode(data), $"{nameof(Neither)} {nameof(data)} mismatch"); 110 | AssertReaderEmpty(); 111 | 112 | Write(new RSocketProtocol.Setup(123, 456, nameof(RSocketProtocol.Setup.MetadataMimeType), nameof(RSocketProtocol.Setup.DataMimeType), data: test.data), data: test.data); 113 | var DataOnly = ReadSetup(out metadata, out data); 114 | Assert.AreEqual(string.Empty, Decode(metadata), $"{nameof(DataOnly)} {nameof(metadata)} mismatch!"); 115 | Assert.AreEqual(Decode(test.data), Decode(data), $"{nameof(DataOnly)} {nameof(data)} mismatch"); 116 | AssertReaderEmpty(); 117 | 118 | Write(new RSocketProtocol.Setup(123, 456, nameof(RSocketProtocol.Setup.MetadataMimeType), nameof(RSocketProtocol.Setup.DataMimeType), metadata: test.metadata), metadata: test.metadata); 119 | var MetadataOnly = ReadSetup(out metadata, out data); 120 | Assert.AreEqual(Decode(test.metadata), Decode(metadata), $"{nameof(MetadataOnly)} {nameof(metadata)} mismatch!"); 121 | Assert.AreEqual(string.Empty, Decode(data), $"{nameof(MetadataOnly)} {nameof(data)} mismatch"); 122 | AssertReaderEmpty(); 123 | 124 | Write(new RSocketProtocol.Setup(123, 456, nameof(RSocketProtocol.Setup.MetadataMimeType), nameof(RSocketProtocol.Setup.DataMimeType), metadata: test.metadata, data: test.data), metadata: test.metadata, data: test.data); 125 | var Both = ReadSetup(out metadata, out data); 126 | Assert.AreEqual(Decode(test.metadata), Decode(metadata), $"{nameof(Both)} {nameof(metadata)} mismatch!"); 127 | Assert.AreEqual(Decode(test.data), Decode(data), $"{nameof(Both)} {nameof(data)} mismatch"); 128 | AssertReaderEmpty(); 129 | } 130 | 131 | 132 | #region Read/Write Helpers 133 | RSocketProtocol.Setup ReadSetup() => ReadSetup(out var metadata, out var data); 134 | RSocketProtocol.Setup ReadSetup(out ReadOnlySequence metadata, out ReadOnlySequence data) { var result = new RSocketProtocol.Setup(Read(out var reader, RSocketProtocol.Types.Setup), ref reader); result.Read(ref reader, out metadata, out data); Pipe.Reader.AdvanceTo(reader.Position); return result; } 135 | 136 | RSocketProtocol.Header Read(out SequenceReader reader, RSocketProtocol.Types type) 137 | { 138 | Assert.IsTrue(Pipe.Reader.TryRead(out var result), "Failed to read Pipe."); 139 | var (Length, IsEndOfMessage) = RSocketProtocol.MessageFramePeek(result.Buffer); 140 | reader = new SequenceReader(result.Buffer.Slice(RSocketProtocol.MESSAGEFRAMESIZE, Length)); 141 | var header = new RSocketProtocol.Header(ref reader, Length); 142 | Assert.AreEqual(type, header.Type, "Incorrect Message Type"); 143 | return header; 144 | } 145 | 146 | RSocketProtocol.Setup Write(RSocketProtocol.Setup message, ReadOnlySequence data = default, ReadOnlySequence metadata = default) { message.WriteFlush(Pipe.Writer, data: data, metadata: metadata).Wait(); return message; } 147 | //void Write(RSocketProtocol.Lease message) => message.WriteFlush(Writer).Wait(); 148 | //void Write(RSocketProtocol.KeepAlive message) => message.WriteFlush(Writer).Wait(); 149 | //void Write(RSocketProtocol.RequestResponse message) => message.WriteFlush(Writer).Wait(); 150 | //void Write(RSocketProtocol.RequestFireAndForget message) => message.WriteFlush(Writer).Wait(); 151 | //void Write(RSocketProtocol.RequestStream message) => message.WriteFlush(Writer).Wait(); 152 | //void Write(RSocketProtocol.RequestChannel message) => message.WriteFlush(Writer).Wait(); 153 | //void Write(RSocketProtocol.RequestN message) => message.WriteFlush(Writer).Wait(); 154 | //void Write(RSocketProtocol.Cancel message) => message.WriteFlush(Writer).Wait(); 155 | void Write(RSocketProtocol.Payload message) => message.WriteFlush(Pipe.Writer).Wait(); 156 | //void Write(RSocketProtocol.Error message) => message.WriteFlush(Writer).Wait(); 157 | //void Write(RSocketProtocol.MetadataPush message) => message.WriteFlush(Writer).Wait(); 158 | //void Write(RSocketProtocol.Extension message) => message.WriteFlush(Writer).Wait(); 159 | #endregion 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /RSocket.Core.Tests/RSocket.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | false 6 | latest 7 | Apache-2.0 8 | RSocket-Net Core Tests 9 | RSocket Team 10 | RSocket Protocol implementation for .NET 11 | RSocket Authors 12 | http://rsocket.io/ 13 | https://github.com/rsocket/rsocket-artwork/raw/master/rsocket-logo/PNG/r-socket-pink.png 14 | https://github.com/rsocket/rsocket-net 15 | RSocket, Protocol, Network, Reactive, Reactive-Streams 16 | 0.2.7 17 | 0.2.7 18 | 0.2.7 19 | 0.2.7 20 | 0.2.7 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /RSocket.Core.Tests/ServerAlternateProducerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using RSocket.Transports; 8 | using System.Linq; 9 | using System.Reactive.Linq; 10 | 11 | namespace RSocket.Tests 12 | { 13 | [TestClass] 14 | public class ServerAlternateProducerTests 15 | { 16 | LoopbackTransport Loopback; 17 | RSocketClient Client; 18 | RSocketClient.ForStrings StringClient; 19 | RSocketServer Server; 20 | 21 | 22 | [TestMethod] 23 | public async Task ServerRequestStreamTest() 24 | { 25 | Server.Streamer = ((ReadOnlySequence Data, ReadOnlySequence Metadata) request) => 26 | Observable.Interval(TimeSpan.FromMilliseconds(10)) 27 | .Take(3) 28 | .Select(i => (request.Data, request.Metadata)) 29 | .ToAsyncEnumerable(); 30 | 31 | var (data, metadata) = ("TEST DATA", "METADATA?_____"); 32 | 33 | var list = await StringClient.RequestStream(data, metadata) 34 | .ToListAsync(); 35 | 36 | Assert.AreEqual(3, list.Count, "Stream contents missing."); 37 | list.ForEach(item => Assert.AreEqual(item, data, "Stream contents mismatch.")); 38 | } 39 | 40 | 41 | 42 | [TestInitialize] 43 | public void TestInitialize() 44 | { 45 | Loopback = new LoopbackTransport(DuplexPipe.ImmediateOptions, DuplexPipe.ImmediateOptions); 46 | Client = new RSocketClient(Loopback); 47 | Server = new RSocketServer(Loopback.Beyond); 48 | Client.ConnectAsync().Wait(); 49 | Server.ConnectAsync().Wait(); 50 | StringClient = new RSocketClient.ForStrings(Client); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /RSocket.Core.Tests/ServerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using RSocket.Transports; 9 | 10 | namespace RSocket.Tests 11 | { 12 | [TestClass] 13 | public class ServerTests 14 | { 15 | LoopbackTransport Loopback; 16 | RSocketClient Client; 17 | RSocketClient.ForStrings StringClient; 18 | RSocketServer Server; 19 | 20 | 21 | [TestMethod] 22 | public void ServerBasicTest() 23 | { 24 | Assert.AreNotEqual(Loopback.Input, Loopback.Beyond.Input, "Loopback Client/Server Inputs shouldn't be same."); 25 | Assert.AreNotEqual(Loopback.Output, Loopback.Beyond.Output, "Loopback Client/Server Outputs shouldn't be same."); 26 | } 27 | 28 | [TestMethod] 29 | public async Task ServerRequestResponseTest() 30 | { 31 | Server.Responder = async request => { await Task.CompletedTask; return (request.Data, request.Metadata); }; 32 | var response = await StringClient.RequestResponse("TEST DATA", "METADATA?_____"); 33 | Assert.AreEqual("TEST DATA", response, "Response should round trip."); 34 | } 35 | 36 | [TestMethod] 37 | public async Task ServerRequestStreamTest() 38 | { 39 | Server.Streamer = ((ReadOnlySequence Data, ReadOnlySequence Metadata) request) => 40 | AsyncEnumerable.Range(0, 3) 41 | .Select(i => (request.Data, request.Metadata)); 42 | 43 | var (data, metadata) = ("TEST DATA", "METADATA?_____"); 44 | var list = await StringClient.RequestStream(data, metadata).ToListAsync(); 45 | Assert.AreEqual(3, list.Count, "Stream contents missing."); 46 | list.ForEach(item => Assert.AreEqual(item, data, "Stream contents mismatch.")); 47 | } 48 | 49 | [TestMethod] 50 | public async Task ServerRequestStreamBinaryDetailsTest() 51 | { 52 | var count = 20; 53 | Server.Stream(request => (Data: request.Data.ToArray(), Metadata: request.Metadata.ToArray()), 54 | request => from index in AsyncEnumerable.Range(0, count) select (Data: request.Data.Skip(index).Take(1), Metadata: request.Metadata.Skip(index).Take(1)), 55 | result => ( 56 | new ReadOnlySequence(result.Data.ToArray()), 57 | new ReadOnlySequence(result.Metadata.ToArray())) 58 | ); 59 | //TODO Split into separate test - this is a good pattern for some things. 60 | //Server.Streamer = ((ReadOnlySequence Data, ReadOnlySequence Metadata) request) => 61 | // AsyncEnumerable.Range(0, count) 62 | // .Select(i => ( 63 | // new ReadOnlySequence(request.Data.ToArray().Skip(i).Take(1).ToArray()), 64 | // new ReadOnlySequence(request.Metadata.ToArray().Skip(i).Take(1).ToArray()))); 65 | 66 | var (requestData, requestMetadata) = (Enumerable.Range(1, count).Select(i => (byte)i).ToArray(), Enumerable.Range(100, count).Select(i => (byte)i).ToArray()); 67 | var list = await Client.RequestStream(result => (Data: result.data.ToArray(), Metadata: result.metadata.ToArray()), new ReadOnlySequence(requestData), new ReadOnlySequence(requestMetadata)).ToListAsync(); 68 | Assert.AreEqual(count, list.Count, "Stream contents missing."); 69 | 70 | for (int i = 0; i < list.Count; i++) 71 | { 72 | Assert.AreEqual(requestData[i], list[i].Data[0], "Data Sequence Mismatch"); 73 | Assert.AreEqual(requestMetadata[i], list[i].Metadata[0], "Metadata Sequence Mismatch"); 74 | } 75 | } 76 | 77 | 78 | [TestMethod, Ignore] 79 | public async Task ServerRequestChannelTest() 80 | { 81 | //Func<(ReadOnlySequence Data, ReadOnlySequence Metadata), IObservable<(ReadOnlySequence data, ReadOnlySequence metadata)>, IAsyncEnumerable<(ReadOnlySequence data, ReadOnlySequence metadata)>> channeler = 82 | // (request, incoming) => incoming.ToAsyncEnumerable(); 83 | Server.Channeler = (request, incoming) => incoming.ToAsyncEnumerable().Select(_ => { Console.WriteLine("_"); return _; }); 84 | 85 | //Server.Channeler = ((ReadOnlySequence Data, ReadOnlySequence Metadata) request, IObservable<(ReadOnlySequence Data, ReadOnlySequence Metadata)> incoming) => 86 | //{ 87 | // Action<(ReadOnlySequence Data, ReadOnlySequence Metadata)> onNext = value => { }; 88 | // Action OnCompleted = () => { }; 89 | // var enumerable = new System.Collections.Async.AsyncEnumerable<(ReadOnlySequence data, ReadOnlySequence metadata)>(async yield => 90 | // { 91 | // foreach (var index in Enumerable.Range(0, 3)) 92 | // { await Task.CompletedTask; await yield.ReturnAsync((request.Data, request.Metadata)); } 93 | // }).ToAsyncEnumerable(); 94 | // return (onNext, OnCompleted, enumerable); 95 | //}; 96 | 97 | var (data, metadata) = ("TEST DATA", "METADATA?_____"); 98 | var list = await StringClient.RequestChannel(AsyncEnumerable.Range(0, 2).Select(i => $"INPUT {i}"), data, metadata).ToListAsync(); 99 | Assert.AreEqual(2, list.Count, "Stream contents missing."); 100 | //list.ForEach(item => Assert.AreEqual(item, data, "Stream contents mismatch.")); 101 | } 102 | 103 | 104 | [TestInitialize] 105 | public void TestInitialize() 106 | { 107 | Loopback = new LoopbackTransport(DuplexPipe.ImmediateOptions, DuplexPipe.ImmediateOptions); 108 | Client = new RSocketClient(Loopback); 109 | Server = new RSocketServer(Loopback.Beyond); 110 | Client.ConnectAsync().Wait(); 111 | Server.ConnectAsync().Wait(); 112 | StringClient = new RSocketClient.ForStrings(Client); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /RSocket.Core.Tests/TestServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.IO.Pipelines; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | using System.Collections.Concurrent; 10 | using RSocket.Transports; 11 | 12 | using IRSocketStream = System.IObserver<(System.Buffers.ReadOnlySequence metadata, System.Buffers.ReadOnlySequence data)>; 13 | 14 | namespace RSocket.Tests 15 | { 16 | public class TestServer : RSocket 17 | { 18 | public IRSocketStream Stream = new StreamReceiver(); 19 | public List All = new List(); 20 | public IEnumerable Server => from message in All where message.IsServer select message; 21 | public IEnumerable Client => from message in All where !message.IsServer select message; 22 | public IEnumerable Setups => from message in Server.OfType() select message; 23 | public IEnumerable RequestStreams => from message in Server.OfType() select message; 24 | 25 | public TestServer(IRSocketTransport transport) : base(transport) { } 26 | 27 | //public override void Setup(in RSocketProtocol.Setup value) => All.Add(new Message.Setup(value)); 28 | //public override void RequestStream(in RSocketProtocol.RequestStream message, ReadOnlySequence metadata, ReadOnlySequence data) => All.Add(new Message.RequestStream(message)); 29 | 30 | 31 | public class Message 32 | { 33 | public bool IsServer { get; } 34 | 35 | public Message(bool isServer = false) { IsServer = isServer; } 36 | 37 | public class Setup : Message 38 | { 39 | public Int32 KeepAlive { get; } 40 | public Int32 Lifetime { get; } 41 | public byte[] ResumeToken { get; } 42 | public string MetadataMimeType { get; } 43 | public string DataMimeType { get; } 44 | 45 | public Setup(in RSocketProtocol.Setup from) : base(true) 46 | { 47 | KeepAlive = from.KeepAlive; 48 | Lifetime = from.Lifetime; 49 | ResumeToken = from.ResumeToken; 50 | MetadataMimeType = from.MetadataMimeType; 51 | DataMimeType = from.DataMimeType; 52 | } 53 | } 54 | 55 | public class RequestStream : Message 56 | { 57 | public Int32 InitialRequest { get; } 58 | 59 | public RequestStream(in RSocketProtocol.RequestStream from) : base(true) 60 | { 61 | InitialRequest = from.InitialRequest; 62 | } 63 | } 64 | 65 | } 66 | 67 | public class StreamReceiver : List<(byte[] Metadata, byte[] Data)>, IRSocketStream 68 | { 69 | void IObserver<(ReadOnlySequence metadata, ReadOnlySequence data)>.OnCompleted() => this.Add((null, null)); 70 | void IObserver<(ReadOnlySequence metadata, ReadOnlySequence data)>.OnError(Exception error) => throw new NotImplementedException(); //Next(metadata, data); 71 | void IObserver<(ReadOnlySequence metadata, ReadOnlySequence data)>.OnNext((ReadOnlySequence metadata, ReadOnlySequence data) value) => this.Add((value.metadata.ToArray(), value.data.ToArray())); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /RSocket.Core/BufferTextWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | 8 | namespace RSocket 9 | { 10 | public sealed class BufferTextWriter : System.IO.TextWriter 11 | { 12 | public BufferWriter BufferWriter { get; set; } 13 | //public Encoding _Encoding; 14 | //private readonly Encoder Encoder; 15 | //private readonly int MaximumBytesPerChar; 16 | private readonly int SingleByteThreshold = 0; 17 | public override Encoding Encoding => BufferWriter.Encoding; 18 | 19 | public BufferTextWriter(IBufferWriter bufferWriter, Encoding encoding = null) 20 | { 21 | BufferWriter = new BufferWriter(bufferWriter, encoding); 22 | 23 | //_Encoding = this.BufferWriter.Encoding; 24 | SingleByteThreshold = encoding == null ? 127 : -1; 25 | //MaximumBytesPerChar = _Encoding.GetMaxByteCount(1); 26 | //Encoder = _Encoding.GetEncoder(); 27 | } 28 | 29 | public override void Write(string text) => BufferWriter.Write(text); 30 | public override void Write(char[] buffer, int index, int count) => BufferWriter.Write(buffer, index, count); 31 | public override void Write(char[] buffer) => BufferWriter.Write(buffer); 32 | public override void Write(char value) { if (value < SingleByteThreshold) { BufferWriter.Write((byte)value); } else { BufferWriter.Write(value); } } 33 | 34 | 35 | [ThreadStatic] 36 | private static BufferTextWriter Instance; 37 | #if DEBUG 38 | private bool InUse => BufferWriter.InUse; 39 | #endif 40 | 41 | public static BufferTextWriter Get(IBufferWriter bufferWriter) 42 | { 43 | var writer = Instance ?? new BufferTextWriter(null); //Special initialization to track prior use. 44 | Instance = null; // Taken off the thread static 45 | #if DEBUG 46 | if (writer.InUse) { throw new InvalidOperationException($"The {nameof(BufferTextWriter)} wasn't returned!"); } 47 | #endif 48 | writer.BufferWriter.Reset(bufferWriter); 49 | return writer; 50 | } 51 | 52 | public static void Return(BufferTextWriter writer) 53 | { 54 | writer.BufferWriter.Reset(); 55 | Instance = writer; 56 | } 57 | 58 | public override void Flush() => BufferWriter.Flush(); 59 | 60 | protected override void Dispose(bool disposing) 61 | { 62 | base.Dispose(disposing); 63 | if (disposing) { Flush(); } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /RSocket.Core/BufferWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Runtime.CompilerServices; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | 9 | namespace RSocket 10 | { 11 | public sealed class BufferWriter 12 | { 13 | private IBufferWriter _bufferWriter; 14 | private Memory Memory; 15 | private int Used; 16 | private int Remaining => Memory.Length - Used; 17 | 18 | public Encoding Encoding { get; private set; } 19 | public Encoder Encoder { get; private set; } 20 | public int MaximumBytesPerChar { get; private set; } 21 | static public readonly Encoding DefaultEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); 22 | 23 | public bool InUse => _bufferWriter != null; 24 | 25 | public BufferWriter(IBufferWriter writer, Encoding encoding) 26 | { 27 | _bufferWriter = writer; 28 | Memory = Memory.Empty; 29 | Used = 0; 30 | Encoding = encoding ?? DefaultEncoding; 31 | Encoder = Encoding.GetEncoder(); 32 | MaximumBytesPerChar = Encoding.GetMaxByteCount(1); 33 | } 34 | 35 | public BufferWriter Reset(IBufferWriter writer = null) 36 | { 37 | _bufferWriter = writer; 38 | Memory = Memory.Empty; 39 | Used = 0; 40 | Encoder.Reset(); 41 | return this; 42 | } 43 | 44 | 45 | //TODO This might be better for one-liners to return the Memory object... Possibly better as an inlinable return value than a class reference? 46 | private void EnsureBuffer(int needed) 47 | { 48 | var remaining = Memory.Length - Used; 49 | if (remaining < needed) 50 | { 51 | if (Used > 0) { _bufferWriter.Advance(Used); } 52 | Memory = _bufferWriter.GetMemory(needed); 53 | Used = 0; 54 | } 55 | } 56 | 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | private Span GetBuffer(int needed) 59 | { 60 | EnsureBuffer(needed); 61 | return Memory.Span.Slice(Used, Memory.Length - Used); //TODO Is this the right kind? 62 | } 63 | 64 | public (Memory, int) Frame() { EnsureBuffer(sizeof(int)); var frame = (Memory, Used); Used += sizeof(Int32); return frame; } 65 | public void Frame((Memory, int) frame, int value) { var (Memory, Used) = frame; BinaryPrimitives.WriteInt32BigEndian(Memory.Span.Slice(Used, Memory.Length - Used), value); } 66 | 67 | 68 | public void Write(byte value) { EnsureBuffer(sizeof(byte)); Memory.Span[Used++] = (byte)value; } //Save Memory.Slice overhead for performance 69 | 70 | public void WriteByte(byte value) => Write(value); 71 | public void WriteByte(int value) => WriteByte((byte)value); //This is a convenience for calls that use binary operators which always return int 72 | 73 | public int WriteUInt16BigEndian(int value) => WriteUInt16BigEndian((UInt16)value); 74 | public int WriteUInt16BigEndian(UInt16 value) { BinaryPrimitives.WriteUInt16BigEndian(GetBuffer(sizeof(UInt16)), value); Used += sizeof(UInt16); return sizeof(UInt16); } 75 | 76 | //public int WriteUInt16LittleEndian(UInt16 value) { BinaryPrimitives.WriteUInt16LittleEndian(GetBuffer(sizeof(UInt16)), value); Used += sizeof(UInt16); return sizeof(UInt16); } 77 | 78 | public int WriteInt32BigEndian(Int32 value) { BinaryPrimitives.WriteInt32BigEndian(GetBuffer(sizeof(Int32)), value); Used += sizeof(Int32); return sizeof(Int32); } 79 | public int WriteInt64BigEndian(Int64 value) { BinaryPrimitives.WriteInt64BigEndian(GetBuffer(sizeof(Int64)), value); Used += sizeof(Int64); return sizeof(Int64); } 80 | public int WriteUInt32BigEndian(UInt32 value) { BinaryPrimitives.WriteUInt32BigEndian(GetBuffer(sizeof(UInt32)), value); Used += sizeof(UInt32); return sizeof(UInt32); } 81 | public int WriteInt24BigEndian(int value) { const int SIZEOF = 3; var span = GetBuffer(SIZEOF); span[0] = (byte)((value >> 16) & 0xFF); span[1] = (byte)((value >> 8) & 0xFF); span[2] = (byte)(value & 0xFF); Used += SIZEOF; return SIZEOF; } 82 | 83 | public int Write(byte[] values) { foreach (var value in values) { Write(value); } return values.Length; } //TODO Buffer Slice Writer 84 | public int Write(ReadOnlySpan values) => Write(values.ToArray()); //TODO SpanWriter - I had this, where did it go? Maybe speed the Sequence writer too. 85 | public int Write(ReadOnlySequence values) { if (values.IsSingleSegment) { return Write(values.First.Span); } else { int count = 0; foreach (var memory in values) { count += Write(memory.Span); } return count; } } 86 | 87 | public int Write(string text) => Write(text.AsSpan(), Encoder, MaximumBytesPerChar); 88 | 89 | public int WritePrefixByte(string text) 90 | { 91 | var bytes = Encoding.GetByteCount(text); 92 | if (bytes > Byte.MaxValue) { throw new ArgumentOutOfRangeException(nameof(text), text, $"String encoding [{bytes}] would exceed the maximum prefix length. [{Byte.MaxValue}]"); } 93 | Write((Byte)bytes); 94 | return sizeof(Byte) + Write(text); 95 | } 96 | 97 | public int WritePrefixShort(string text) 98 | { 99 | var bytes = Encoding.GetByteCount(text); 100 | if (bytes > UInt16.MaxValue) { throw new ArgumentOutOfRangeException(nameof(text), text, $"String encoding [{bytes}] would exceed the maximum prefix length. [{UInt16.MaxValue}]"); } 101 | WriteUInt16BigEndian(bytes); 102 | return sizeof(UInt16) + Write(text); 103 | } 104 | 105 | public int WritePrefixShort(ReadOnlySpan buffer) 106 | { 107 | var bytes = buffer.Length; 108 | if (bytes > UInt16.MaxValue) { throw new ArgumentOutOfRangeException(nameof(buffer), buffer.Length, $"Buffer [{bytes}] would exceed the maximum prefix length. [{UInt16.MaxValue}]"); } 109 | WriteUInt16BigEndian(bytes); 110 | return sizeof(UInt16) + Write(buffer); 111 | } 112 | 113 | public int WritePrefixShort(ReadOnlySequence buffer) 114 | { 115 | var bytes = buffer.Length; 116 | if (bytes > UInt16.MaxValue) { throw new ArgumentOutOfRangeException(nameof(buffer), buffer.Length, $"Buffer [{bytes}] would exceed the maximum prefix length. [{UInt16.MaxValue}]"); } 117 | WriteUInt16BigEndian((UInt16)bytes); 118 | return sizeof(UInt16) + Write(buffer); 119 | } 120 | 121 | 122 | public unsafe void Write(char value, Encoder encoder, int encodingmaxbytesperchar) 123 | { 124 | var destination = GetBuffer(encodingmaxbytesperchar); 125 | 126 | var bytesUsed = 0; 127 | var charsUsed = 0; 128 | #if NETCOREAPP2_2 129 | _encoder.Convert(new Span(&value, 1), destination, false, out charsUsed, out bytesUsed, out _); 130 | #else 131 | fixed (byte* destinationBytes = &MemoryMarshal.GetReference(destination)) 132 | { 133 | encoder.Convert(&value, 1, destinationBytes, destination.Length, false, out charsUsed, out bytesUsed, out _); 134 | } 135 | #endif 136 | 137 | System.Diagnostics.Debug.Assert(charsUsed == 1); 138 | Used += bytesUsed; 139 | } 140 | 141 | public int Write(ReadOnlySpan source, Encoder encoder, int encodingmaxbytesperchar) 142 | { 143 | var length = 0; 144 | while (source.Length > 0) 145 | { 146 | var destination = GetBuffer(encodingmaxbytesperchar); 147 | 148 | var bytesUsed = 0; 149 | var charsUsed = 0; 150 | #if NETCOREAPP2_2 151 | encoder.Convert(source, destination, false, out charsUsed, out bytesUsed, out _); 152 | #else 153 | unsafe 154 | { 155 | fixed (char* sourceChars = &MemoryMarshal.GetReference(source)) 156 | fixed (byte* destinationBytes = &MemoryMarshal.GetReference(destination)) 157 | { 158 | encoder.Convert(sourceChars, source.Length, destinationBytes, destination.Length, false, out charsUsed, out bytesUsed, out _); 159 | } 160 | } 161 | #endif 162 | source = source.Slice(charsUsed); 163 | Used += bytesUsed; 164 | length += bytesUsed; 165 | } 166 | return length; 167 | } 168 | 169 | //TextWriter Compatibility 170 | public void Write(char[] buffer, int index, int count) => Write(buffer.AsSpan(index, count), Encoder, MaximumBytesPerChar); 171 | public void Write(char[] buffer) => Write(buffer, Encoder, MaximumBytesPerChar); 172 | public void Write(char value) => Write(value, Encoder, MaximumBytesPerChar); 173 | 174 | public void Flush() 175 | { 176 | if (Used > 0) 177 | { 178 | _bufferWriter.Advance(Used); 179 | Memory = Memory.Slice(Used, Memory.Length - Used); //TODO Is this the right overload for this? 180 | Used = 0; 181 | } 182 | } 183 | 184 | 185 | [ThreadStatic] 186 | private static BufferWriter Instance; 187 | 188 | public static BufferWriter Get(IBufferWriter bufferWriter, Encoding encoding = null) 189 | { 190 | var writer = Instance ?? new BufferWriter(null, encoding); //Special initialization to track prior use. 191 | Instance = null; //Decache this on the thread 192 | #if DEBUG 193 | if (writer.InUse) { throw new InvalidOperationException($"The {nameof(BufferWriter)} wasn't returned!"); } 194 | #endif 195 | return writer.Reset(bufferWriter); 196 | } 197 | 198 | public static void Return(BufferWriter writer) => Instance = writer.Reset(); 199 | } 200 | } -------------------------------------------------------------------------------- /RSocket.Core/IRSocketProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | 4 | namespace RSocket 5 | { 6 | /// 7 | /// Defines a handler for the raw protocol. Clients and Servers both implement parts of this interface; when they do not, they should throw NotImplementedException for that method. 8 | /// 9 | public interface IRSocketProtocol 10 | { 11 | void Setup(in RSocketProtocol.Setup message); 12 | void Error(in RSocketProtocol.Error message); 13 | void Payload(in RSocketProtocol.Payload message, ReadOnlySequence metadata, ReadOnlySequence data); 14 | void RequestStream(in RSocketProtocol.RequestStream message, ReadOnlySequence metadata, ReadOnlySequence data); 15 | void RequestResponse(in RSocketProtocol.RequestResponse message, ReadOnlySequence metadata, ReadOnlySequence data); 16 | void RequestFireAndForget(in RSocketProtocol.RequestFireAndForget message, ReadOnlySequence metadata, ReadOnlySequence data); 17 | void RequestChannel(in RSocketProtocol.RequestChannel message, ReadOnlySequence metadata, ReadOnlySequence data); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RSocket.Core/IRSocketStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Threading.Tasks; 4 | 5 | namespace RSocket 6 | { 7 | ///// 8 | ///// A stream of items from an RSocket. This is simply an Observer of the protocol's tuples. 9 | ///// 10 | //public interface IRSocketStream : IObserver<(ReadOnlySequence metadata, ReadOnlySequence data)> 11 | //{ 12 | //} 13 | 14 | //Not yet, backpressure! 15 | } 16 | -------------------------------------------------------------------------------- /RSocket.Core/IRSocketTransports.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Pipelines; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace RSocket 7 | { 8 | /// 9 | /// A Pipeline Transport for a connectable RSocket. Once connected, the Input and Output Pipelines can be used to communicate abstractly with the RSocket bytestream. 10 | /// 11 | public interface IRSocketTransport 12 | { 13 | PipeReader Input { get; } 14 | PipeWriter Output { get; } 15 | 16 | Task StartAsync(CancellationToken cancel = default); 17 | Task StopAsync(); 18 | } 19 | 20 | /// 21 | /// A Pipeline Transport for a serving RSocket. 22 | /// 23 | public interface IRSocketServerTransport 24 | { 25 | PipeReader Input { get; } 26 | PipeWriter Output { get; } 27 | 28 | Task StartAsync(CancellationToken cancel = default); 29 | Task StopAsync(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /RSocket.Core/RSocket.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | RSocket 6 | latest 7 | Apache-2.0 8 | RSocket-Net Core Library 9 | RSocket Team 10 | RSocket Protocol implementation for .NET 11 | RSocket Authors 12 | http://rsocket.io/ 13 | https://github.com/rsocket/rsocket-artwork/raw/master/rsocket-logo/PNG/r-socket-pink.png 14 | https://github.com/rsocket/rsocket-net 15 | RSocket, Protocol, Network, Reactive, Reactive-Streams 16 | 0.2.7 17 | 0.2.7 18 | 0.2.7 19 | 0.2.7 20 | 0.2.7 21 | 22 | 23 | 24 | true 25 | 26 | 27 | 28 | true 29 | 30 | 31 | 32 | RSocketStrongName.snk 33 | 34 | 35 | 36 | True 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /RSocket.Core/RSocket.Receiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Linq; 10 | using System.Reactive.Linq; 11 | using System.Reactive.Disposables; 12 | using System.Reactive.Threading.Tasks; 13 | 14 | using IRSocketStream = System.IObserver<(System.Buffers.ReadOnlySequence metadata, System.Buffers.ReadOnlySequence data)>; 15 | 16 | namespace RSocket 17 | { 18 | partial class RSocket 19 | { 20 | public class Receiver : IAsyncEnumerable 21 | { 22 | readonly Func Subscriber; 23 | readonly Func<(ReadOnlySequence data, ReadOnlySequence metadata), T> Mapper; 24 | 25 | public Receiver(Func subscriber, Func<(ReadOnlySequence data, ReadOnlySequence metadata), T> mapper) 26 | { 27 | Subscriber = subscriber; 28 | Mapper = mapper; 29 | } 30 | 31 | public async Task ExecuteAsync(CancellationToken cancellation = default) 32 | { 33 | var observable = Observable.Create<(ReadOnlySequence metadata, ReadOnlySequence data)>(observer => { 34 | Subscriber(observer).ConfigureAwait(false); 35 | return Disposable.Empty; 36 | }); 37 | 38 | var value = await observable.ToTask(cancellation); 39 | return Mapper((value.data, value.metadata)); 40 | } 41 | 42 | public async Task ExecuteAsync(T result, CancellationToken cancellation = default) 43 | { 44 | var observable = Observable.Create<(ReadOnlySequence metadata, ReadOnlySequence data)>(observer => { 45 | Subscriber(observer).ConfigureAwait(false); 46 | return Disposable.Empty; 47 | }); 48 | await observable.ToTask(cancellation); 49 | return result; 50 | } 51 | 52 | public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellation = default) 53 | { 54 | var observable = Observable.Create<(ReadOnlySequence metadata, ReadOnlySequence data)>(observer => { 55 | Subscriber(observer).ConfigureAwait(false); 56 | return Disposable.Empty; 57 | }); 58 | return observable 59 | .Select(value => Mapper((value.data, value.metadata))) 60 | .ToAsyncEnumerable() 61 | .GetAsyncEnumerator(cancellation); 62 | } 63 | } 64 | 65 | public class Receiver : Receiver 66 | { 67 | public Receiver(Func> subscriber, IAsyncEnumerable source, Func metadata, ReadOnlySequence data)> sourcemapper, Func<(ReadOnlySequence metadata, ReadOnlySequence data), T> resultmapper) : 68 | base(stream => Subscribe(stream, subscriber(stream), source, sourcemapper), resultmapper) 69 | { 70 | } 71 | 72 | static async Task Subscribe(IRSocketStream stream, Task original, IAsyncEnumerable source, Func metadata, ReadOnlySequence data)> sourcemapper) 73 | { 74 | var channel = await original; //Let the receiver hook up first before we start generating values. 75 | var enumerator = source.GetAsyncEnumerator(); 76 | try 77 | { 78 | while (await enumerator.MoveNextAsync()) 79 | { 80 | await channel.Send(sourcemapper(enumerator.Current)); 81 | } 82 | } 83 | finally { await enumerator.DisposeAsync(); } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /RSocket.Core/RSocket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Reactive.Linq; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | using IRSocketStream = System.IObserver<(System.Buffers.ReadOnlySequence metadata, System.Buffers.ReadOnlySequence data)>; 11 | 12 | namespace RSocket 13 | { 14 | public interface IRSocketChannel 15 | { 16 | Task Send((ReadOnlySequence metadata, ReadOnlySequence data) value); 17 | } 18 | 19 | public partial class RSocket : IRSocketProtocol 20 | { 21 | PrefetchOptions Options { get; set; } 22 | 23 | //TODO Hide. 24 | public IRSocketTransport Transport { get; set; } 25 | private int StreamId = 1 - 2; //SPEC: Stream IDs on the client MUST start at 1 and increment by 2 sequentially, such as 1, 3, 5, 7, etc 26 | private int NewStreamId() => Interlocked.Add(ref StreamId, 2); //TODO SPEC: To reuse or not... Should tear down the client if this happens or have to skip in-use IDs. 27 | 28 | private ConcurrentDictionary Dispatcher = new ConcurrentDictionary(); 29 | private int StreamDispatch(int id, IRSocketStream transform) { Dispatcher[id] = transform; return id; } 30 | private int StreamDispatch(IRSocketStream transform) => StreamDispatch(NewStreamId(), transform); 31 | //TODO Stream Destruction - i.e. removal from the dispatcher. 32 | 33 | protected IDisposable ChannelSubscription; //TODO Tracking state for channels 34 | 35 | public RSocket(IRSocketTransport transport, PrefetchOptions options = default) 36 | { 37 | Transport = transport; 38 | Options = options ?? PrefetchOptions.Default; 39 | } 40 | 41 | /// Binds the RSocket to its Transport and begins handling messages. 42 | /// Cancellation for the handler. Requesting cancellation will stop message handling. 43 | /// The handler task. 44 | public Task Connect(CancellationToken cancel = default) => RSocketProtocol.Handler(this, Transport.Input, cancel); 45 | public Task Setup(TimeSpan keepalive, TimeSpan lifetime, string metadataMimeType = null, string dataMimeType = null, ReadOnlySequence data = default, ReadOnlySequence metadata = default) => new RSocketProtocol.Setup(keepalive, lifetime, metadataMimeType: metadataMimeType, dataMimeType: dataMimeType, data: data, metadata: metadata).WriteFlush(Transport.Output, data: data, metadata: metadata); 46 | 47 | 48 | //TODO SPEC: A requester MUST not send PAYLOAD frames after the REQUEST_CHANNEL frame until the responder sends a REQUEST_N frame granting credits for number of PAYLOADs able to be sent. 49 | 50 | public virtual IAsyncEnumerable RequestChannel(IAsyncEnumerable source, Func> sourcemapper, 51 | Func<(ReadOnlySequence data, ReadOnlySequence metadata), T> resultmapper, 52 | ReadOnlySequence data = default, ReadOnlySequence metadata = default) 53 | => new Receiver(stream => RequestChannel(stream, data, metadata), source, _ => (default, sourcemapper(_)), value => resultmapper(value)); 54 | 55 | public async Task RequestChannel(IRSocketStream stream, ReadOnlySequence data, ReadOnlySequence metadata = default, int initial = RSocketOptions.INITIALDEFAULT) 56 | { 57 | var id = StreamDispatch(stream); 58 | await new RSocketProtocol.RequestChannel(id, data, metadata, initialRequest: Options.GetInitialRequestSize(initial)).WriteFlush(Transport.Output, data, metadata); 59 | var channel = new ChannelHandler(this, id); 60 | return channel; 61 | } 62 | 63 | protected class ChannelHandler : IRSocketChannel //TODO hmmm... 64 | { 65 | readonly RSocket Socket; 66 | readonly int Stream; 67 | 68 | public ChannelHandler(RSocket socket, int stream) { Socket = socket; Stream = stream; } 69 | 70 | public Task Send((ReadOnlySequence metadata, ReadOnlySequence data) value) 71 | { 72 | if (!Socket.Dispatcher.ContainsKey(Stream)) { throw new InvalidOperationException("Channel is closed"); } 73 | return new RSocketProtocol.Payload(Stream, value.data, value.metadata, next: true).WriteFlush(Socket.Transport.Output, value.data, value.metadata); 74 | } 75 | } 76 | 77 | 78 | public virtual IAsyncEnumerable RequestStream(Func<(ReadOnlySequence data, ReadOnlySequence metadata), T> resultmapper, 79 | ReadOnlySequence data = default, ReadOnlySequence metadata = default) 80 | => new Receiver(stream => RequestStream(stream, data, metadata), value => resultmapper(value)); 81 | 82 | public Task RequestStream(IRSocketStream stream, ReadOnlySequence data, ReadOnlySequence metadata = default, int initial = RSocketOptions.INITIALDEFAULT) 83 | { 84 | var id = StreamDispatch(stream); 85 | return new RSocketProtocol.RequestStream(id, data, metadata, initialRequest: Options.GetInitialRequestSize(initial)).WriteFlush(Transport.Output, data, metadata); 86 | } 87 | 88 | public virtual Task RequestResponse(Func<(ReadOnlySequence data, ReadOnlySequence metadata), T> resultmapper, 89 | ReadOnlySequence data = default, ReadOnlySequence metadata = default) 90 | => new Receiver(stream => RequestResponse(stream, data, metadata), resultmapper).ExecuteAsync(); 91 | 92 | public Task RequestResponse(IRSocketStream stream, ReadOnlySequence data, ReadOnlySequence metadata = default) 93 | { 94 | var id = StreamDispatch(stream); 95 | return new RSocketProtocol.RequestResponse(id, data, metadata).WriteFlush(Transport.Output, data, metadata); 96 | } 97 | 98 | 99 | public virtual Task RequestFireAndForget( 100 | ReadOnlySequence data = default, ReadOnlySequence metadata = default) 101 | => new Receiver(stream => RequestFireAndForget(stream, data, metadata), _ => true).ExecuteAsync(result: true); 102 | 103 | public Task RequestFireAndForget(IRSocketStream stream, ReadOnlySequence data, ReadOnlySequence metadata = default) 104 | { 105 | var id = StreamDispatch(stream); 106 | return new RSocketProtocol.RequestFireAndForget(id, data, metadata).WriteFlush(Transport.Output, data, metadata); 107 | } 108 | 109 | 110 | void IRSocketProtocol.Payload(in RSocketProtocol.Payload message, ReadOnlySequence metadata, ReadOnlySequence data) 111 | { 112 | //Console.WriteLine($"{value.Header.Stream:0000}===>{Encoding.UTF8.GetString(value.Data.ToArray())}"); 113 | if (Dispatcher.TryGetValue(message.Stream, out var transform)) 114 | { 115 | if (message.IsNext) { transform.OnNext((metadata, data)); } 116 | if (message.IsComplete) { transform.OnCompleted(); } 117 | } 118 | else 119 | { 120 | //TODO Log missing stream here. 121 | } 122 | } 123 | 124 | 125 | void Schedule(int stream, Func operation, CancellationToken cancel = default) 126 | { 127 | var task = operation(stream, cancel); 128 | if (!task.IsCompleted) { task.ConfigureAwait(false); } //FUTURE Someday might want to schedule these in a different pool or perhaps track all in-flight tasks. 129 | } 130 | 131 | 132 | public virtual void Setup(in RSocketProtocol.Setup value) => throw new InvalidOperationException($"Client cannot process Setup frames"); //TODO This exception just stalls processing. Need to make sure it's handled. 133 | 134 | void IRSocketProtocol.Error(in RSocketProtocol.Error message) 135 | { 136 | if (Dispatcher.TryGetValue(message.Stream, out var transform)) 137 | { 138 | transform.OnError(new RSocketException(message.ErrorText, message.ErrorCode)); 139 | } 140 | else 141 | { 142 | //TODO Log missing stream here. 143 | } 144 | } 145 | 146 | void IRSocketProtocol.RequestFireAndForget(in RSocketProtocol.RequestFireAndForget message, ReadOnlySequence metadata, ReadOnlySequence data) => throw new NotImplementedException(); 147 | 148 | 149 | public void Respond( 150 | Func<(ReadOnlySequence Data, ReadOnlySequence Metadata), TRequest> requestTransform, 151 | Func> producer, 152 | Func Data, ReadOnlySequence Metadata)> resultTransform) => 153 | Responder = (request) => (from result in producer(requestTransform(request)) select resultTransform(result)).FirstAsync(); 154 | 155 | public Func<(ReadOnlySequence Data, ReadOnlySequence Metadata), ValueTask<(ReadOnlySequence Data, ReadOnlySequence Metadata)>> Responder { get; set; } = request => throw new NotImplementedException(); 156 | 157 | void IRSocketProtocol.RequestResponse(in RSocketProtocol.RequestResponse message, ReadOnlySequence metadata, ReadOnlySequence data) 158 | { 159 | Schedule(message.Stream, async (stream, cancel) => 160 | { 161 | var value = await Responder((data, metadata)); //TODO Handle Errors. 162 | await new RSocketProtocol.Payload(stream, value.Data, value.Metadata, next: true, complete: true).WriteFlush(Transport.Output, value.Data, value.Metadata); 163 | }); 164 | } 165 | 166 | 167 | public void Stream( 168 | Func<(ReadOnlySequence Data, ReadOnlySequence Metadata), TRequest> requestTransform, 169 | Func> producer, 170 | Func Data, ReadOnlySequence Metadata)> resultTransform) => 171 | Streamer = (request) => from result in producer(requestTransform(request)) select resultTransform(result); 172 | 173 | public Func<(ReadOnlySequence Data, ReadOnlySequence Metadata), IAsyncEnumerable<(ReadOnlySequence Data, ReadOnlySequence Metadata)>> Streamer { get; set; } = request => throw new NotImplementedException(); 174 | 175 | void IRSocketProtocol.RequestStream(in RSocketProtocol.RequestStream message, ReadOnlySequence metadata, ReadOnlySequence data) 176 | { 177 | Schedule(message.Stream, async (stream, cancel) => 178 | { 179 | var source = Streamer((data, metadata)); //TODO Handle Errors. 180 | await ForEach(source, 181 | action: value => new RSocketProtocol.Payload(stream, value.Data, value.Metadata, next: true).WriteFlush(Transport.Output, value.Data, value.Metadata), 182 | final: () => new RSocketProtocol.Payload(stream, complete: true).WriteFlush(Transport.Output)); 183 | }); 184 | } 185 | 186 | 187 | //TODO, probably need to have an IAE pipeline overload too. 188 | 189 | public void Channel(Func, IAsyncEnumerable> pipeline, 190 | Func<(ReadOnlySequence Data, ReadOnlySequence Metadata), TRequest> requestTransform, 191 | Func<(ReadOnlySequence Data, ReadOnlySequence Metadata), TIncoming> incomingTransform, 192 | Func Data, ReadOnlySequence Metadata)> outgoingTransform) => 193 | Channeler = (request, incoming) => from result in pipeline(requestTransform(request), from item in incoming select incomingTransform(item)) select outgoingTransform(result); 194 | 195 | 196 | public Func<(ReadOnlySequence Data, ReadOnlySequence Metadata), IObservable<(ReadOnlySequence data, ReadOnlySequence metadata)>, IAsyncEnumerable<(ReadOnlySequence data, ReadOnlySequence metadata)>> Channeler { get; set; } = (request, incoming) => throw new NotImplementedException(); 197 | 198 | 199 | void IRSocketProtocol.RequestChannel(in RSocketProtocol.RequestChannel message, ReadOnlySequence metadata, ReadOnlySequence data) 200 | { 201 | Schedule(message.Stream, async (stream, cancel) => 202 | { 203 | var inc = Observable.Create<(ReadOnlySequence metadata, ReadOnlySequence data)>(observer => () => StreamDispatch(stream, observer)); 204 | var outgoing = Channeler((data, metadata), inc); //TODO Handle Errors. 205 | 206 | await ForEach(outgoing, 207 | action: value => new RSocketProtocol.Payload(stream, value.data, value.metadata, next: true).WriteFlush(Transport.Output, value.data, value.metadata), 208 | final: () => new RSocketProtocol.Payload(stream, complete: true).WriteFlush(Transport.Output)); 209 | }); 210 | } 211 | 212 | static async Task ForEach(IAsyncEnumerable source, Func action, CancellationToken cancel = default, Func final = default) 213 | { 214 | await source.ForEachAsync(item => action(item), cancel); 215 | await final?.Invoke(); 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /RSocket.Core/RSocketClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using IRSocketStream = System.IObserver<(System.Buffers.ReadOnlySequence metadata, System.Buffers.ReadOnlySequence data)>; 9 | 10 | namespace RSocket 11 | { 12 | public class RSocketClient : RSocket 13 | { 14 | Task Handler; 15 | RSocketOptions Options { get; set; } 16 | 17 | public RSocketClient(IRSocketTransport transport, RSocketOptions options = default) : base(transport, options) { } 18 | 19 | public Task ConnectAsync(RSocketOptions options = default, byte[] data = default, byte[] metadata = default) => ConnectAsync(options ?? RSocketOptions.Default, data: data == default ? default : new ReadOnlySequence(data), metadata: metadata == default ? default : new ReadOnlySequence(metadata)); 20 | 21 | public async Task ConnectAsync(RSocketOptions options, ReadOnlySequence metadata, ReadOnlySequence data) 22 | { 23 | await Transport.StartAsync(); 24 | Handler = Connect(CancellationToken.None); 25 | await Setup(options.KeepAlive, options.Lifetime, options.MetadataMimeType, options.DataMimeType, data: data, metadata: metadata); 26 | } 27 | 28 | /// A simplfied RSocket Client that operates only on UTF-8 strings. 29 | public class ForStrings 30 | { 31 | private readonly RSocketClient Client; 32 | public ForStrings(RSocketClient client) { Client = client; } 33 | public Task RequestResponse(string data, string metadata = default) => Client.RequestResponse(value => Encoding.UTF8.GetString(value.data.ToArray()), new ReadOnlySequence(Encoding.UTF8.GetBytes(data)), metadata == default ? default : new ReadOnlySequence(Encoding.UTF8.GetBytes(metadata))); 34 | public IAsyncEnumerable RequestStream(string data, string metadata = default) 35 | { 36 | return Client.RequestStream(value => 37 | { 38 | return Encoding.UTF8.GetString(value.data.ToArray()); 39 | }, new ReadOnlySequence(Encoding.UTF8.GetBytes(data)), metadata == default ? default : new ReadOnlySequence(Encoding.UTF8.GetBytes(metadata))); 40 | } 41 | 42 | public IAsyncEnumerable RequestChannel(IAsyncEnumerable inputs, string data = default, string metadata = default) => 43 | Client.RequestChannel(inputs, input => new ReadOnlySequence(Encoding.UTF8.GetBytes(input)), result => Encoding.UTF8.GetString(result.data.ToArray()), 44 | data == default ? default : new ReadOnlySequence(Encoding.UTF8.GetBytes(data)), 45 | metadata == default ? default : new ReadOnlySequence(Encoding.UTF8.GetBytes(metadata))); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RSocket.Core/RSocketException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace RSocket 5 | { 6 | public class RSocketException : Exception 7 | { 8 | public RSocketProtocol.ErrorCodes Error { get; } 9 | 10 | public RSocketException(RSocketProtocol.ErrorCodes error) 11 | { 12 | Error = error; 13 | } 14 | 15 | protected RSocketException(SerializationInfo info, StreamingContext context, RSocketProtocol.ErrorCodes error) : base(info, context) 16 | { 17 | Error = error; 18 | } 19 | 20 | public RSocketException(string message, RSocketProtocol.ErrorCodes error) : base(message) 21 | { 22 | Error = error; 23 | } 24 | 25 | public RSocketException(string message, Exception innerException, RSocketProtocol.ErrorCodes error) : base(message, innerException) 26 | { 27 | Error = error; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RSocket.Core/RSocketOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RSocket 4 | { 5 | 6 | public class PrefetchOptions { 7 | public int InitialRequestSize { get; set; } = 8; 8 | 9 | public int GetInitialRequestSize(int initial) => initial <= 0 ? InitialRequestSize : initial; 10 | 11 | public static readonly PrefetchOptions Default = new PrefetchOptions(); 12 | } 13 | 14 | public class RSocketOptions : PrefetchOptions 15 | { 16 | public const int INITIALDEFAULT = int.MinValue; 17 | public const string BINARYMIMETYPE = "application/octet-stream"; 18 | 19 | public TimeSpan KeepAlive { get; set; } 20 | public TimeSpan Lifetime { get; set; } 21 | public string DataMimeType { get; set; } 22 | public string MetadataMimeType { get; set; } 23 | 24 | public static readonly RSocketOptions Default = new RSocketOptions() 25 | { 26 | KeepAlive = TimeSpan.FromMinutes(1), 27 | Lifetime = TimeSpan.FromMinutes(3), 28 | DataMimeType = BINARYMIMETYPE, 29 | MetadataMimeType = BINARYMIMETYPE, 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /RSocket.Core/RSocketProtocol.Enumerations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace RSocket 6 | { 7 | partial class RSocketProtocol 8 | { 9 | public const UInt16 MAJOR_VERSION = 1; 10 | public const UInt16 MINOR_VERSION = 0; 11 | 12 | private const int MaxMetadataLength = 16777215; 13 | 14 | public enum Types 15 | { 16 | /// Reserved 17 | Reserved = 0x00, 18 | /// Setup: Sent by client to initiate protocol processing. 19 | Setup = 0x01, 20 | /// Lease: Sent by Responder to grant the ability to send requests. 21 | Lease = 0x02, 22 | /// Keepalive: Connection keepalive. 23 | KeepAlive = 0x03, 24 | /// Request Response: Request single response. 25 | Request_Response = 0x04, 26 | /// Fire And Forget: A single one-way message. 27 | Request_Fire_And_Forget = 0x05, 28 | /// Request Stream: Request a completable stream. 29 | Request_Stream = 0x06, 30 | /// Request Channel: Request a completable stream in both directions. 31 | Request_Channel = 0x07, 32 | /// Request N: Request N more items with Reactive Streams semantics. 33 | Request_N = 0x08, 34 | /// Cancel Request: Cancel outstanding request. 35 | Cancel = 0x09, 36 | /// Payload: Payload on a stream. For example, response to a request, or message on a channel. 37 | Payload = 0x0A, 38 | /// Error: Error at connection or application level. 39 | Error = 0x0B, 40 | /// Metadata: Asynchronous Metadata frame 41 | Metadata_Push = 0x0C, 42 | /// Resume: Replaces SETUP for Resuming Operation (optional) 43 | Resume = 0x0D, 44 | /// Resume OK : Sent in response to a RESUME if resuming operation possible (optional) 45 | Resume_OK = 0x0E, 46 | /// Extension Header: Used To Extend more frame types as well as extensions. 47 | Extension = 0x3F, 48 | } 49 | 50 | public enum ErrorCodes : uint 51 | { 52 | /// Reserved 53 | Reserved = 0x00000000, 54 | /// The Setup frame is invalid for the server (it could be that the client is too recent for the old server). Stream ID MUST be 0. 55 | Invalid_Setup = 0x00000001, 56 | /// Some (or all) of the parameters specified by the client are unsupported by the server. Stream ID MUST be 0. 57 | Unsupported_Setup = 0x00000002, 58 | /// The server rejected the setup, it can specify the reason in the payload. Stream ID MUST be 0. 59 | Rejected_Setup = 0x00000003, 60 | /// The server rejected the resume, it can specify the reason in the payload. Stream ID MUST be 0. 61 | Rejected_Resume = 0x00000004, 62 | /// The connection is being terminated. Stream ID MUST be 0. Sender or Receiver of this frame MAY close the connection immediately without waiting for outstanding streams to terminate. 63 | Connection_Error = 0x00000101, 64 | /// The connection is being terminated. Stream ID MUST be 0. Sender or Receiver of this frame MUST wait for outstanding streams to terminate before closing the connection. New requests MAY not be accepted. 65 | Connection_Close = 0x00000102, 66 | /// Application layer logic generating a Reactive Streams onError event. Stream ID MUST be > 0. 67 | Application_Error = 0x00000201, 68 | /// Despite being a valid request, the Responder decided to reject it. The Responder guarantees that it didn't process the request. The reason for the rejection is explained in the Error Data section. Stream ID MUST be > 0. 69 | Rejected = 0x00000202, 70 | /// The Responder canceled the request but may have started processing it (similar to REJECTED but doesn't guarantee lack of side-effects). Stream ID MUST be > 0. 71 | Canceled = 0x00000203, 72 | /// The request is invalid. Stream ID MUST be > 0. 73 | Invalid = 0x00000204, 74 | /// Reserved for Extension Use 75 | Extension = 0xFFFFFFFF, 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /RSocket.Core/RSocketProtocol.Handler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.IO.Pipelines; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace RSocket 11 | { 12 | partial class RSocketProtocol 13 | { 14 | //TODO Consider if this is really needed. Could always wrap a logging protocol handler. 15 | static void Decoded(string message) => Console.WriteLine(message); 16 | static void OnSetup(IRSocketProtocol sink, in RSocketProtocol.Setup message) => sink.Setup(message); 17 | static void OnError(IRSocketProtocol sink, in RSocketProtocol.Error message) => sink.Error(message); 18 | static void OnPayload(IRSocketProtocol sink, in RSocketProtocol.Payload message, ReadOnlySequence metadata, ReadOnlySequence data) => sink.Payload(message, metadata, data); 19 | static void OnRequestStream(IRSocketProtocol sink, in RSocketProtocol.RequestStream message, ReadOnlySequence metadata, ReadOnlySequence data) => sink.RequestStream(message, metadata, data); 20 | static void OnRequestResponse(IRSocketProtocol sink, in RSocketProtocol.RequestResponse message, ReadOnlySequence metadata, ReadOnlySequence data) => sink.RequestResponse(message, metadata, data); 21 | static void OnRequestFireAndForget(IRSocketProtocol sink, in RSocketProtocol.RequestFireAndForget message, ReadOnlySequence metadata, ReadOnlySequence data) => sink.RequestFireAndForget(message, metadata, data); 22 | static void OnRequestChannel(IRSocketProtocol sink, in RSocketProtocol.RequestChannel message, ReadOnlySequence metadata, ReadOnlySequence data) => sink.RequestChannel(message, metadata, data); 23 | 24 | static public async Task Handler(IRSocketProtocol sink, PipeReader pipereader, CancellationToken cancellation) 25 | { 26 | //The original implementation was a state-machine parser with resumability. It doesn't seem like the other implementations follow this pattern and the .NET folks are still figuring this out too - see the internal JSON parser discussion for how they're handling state machine persistence across async boundaries when servicing a Pipeline. So, this version of the handler only processes complete messages at some cost to memory buffer scalability. 27 | //Note that this means that the Pipeline must be configured to have enough buffering for a complete message before source-quenching. This also means that the downstream consumers don't really have to support resumption, so the interface no longer has the partial buffer methods in it. 28 | while (!cancellation.IsCancellationRequested) 29 | { 30 | var read = await pipereader.ReadAsync(cancellation); 31 | var buffer = read.Buffer; 32 | if (buffer.IsEmpty && read.IsCompleted) { break; } 33 | var position = buffer.Start; 34 | 35 | //Due to the nature of Pipelines as simple binary pipes, all Transport adapters assemble a standard message frame whether or not the underlying transport signals length, EoM, etc. 36 | var (Length, IsEndOfMessage) = MessageFramePeek(buffer); 37 | if (buffer.Length < Length + MESSAGEFRAMESIZE) { pipereader.AdvanceTo(buffer.Start, buffer.End); continue; } //Don't have a complete message yet. Tell the pipe that we've evaluated up to the current buffer end, but cannot yet consume it. 38 | 39 | await Process(Length, buffer.Slice(position = buffer.GetPosition(MESSAGEFRAMESIZE, position), Length)); 40 | pipereader.AdvanceTo(position = buffer.GetPosition(Length, position)); 41 | //TODO UNIT TEST- this should work now too!!! Need to evaluate if there is more than one packet in the pipe including edges like part of the length bytes are there but not all. 42 | } 43 | pipereader.Complete(); 44 | 45 | 46 | //This is the non-async portion of the handler. SequenceReader and the other stack-allocated items cannot be used in an async context. 47 | Task Process(int framelength, ReadOnlySequence sequence) 48 | { 49 | var reader = new SequenceReader(sequence); 50 | var header = new Header(ref reader, framelength); 51 | 52 | switch (header.Type) 53 | { 54 | case Types.Reserved: throw new InvalidOperationException($"Protocol Reserved! [{header.Type}]"); 55 | case Types.Setup: 56 | var setup = new Setup(header, ref reader); 57 | OnSetup(sink, setup); //TODO These can have metadata! , setup.ReadMetadata(ref reader), setup.ReadData(ref reader));); 58 | break; 59 | case Types.Lease: 60 | var lease = new Lease(header, ref reader); 61 | break; 62 | case Types.KeepAlive: 63 | var keepalive = new KeepAlive(header, ref reader); 64 | break; 65 | case Types.Request_Response: 66 | var requestresponse = new RequestResponse(header, ref reader); 67 | if (requestresponse.Validate()) { OnRequestResponse(sink, requestresponse, requestresponse.ReadMetadata(reader), requestresponse.ReadData(reader)); } 68 | break; 69 | case Types.Request_Fire_And_Forget: 70 | var requestfireandforget = new RequestFireAndForget(header, ref reader); 71 | if (requestfireandforget.Validate()) { OnRequestFireAndForget(sink, requestfireandforget, requestfireandforget.ReadMetadata(reader), requestfireandforget.ReadData(reader)); } 72 | break; 73 | case Types.Request_Stream: 74 | var requeststream = new RequestStream(header, ref reader); 75 | if (requeststream.Validate()) { OnRequestStream(sink, requeststream, requeststream.ReadMetadata(reader), requeststream.ReadData(reader)); } 76 | break; 77 | case Types.Request_Channel: 78 | var requestchannel = new RequestChannel(header, ref reader); 79 | if (requestchannel.Validate()) { OnRequestChannel(sink, requestchannel, requestchannel.ReadMetadata(reader), requestchannel.ReadData(reader)); } 80 | break; 81 | case Types.Request_N: 82 | var requestne = new RequestN(header, ref reader); 83 | break; 84 | case Types.Cancel: 85 | var cancel = new Cancel(header, ref reader); 86 | break; 87 | case Types.Payload: 88 | var payload = new Payload(header, ref reader); 89 | Decoded(payload.ToString()); 90 | if (payload.Validate()) { OnPayload(sink, payload, payload.ReadMetadata(reader), payload.ReadData(reader)); } 91 | break; 92 | case Types.Error: 93 | var error = new Error(header, ref reader); 94 | Decoded(error.ToString()); 95 | OnError(sink, error); 96 | break; 97 | case Types.Metadata_Push: 98 | var metadatapush = new MetadataPush(header, ref reader); 99 | break; 100 | case Types.Resume: { throw new NotSupportedException($"Protocol Resumption not Supported. [{header.Type}]"); } 101 | case Types.Resume_OK: { throw new NotSupportedException($"Protocol Resumption not Supported. [{header.Type}]"); } 102 | case Types.Extension: if (!header.CanIgnore) { throw new InvalidOperationException($"Protocol Extension Unsupported! [{header.Type}]"); } else break; 103 | default: if (!header.CanIgnore) { throw new InvalidOperationException($"Protocol Unknown Type! [{header.Type}]"); } else break; 104 | } 105 | return Task.CompletedTask; 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /RSocket.Core/RSocketServer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace RSocket 5 | { 6 | public class RSocketServer : RSocket 7 | { 8 | Task Handler; 9 | 10 | public RSocketServer(IRSocketTransport transport, PrefetchOptions options = default) : base(transport, options) { } 11 | 12 | public async Task ConnectAsync() 13 | { 14 | await Transport.StartAsync(); 15 | Handler = Connect(CancellationToken.None); 16 | } 17 | 18 | public override void Setup(in RSocketProtocol.Setup value) 19 | { 20 | 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RSocket.Core/RSocketStrongName.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsocket/rsocket-net/be1092e417e0ec227cf77ebd41ed4b5469ede421/RSocket.Core/RSocketStrongName.snk -------------------------------------------------------------------------------- /RSocket.Core/SignalR/Common/Utf8BufferTextWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Buffers; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Runtime.CompilerServices; 9 | using System.Runtime.InteropServices; 10 | using System.Text; 11 | 12 | namespace Microsoft.AspNetCore.Internal 13 | { 14 | internal sealed class Utf8BufferTextWriter : TextWriter 15 | { 16 | private static readonly UTF8Encoding _utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); 17 | private static readonly int MaximumBytesPerUtf8Char = 4; 18 | 19 | [ThreadStatic] 20 | private static Utf8BufferTextWriter _cachedInstance; 21 | 22 | private readonly Encoder _encoder; 23 | private IBufferWriter _bufferWriter; 24 | private Memory _memory; 25 | private int _memoryUsed; 26 | 27 | #if DEBUG 28 | private bool _inUse; 29 | #endif 30 | 31 | public override Encoding Encoding => _utf8NoBom; 32 | 33 | public Utf8BufferTextWriter() 34 | { 35 | _encoder = _utf8NoBom.GetEncoder(); 36 | } 37 | 38 | public static Utf8BufferTextWriter Get(IBufferWriter bufferWriter) 39 | { 40 | var writer = _cachedInstance; 41 | if (writer == null) 42 | { 43 | writer = new Utf8BufferTextWriter(); 44 | } 45 | 46 | // Taken off the thread static 47 | _cachedInstance = null; 48 | #if DEBUG 49 | if (writer._inUse) 50 | { 51 | throw new InvalidOperationException("The writer wasn't returned!"); 52 | } 53 | 54 | writer._inUse = true; 55 | #endif 56 | writer.SetWriter(bufferWriter); 57 | return writer; 58 | } 59 | 60 | public static void Return(Utf8BufferTextWriter writer) 61 | { 62 | _cachedInstance = writer; 63 | 64 | writer._encoder.Reset(); 65 | writer._memory = Memory.Empty; 66 | writer._memoryUsed = 0; 67 | writer._bufferWriter = null; 68 | 69 | #if DEBUG 70 | writer._inUse = false; 71 | #endif 72 | } 73 | 74 | public void SetWriter(IBufferWriter bufferWriter) 75 | { 76 | _bufferWriter = bufferWriter; 77 | } 78 | 79 | public override void Write(char[] buffer, int index, int count) 80 | { 81 | WriteInternal(buffer.AsSpan(index, count)); 82 | } 83 | 84 | public override void Write(char[] buffer) 85 | { 86 | WriteInternal(buffer); 87 | } 88 | 89 | public override void Write(char value) 90 | { 91 | if (value <= 127) 92 | { 93 | EnsureBuffer(); 94 | 95 | // Only need to set one byte 96 | // Avoid Memory.Slice overhead for perf 97 | _memory.Span[_memoryUsed] = (byte)value; 98 | _memoryUsed++; 99 | } 100 | else 101 | { 102 | WriteMultiByteChar(value); 103 | } 104 | } 105 | 106 | private unsafe void WriteMultiByteChar(char value) 107 | { 108 | var destination = GetBuffer(); 109 | 110 | // Json.NET only writes ASCII characters by themselves, e.g. {}[], etc 111 | // this should be an exceptional case 112 | var bytesUsed = 0; 113 | var charsUsed = 0; 114 | #if NETCOREAPP2_2 115 | _encoder.Convert(new Span(&value, 1), destination, false, out charsUsed, out bytesUsed, out _); 116 | #else 117 | fixed (byte* destinationBytes = &MemoryMarshal.GetReference(destination)) 118 | { 119 | _encoder.Convert(&value, 1, destinationBytes, destination.Length, false, out charsUsed, out bytesUsed, out _); 120 | } 121 | #endif 122 | 123 | Debug.Assert(charsUsed == 1); 124 | 125 | _memoryUsed += bytesUsed; 126 | } 127 | 128 | public override void Write(string value) 129 | { 130 | WriteInternal(value.AsSpan()); 131 | } 132 | 133 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 134 | private Span GetBuffer() 135 | { 136 | EnsureBuffer(); 137 | 138 | return _memory.Span.Slice(_memoryUsed, _memory.Length - _memoryUsed); 139 | } 140 | 141 | private void EnsureBuffer() 142 | { 143 | // We need at least enough bytes to encode a single UTF-8 character, or Encoder.Convert will throw. 144 | // Normally, if there isn't enough space to write every character of a char buffer, Encoder.Convert just 145 | // writes what it can. However, if it can't even write a single character, it throws. So if the buffer has only 146 | // 2 bytes left and the next character to write is 3 bytes in UTF-8, an exception is thrown. 147 | var remaining = _memory.Length - _memoryUsed; 148 | if (remaining < MaximumBytesPerUtf8Char) 149 | { 150 | // Used up the memory from the buffer writer so advance and get more 151 | if (_memoryUsed > 0) 152 | { 153 | _bufferWriter.Advance(_memoryUsed); 154 | } 155 | 156 | _memory = _bufferWriter.GetMemory(MaximumBytesPerUtf8Char); 157 | _memoryUsed = 0; 158 | } 159 | } 160 | 161 | private void WriteInternal(ReadOnlySpan buffer) 162 | { 163 | while (buffer.Length > 0) 164 | { 165 | // The destination byte array might not be large enough so multiple writes are sometimes required 166 | var destination = GetBuffer(); 167 | 168 | var bytesUsed = 0; 169 | var charsUsed = 0; 170 | #if NETCOREAPP2_2 171 | _encoder.Convert(buffer, destination, false, out charsUsed, out bytesUsed, out _); 172 | #else 173 | unsafe 174 | { 175 | fixed (char* sourceChars = &MemoryMarshal.GetReference(buffer)) 176 | fixed (byte* destinationBytes = &MemoryMarshal.GetReference(destination)) 177 | { 178 | _encoder.Convert(sourceChars, buffer.Length, destinationBytes, destination.Length, false, out charsUsed, out bytesUsed, out _); 179 | } 180 | } 181 | #endif 182 | 183 | buffer = buffer.Slice(charsUsed); 184 | _memoryUsed += bytesUsed; 185 | } 186 | } 187 | 188 | public override void Flush() 189 | { 190 | if (_memoryUsed > 0) 191 | { 192 | _bufferWriter.Advance(_memoryUsed); 193 | _memory = _memory.Slice(_memoryUsed, _memory.Length - _memoryUsed); 194 | _memoryUsed = 0; 195 | } 196 | } 197 | 198 | protected override void Dispose(bool disposing) 199 | { 200 | base.Dispose(disposing); 201 | 202 | if (disposing) 203 | { 204 | Flush(); 205 | } 206 | } 207 | } 208 | } -------------------------------------------------------------------------------- /RSocket.Core/System.Buffers/ReadOnlySequenceFromNetCore3.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace System.Buffers 5 | { 6 | static public class ReadOnlySequenceFromNetCore3 7 | { 8 | //FROM https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Buffers/ReadOnlySequence.cs 9 | 10 | internal static class ReadOnlySequence 11 | { 12 | public const int FlagBitMask = 1 << 31; 13 | public const int IndexBitMask = ~FlagBitMask; 14 | } 15 | 16 | /// 17 | /// Helper to efficiently prepare the 18 | /// 19 | /// The first span in the sequence. 20 | /// The next position. 21 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 22 | static internal void GetFirstSpan(this ReadOnlySequence sequence, out ReadOnlySpan first, out SequencePosition next) 23 | { 24 | first = default; 25 | next = default; 26 | SequencePosition start = sequence.Start; 27 | int startIndex = start.GetInteger(); 28 | object startObject = start.GetObject(); 29 | 30 | if (startObject != null) 31 | { 32 | SequencePosition end = sequence.End; 33 | int endIndex = end.GetInteger(); 34 | bool hasMultipleSegments = startObject != end.GetObject(); 35 | 36 | if (startIndex >= 0) 37 | { 38 | if (endIndex >= 0) 39 | { 40 | // Positive start and end index == ReadOnlySequenceSegment 41 | ReadOnlySequenceSegment segment = (ReadOnlySequenceSegment)startObject; 42 | next = new SequencePosition(segment.Next, 0); 43 | first = segment.Memory.Span; 44 | if (hasMultipleSegments) 45 | { 46 | first = first.Slice(startIndex); 47 | } 48 | else 49 | { 50 | first = first.Slice(startIndex, endIndex - startIndex); 51 | } 52 | } 53 | else 54 | { 55 | // Positive start and negative end index == T[] 56 | if (hasMultipleSegments) 57 | ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); 58 | 59 | first = new ReadOnlySpan((T[])startObject, startIndex, (endIndex & ReadOnlySequence.IndexBitMask) - startIndex); 60 | } 61 | } 62 | else 63 | { 64 | first = GetFirstSpanSlow(startObject, startIndex, endIndex, hasMultipleSegments); 65 | } 66 | } 67 | } 68 | 69 | 70 | //FROM https://github.com/dotnet/corefxlab/blob/master/src/System.Buffers.ReaderWriter/System/Buffers/Reader/BufferReader.cs 71 | 72 | private const int FlagBitMask = 1 << 31; 73 | private const int IndexBitMask = ~FlagBitMask; 74 | 75 | [MethodImpl(MethodImplOptions.NoInlining)] 76 | private static ReadOnlySpan GetFirstSpanSlow(object startObject, int startIndex, int endIndex, bool isMultiSegment) 77 | { 78 | Debug.Assert(startIndex < 0 || endIndex < 0); 79 | if (isMultiSegment) 80 | ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); 81 | 82 | // The type == char check here is redundant. However, we still have it to allow 83 | // the JIT to see when that the code is unreachable and eliminate it. 84 | // A == 1 && B == 1 means SequenceType.String 85 | if (typeof(T) == typeof(char) && endIndex < 0) 86 | { 87 | var memory = (ReadOnlyMemory)(object)((string)startObject).AsMemory(); 88 | 89 | // No need to remove the FlagBitMask since (endIndex - startIndex) == (endIndex & ReadOnlySequence.IndexBitMask) - (startIndex & ReadOnlySequence.IndexBitMask) 90 | return memory.Span.Slice(startIndex & IndexBitMask, endIndex - startIndex); 91 | } 92 | else // endIndex >= 0, A == 1 && B == 0 means SequenceType.MemoryManager 93 | { 94 | startIndex &= IndexBitMask; 95 | return ((MemoryManager)startObject).Memory.Span.Slice(startIndex, endIndex - startIndex); 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /RSocket.Core/System.Buffers/SequenceReader.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Diagnostics; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace System.Buffers 9 | { 10 | public ref partial struct SequenceReader where T : unmanaged, IEquatable 11 | { 12 | private SequencePosition _currentPosition; 13 | private SequencePosition _nextPosition; 14 | private bool _moreData; 15 | private long _length; 16 | 17 | /// 18 | /// Create a over the given . 19 | /// 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | public SequenceReader(ReadOnlySequence sequence) 22 | { 23 | CurrentSpanIndex = 0; 24 | Consumed = 0; 25 | Sequence = sequence; 26 | _currentPosition = sequence.Start; 27 | _length = -1; 28 | 29 | sequence.GetFirstSpan(out ReadOnlySpan first, out _nextPosition); 30 | CurrentSpan = first; 31 | _moreData = first.Length > 0; 32 | 33 | if (!_moreData && !sequence.IsSingleSegment) 34 | { 35 | _moreData = true; 36 | GetNextSpan(); 37 | } 38 | } 39 | 40 | /// 41 | /// True when there is no more data in the . 42 | /// 43 | public bool End => !_moreData; 44 | 45 | /// 46 | /// The underlying for the reader. 47 | /// 48 | public ReadOnlySequence Sequence { get; } 49 | 50 | /// 51 | /// The current position in the . 52 | /// 53 | public SequencePosition Position 54 | => Sequence.GetPosition(CurrentSpanIndex, _currentPosition); 55 | 56 | /// 57 | /// The current segment in the as a span. 58 | /// 59 | public ReadOnlySpan CurrentSpan { get; private set; } 60 | 61 | /// 62 | /// The index in the . 63 | /// 64 | public int CurrentSpanIndex { get; private set; } 65 | 66 | /// 67 | /// The unread portion of the . 68 | /// 69 | public ReadOnlySpan UnreadSpan 70 | { 71 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 72 | get => CurrentSpan.Slice(CurrentSpanIndex); 73 | } 74 | 75 | /// 76 | /// The total number of 's processed by the reader. 77 | /// 78 | public long Consumed { get; private set; } 79 | 80 | /// 81 | /// Remaining 's in the reader's . 82 | /// 83 | public long Remaining => Length - Consumed; 84 | 85 | /// 86 | /// Count of in the reader's . 87 | /// 88 | public long Length 89 | { 90 | get 91 | { 92 | if (_length < 0) 93 | { 94 | // Cache the length 95 | _length = Sequence.Length; 96 | } 97 | return _length; 98 | } 99 | } 100 | 101 | /// 102 | /// Peeks at the next value without advancing the reader. 103 | /// 104 | /// The next value or default if at the end. 105 | /// False if at the end of the reader. 106 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 107 | public bool TryPeek(out T value) 108 | { 109 | if (_moreData) 110 | { 111 | value = CurrentSpan[CurrentSpanIndex]; 112 | return true; 113 | } 114 | else 115 | { 116 | value = default; 117 | return false; 118 | } 119 | } 120 | 121 | /// 122 | /// Read the next value and advance the reader. 123 | /// 124 | /// The next value or default if at the end. 125 | /// False if at the end of the reader. 126 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 127 | public bool TryRead(out T value) 128 | { 129 | if (End) 130 | { 131 | value = default; 132 | return false; 133 | } 134 | 135 | value = CurrentSpan[CurrentSpanIndex]; 136 | CurrentSpanIndex++; 137 | Consumed++; 138 | 139 | if (CurrentSpanIndex >= CurrentSpan.Length) 140 | { 141 | GetNextSpan(); 142 | } 143 | 144 | return true; 145 | } 146 | 147 | /// 148 | /// Move the reader back the specified number of items. 149 | /// 150 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 151 | public void Rewind(long count) 152 | { 153 | if (count < 0) 154 | { 155 | ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count); 156 | } 157 | 158 | Consumed -= count; 159 | 160 | if (CurrentSpanIndex >= count) 161 | { 162 | CurrentSpanIndex -= (int)count; 163 | _moreData = true; 164 | } 165 | else 166 | { 167 | // Current segment doesn't have enough data, scan backward through segments 168 | RetreatToPreviousSpan(Consumed); 169 | } 170 | } 171 | 172 | [MethodImpl(MethodImplOptions.NoInlining)] 173 | private void RetreatToPreviousSpan(long consumed) 174 | { 175 | ResetReader(); 176 | Advance(consumed); 177 | } 178 | 179 | private void ResetReader() 180 | { 181 | CurrentSpanIndex = 0; 182 | Consumed = 0; 183 | _currentPosition = Sequence.Start; 184 | _nextPosition = _currentPosition; 185 | 186 | if (Sequence.TryGet(ref _nextPosition, out ReadOnlyMemory memory, advance: true)) 187 | { 188 | _moreData = true; 189 | 190 | if (memory.Length == 0) 191 | { 192 | CurrentSpan = default; 193 | // No data in the first span, move to one with data 194 | GetNextSpan(); 195 | } 196 | else 197 | { 198 | CurrentSpan = memory.Span; 199 | } 200 | } 201 | else 202 | { 203 | // No data in any spans and at end of sequence 204 | _moreData = false; 205 | CurrentSpan = default; 206 | } 207 | } 208 | 209 | /// 210 | /// Get the next segment with available data, if any. 211 | /// 212 | private void GetNextSpan() 213 | { 214 | if (!Sequence.IsSingleSegment) 215 | { 216 | SequencePosition previousNextPosition = _nextPosition; 217 | while (Sequence.TryGet(ref _nextPosition, out ReadOnlyMemory memory, advance: true)) 218 | { 219 | _currentPosition = previousNextPosition; 220 | if (memory.Length > 0) 221 | { 222 | CurrentSpan = memory.Span; 223 | CurrentSpanIndex = 0; 224 | return; 225 | } 226 | else 227 | { 228 | CurrentSpan = default; 229 | CurrentSpanIndex = 0; 230 | previousNextPosition = _nextPosition; 231 | } 232 | } 233 | } 234 | _moreData = false; 235 | } 236 | 237 | /// 238 | /// Move the reader ahead the specified number of items. 239 | /// 240 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 241 | public void Advance(long count) 242 | { 243 | const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000); 244 | if ((count & TooBigOrNegative) == 0 && CurrentSpan.Length - CurrentSpanIndex > (int)count) 245 | { 246 | CurrentSpanIndex += (int)count; 247 | Consumed += count; 248 | } 249 | else 250 | { 251 | // Can't satisfy from the current span 252 | AdvanceToNextSpan(count); 253 | } 254 | } 255 | 256 | /// 257 | /// Unchecked helper to avoid unnecessary checks where you know count is valid. 258 | /// 259 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 260 | internal void AdvanceCurrentSpan(long count) 261 | { 262 | Debug.Assert(count >= 0); 263 | 264 | Consumed += count; 265 | CurrentSpanIndex += (int)count; 266 | if (CurrentSpanIndex >= CurrentSpan.Length) 267 | GetNextSpan(); 268 | } 269 | 270 | /// 271 | /// Only call this helper if you know that you are advancing in the current span 272 | /// with valid count and there is no need to fetch the next one. 273 | /// 274 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 275 | internal void AdvanceWithinSpan(long count) 276 | { 277 | Debug.Assert(count >= 0); 278 | 279 | Consumed += count; 280 | CurrentSpanIndex += (int)count; 281 | 282 | Debug.Assert(CurrentSpanIndex < CurrentSpan.Length); 283 | } 284 | 285 | private void AdvanceToNextSpan(long count) 286 | { 287 | if (count < 0) 288 | { 289 | ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count); 290 | } 291 | 292 | Consumed += count; 293 | while (_moreData) 294 | { 295 | int remaining = CurrentSpan.Length - CurrentSpanIndex; 296 | 297 | if (remaining > count) 298 | { 299 | CurrentSpanIndex += (int)count; 300 | count = 0; 301 | break; 302 | } 303 | 304 | // As there may not be any further segments we need to 305 | // push the current index to the end of the span. 306 | CurrentSpanIndex += remaining; 307 | count -= remaining; 308 | Debug.Assert(count >= 0); 309 | 310 | GetNextSpan(); 311 | 312 | if (count == 0) 313 | { 314 | break; 315 | } 316 | } 317 | 318 | if (count != 0) 319 | { 320 | // Not enough data left- adjust for where we actually ended and throw 321 | Consumed -= count; 322 | ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count); 323 | } 324 | } 325 | 326 | /// 327 | /// Copies data from the current to the given span. 328 | /// 329 | /// Destination to copy to. 330 | /// True if there is enough data to copy to the . 331 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 332 | public bool TryCopyTo(Span destination) 333 | { 334 | ReadOnlySpan firstSpan = UnreadSpan; 335 | if (firstSpan.Length >= destination.Length) 336 | { 337 | firstSpan.Slice(0, destination.Length).CopyTo(destination); 338 | return true; 339 | } 340 | 341 | return TryCopyMultisegment(destination); 342 | } 343 | 344 | internal bool TryCopyMultisegment(Span destination) 345 | { 346 | if (Remaining < destination.Length) 347 | return false; 348 | 349 | ReadOnlySpan firstSpan = UnreadSpan; 350 | Debug.Assert(firstSpan.Length < destination.Length); 351 | firstSpan.CopyTo(destination); 352 | int copied = firstSpan.Length; 353 | 354 | SequencePosition next = _nextPosition; 355 | while (Sequence.TryGet(ref next, out ReadOnlyMemory nextSegment, true)) 356 | { 357 | if (nextSegment.Length > 0) 358 | { 359 | ReadOnlySpan nextSpan = nextSegment.Span; 360 | int toCopy = Math.Min(nextSpan.Length, destination.Length - copied); 361 | nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied)); 362 | copied += toCopy; 363 | if (copied >= destination.Length) 364 | { 365 | break; 366 | } 367 | } 368 | } 369 | 370 | return true; 371 | } 372 | } 373 | } -------------------------------------------------------------------------------- /RSocket.Core/System.Buffers/SequenceReaderExtensions.Binary(Unsigned).cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Buffers.Binary; 6 | using System.Diagnostics; 7 | using System.Runtime.CompilerServices; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace System.Buffers 11 | { 12 | public static partial class SequenceReaderExtensions 13 | { 14 | /// 15 | /// Reads an as little endian. 16 | /// 17 | /// False if there wasn't enough data for an . 18 | public static bool TryReadLittleEndian(ref this SequenceReader reader, out ushort value) 19 | { 20 | if (BitConverter.IsLittleEndian) 21 | { 22 | return reader.TryRead(out value); 23 | } 24 | 25 | return TryReadReverseEndianness(ref reader, out value); 26 | } 27 | 28 | /// 29 | /// Reads an as big endian. 30 | /// 31 | /// False if there wasn't enough data for an . 32 | public static bool TryReadBigEndian(ref this SequenceReader reader, out ushort value) 33 | { 34 | if (!BitConverter.IsLittleEndian) 35 | { 36 | return reader.TryRead(out value); 37 | } 38 | 39 | return TryReadReverseEndianness(ref reader, out value); 40 | } 41 | 42 | private static bool TryReadReverseEndianness(ref SequenceReader reader, out ushort value) 43 | { 44 | if (reader.TryRead(out value)) 45 | { 46 | value = BinaryPrimitives.ReverseEndianness(value); 47 | return true; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | /// 54 | /// Reads an as little endian. 55 | /// 56 | /// False if there wasn't enough data for an . 57 | public static bool TryReadLittleEndian(ref this SequenceReader reader, out uint value) 58 | { 59 | if (BitConverter.IsLittleEndian) 60 | { 61 | return reader.TryRead(out value); 62 | } 63 | 64 | return TryReadReverseEndianness(ref reader, out value); 65 | } 66 | 67 | /// 68 | /// Reads an as big endian. 69 | /// 70 | /// False if there wasn't enough data for an . 71 | public static bool TryReadBigEndian(ref this SequenceReader reader, out uint value) 72 | { 73 | if (!BitConverter.IsLittleEndian) 74 | { 75 | return reader.TryRead(out value); 76 | } 77 | 78 | return TryReadReverseEndianness(ref reader, out value); 79 | } 80 | 81 | private static bool TryReadReverseEndianness(ref SequenceReader reader, out uint value) 82 | { 83 | if (reader.TryRead(out value)) 84 | { 85 | value = BinaryPrimitives.ReverseEndianness(value); 86 | return true; 87 | } 88 | 89 | return false; 90 | } 91 | 92 | /// 93 | /// Reads an as little endian. 94 | /// 95 | /// False if there wasn't enough data for an . 96 | public static bool TryReadLittleEndian(ref this SequenceReader reader, out ulong value) 97 | { 98 | if (BitConverter.IsLittleEndian) 99 | { 100 | return reader.TryRead(out value); 101 | } 102 | 103 | return TryReadReverseEndianness(ref reader, out value); 104 | } 105 | 106 | /// 107 | /// Reads an as big endian. 108 | /// 109 | /// False if there wasn't enough data for an . 110 | public static bool TryReadBigEndian(ref this SequenceReader reader, out ulong value) 111 | { 112 | if (!BitConverter.IsLittleEndian) 113 | { 114 | return reader.TryRead(out value); 115 | } 116 | 117 | return TryReadReverseEndianness(ref reader, out value); 118 | } 119 | 120 | private static bool TryReadReverseEndianness(ref SequenceReader reader, out ulong value) 121 | { 122 | if (reader.TryRead(out value)) 123 | { 124 | value = BinaryPrimitives.ReverseEndianness(value); 125 | return true; 126 | } 127 | 128 | return false; 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /RSocket.Core/System.Buffers/SequenceReaderExtensions.Binary.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Buffers.Binary; 6 | using System.Diagnostics; 7 | using System.Runtime.CompilerServices; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace System.Buffers 11 | { 12 | public static partial class SequenceReaderExtensions 13 | { 14 | /// 15 | /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary 16 | /// structs- see remarks for full details. 17 | /// 18 | /// 19 | /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to 20 | /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit 21 | /// overloads such as 22 | /// 23 | /// 24 | /// True if successful. will be default if failed (due to lack of space). 25 | /// 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | internal static unsafe bool TryRead(ref this SequenceReader reader, out T value) where T : unmanaged 28 | { 29 | ReadOnlySpan span = reader.UnreadSpan; 30 | if (span.Length < sizeof(T)) 31 | return TryReadMultisegment(ref reader, out value); 32 | 33 | #if NETSTANDARD2_0 34 | value = MemoryMarshal.Read(span); //FROM https://github.com/dotnet/corefxlab/blob/138c21ab030710c4d9e31d6fab7e928215e3ecc5/src/System.Buffers.ReaderWriter/System/Buffers/Reader/BufferReader_binary.cs#L27 35 | #else 36 | //This was implemented in the mainline code for performance. Discussion here: https://github.com/dotnet/corefxlab/pull/2537 37 | value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span)); 38 | #endif 39 | reader.Advance(sizeof(T)); 40 | return true; 41 | } 42 | 43 | private static unsafe bool TryReadMultisegment(ref SequenceReader reader, out T value) where T : unmanaged 44 | { 45 | Debug.Assert(reader.UnreadSpan.Length < sizeof(T)); 46 | 47 | // Not enough data in the current segment, try to peek for the data we need. 48 | T buffer = default; 49 | Span tempSpan = new Span(&buffer, sizeof(T)); 50 | 51 | if (!reader.TryCopyTo(tempSpan)) 52 | { 53 | value = default; 54 | return false; 55 | } 56 | 57 | #if NETSTANDARD2_0 58 | value = MemoryMarshal.Read(tempSpan); //FROM https://github.com/dotnet/corefxlab/blob/138c21ab030710c4d9e31d6fab7e928215e3ecc5/src/System.Buffers.ReaderWriter/System/Buffers/Reader/BufferReader_binary.cs#L46 59 | #else 60 | value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan)); 61 | #endif 62 | reader.Advance(sizeof(T)); 63 | return true; 64 | } 65 | 66 | /// 67 | /// Reads an as little endian. 68 | /// 69 | /// False if there wasn't enough data for an . 70 | public static bool TryReadLittleEndian(ref this SequenceReader reader, out short value) 71 | { 72 | if (BitConverter.IsLittleEndian) 73 | { 74 | return reader.TryRead(out value); 75 | } 76 | 77 | return TryReadReverseEndianness(ref reader, out value); 78 | } 79 | 80 | /// 81 | /// Reads an as big endian. 82 | /// 83 | /// False if there wasn't enough data for an . 84 | public static bool TryReadBigEndian(ref this SequenceReader reader, out short value) 85 | { 86 | if (!BitConverter.IsLittleEndian) 87 | { 88 | return reader.TryRead(out value); 89 | } 90 | 91 | return TryReadReverseEndianness(ref reader, out value); 92 | } 93 | 94 | private static bool TryReadReverseEndianness(ref SequenceReader reader, out short value) 95 | { 96 | if (reader.TryRead(out value)) 97 | { 98 | value = BinaryPrimitives.ReverseEndianness(value); 99 | return true; 100 | } 101 | 102 | return false; 103 | } 104 | 105 | /// 106 | /// Reads an as little endian. 107 | /// 108 | /// False if there wasn't enough data for an . 109 | public static bool TryReadLittleEndian(ref this SequenceReader reader, out int value) 110 | { 111 | if (BitConverter.IsLittleEndian) 112 | { 113 | return reader.TryRead(out value); 114 | } 115 | 116 | return TryReadReverseEndianness(ref reader, out value); 117 | } 118 | 119 | /// 120 | /// Reads an as big endian. 121 | /// 122 | /// False if there wasn't enough data for an . 123 | public static bool TryReadBigEndian(ref this SequenceReader reader, out int value) 124 | { 125 | if (!BitConverter.IsLittleEndian) 126 | { 127 | return reader.TryRead(out value); 128 | } 129 | 130 | return TryReadReverseEndianness(ref reader, out value); 131 | } 132 | 133 | private static bool TryReadReverseEndianness(ref SequenceReader reader, out int value) 134 | { 135 | if (reader.TryRead(out value)) 136 | { 137 | value = BinaryPrimitives.ReverseEndianness(value); 138 | return true; 139 | } 140 | 141 | return false; 142 | } 143 | 144 | /// 145 | /// Reads an as little endian. 146 | /// 147 | /// False if there wasn't enough data for an . 148 | public static bool TryReadLittleEndian(ref this SequenceReader reader, out long value) 149 | { 150 | if (BitConverter.IsLittleEndian) 151 | { 152 | return reader.TryRead(out value); 153 | } 154 | 155 | return TryReadReverseEndianness(ref reader, out value); 156 | } 157 | 158 | /// 159 | /// Reads an as big endian. 160 | /// 161 | /// False if there wasn't enough data for an . 162 | public static bool TryReadBigEndian(ref this SequenceReader reader, out long value) 163 | { 164 | if (!BitConverter.IsLittleEndian) 165 | { 166 | return reader.TryRead(out value); 167 | } 168 | 169 | return TryReadReverseEndianness(ref reader, out value); 170 | } 171 | 172 | private static bool TryReadReverseEndianness(ref SequenceReader reader, out long value) 173 | { 174 | if (reader.TryRead(out value)) 175 | { 176 | value = BinaryPrimitives.ReverseEndianness(value); 177 | return true; 178 | } 179 | 180 | return false; 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /RSocket.Core/System.Buffers/SequenceReaderExtensions.Custom.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Buffers.Binary; 6 | using System.Diagnostics; 7 | using System.Runtime.CompilerServices; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | 11 | namespace System.Buffers 12 | { 13 | public static partial class SequenceReaderExtensions 14 | { 15 | //[MethodImpl(MethodImplOptions.AggressiveInlining)] static bool TryFail(out string value) { value = default; return false; } 16 | [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool Tried(bool tried, out string value, string from) { value = from; return tried; } 17 | [MethodImpl(MethodImplOptions.AggressiveInlining)] static TResult Undo(this ref SequenceReader reader, long count, TResult result) where T : unmanaged, IEquatable { reader.Rewind(count); return result; } 18 | 19 | /// Attempts to read a Span from a sequence. 20 | /// The reader to extract the Span from; the reader will advance. 21 | /// The destination Span. 22 | /// True if the sequence had enough to fill the span. 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | static public bool TryRead(this ref SequenceReader reader, Span destination) where T : unmanaged, IEquatable { if (reader.TryCopyTo(destination)) { reader.Advance(destination.Length); return true; } else { return false; } } 25 | 26 | /// Attempt to read a byte-prefixed string from a sequence. 27 | /// The reader to extract the string from; the reader will advance. 28 | /// The resulting value. 29 | /// The Encoding of the string bytes. Defaults to ASCII. 30 | /// True if the sequence had enough to fill the string. 31 | public static bool TryReadPrefix(ref this SequenceReader reader, out string value, Encoding encoding = default) => reader.TryRead(out byte length) ? TryRead(ref reader, out value, length, encoding) : Undo(ref reader, 1, Tried(false, out value, default)); 32 | 33 | //TODO DOCS 34 | public static unsafe bool TryRead(ref this SequenceReader reader, out string value, byte length, Encoding encoding = default) 35 | { 36 | var backing = stackalloc byte[length]; 37 | return reader.TryRead(new Span(backing, length)) ? Tried(true, out value, (encoding ?? Encoding.ASCII).GetString(backing, length)) : Tried(false, out value, default); 38 | } 39 | 40 | //TODO DOCS 41 | public static unsafe bool TryRead(ref this SequenceReader reader, out string value, int length, Encoding encoding = default) 42 | { 43 | var backing = new byte[length]; 44 | return reader.TryRead(new Span(backing)) ? Tried(true, out value, (encoding ?? Encoding.UTF8).GetString(backing)) : Tried(false, out value, default); 45 | } 46 | 47 | /// 48 | /// Reads an as big endian; 49 | /// 50 | /// False if there wasn't enough data for an . 51 | public static bool TryReadUInt24BigEndian(ref this SequenceReader reader, out int value) 52 | { 53 | var span = new Span(new byte[sizeof(UInt32) - 1]); 54 | if (!reader.TryRead(span)) { value = default; return false; } 55 | else { value = span[0] << 16 | span[1] << 8 | span[2]; return true; } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /RSocket.Core/System.Buffers/ThrowHelper.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Runtime.CompilerServices; 6 | using System.Buffers; 7 | 8 | namespace System 9 | { 10 | // 11 | // This pattern of easily inlinable "void Throw" routines that stack on top of NoInlining factory methods 12 | // is a compromise between older JITs and newer JITs (RyuJIT in Core CLR 1.1.0+ and desktop CLR in 4.6.3+). 13 | // This package is explicitly targeted at older JITs as newer runtimes expect to implement Span intrinsically for 14 | // best performance. 15 | // 16 | // The aim of this pattern is three-fold 17 | // 1. Extracting the throw makes the method preforming the throw in a conditional branch smaller and more inlinable 18 | // 2. Extracting the throw from generic method to non-generic method reduces the repeated codegen size for value types 19 | // 3a. Newer JITs will not inline the methods that only throw and also recognise them, move the call to cold section 20 | // and not add stack prep and unwind before calling https://github.com/dotnet/coreclr/pull/6103 21 | // 3b. Older JITs will inline the throw itself and move to cold section; but not inline the non-inlinable exception 22 | // factory methods - still maintaining advantages 1 & 2 23 | // 24 | 25 | internal static class ThrowHelper 26 | { 27 | internal static void ThrowArgumentNullException(ExceptionArgument argument) { throw CreateArgumentNullException(argument); } 28 | [MethodImpl(MethodImplOptions.NoInlining)] 29 | private static Exception CreateArgumentNullException(ExceptionArgument argument) { return new ArgumentNullException(argument.ToString()); } 30 | 31 | internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) { throw CreateArgumentOutOfRangeException(argument); } 32 | [MethodImpl(MethodImplOptions.NoInlining)] 33 | private static Exception CreateArgumentOutOfRangeException(ExceptionArgument argument) { return new ArgumentOutOfRangeException(argument.ToString()); } 34 | 35 | internal static void ThrowInvalidOperationException() { throw CreateInvalidOperationException(); } 36 | [MethodImpl(MethodImplOptions.NoInlining)] 37 | private static Exception CreateInvalidOperationException() { return new InvalidOperationException(); } 38 | 39 | internal static void ThrowInvalidOperationException_EndPositionNotReached() { throw CreateInvalidOperationException_EndPositionNotReached(); } 40 | [MethodImpl(MethodImplOptions.NoInlining)] 41 | private static Exception CreateInvalidOperationException_EndPositionNotReached() { return new InvalidOperationException(); } 42 | 43 | internal static void ThrowArgumentOutOfRangeException_PositionOutOfRange() { throw CreateArgumentOutOfRangeException_PositionOutOfRange(); } 44 | [MethodImpl(MethodImplOptions.NoInlining)] 45 | private static Exception CreateArgumentOutOfRangeException_PositionOutOfRange() { return new ArgumentOutOfRangeException("position"); } 46 | 47 | internal static void ThrowArgumentOutOfRangeException_OffsetOutOfRange() { throw CreateArgumentOutOfRangeException_OffsetOutOfRange(); } 48 | [MethodImpl(MethodImplOptions.NoInlining)] 49 | private static Exception CreateArgumentOutOfRangeException_OffsetOutOfRange() { return new ArgumentOutOfRangeException(nameof(ExceptionArgument.offset)); } 50 | 51 | internal static void ThrowObjectDisposedException_ArrayMemoryPoolBuffer() { throw CreateObjectDisposedException_ArrayMemoryPoolBuffer(); } 52 | [MethodImpl(MethodImplOptions.NoInlining)] 53 | private static Exception CreateObjectDisposedException_ArrayMemoryPoolBuffer() { return new ObjectDisposedException("ArrayMemoryPoolBuffer"); } 54 | 55 | // 56 | // ReadOnlySequence .ctor validation Throws coalesced to enable inlining of the .ctor 57 | // 58 | public static void ThrowArgumentValidationException(ReadOnlySequenceSegment startSegment, int startIndex, ReadOnlySequenceSegment endSegment) 59 | => throw CreateArgumentValidationException(startSegment, startIndex, endSegment); 60 | 61 | private static Exception CreateArgumentValidationException(ReadOnlySequenceSegment startSegment, int startIndex, ReadOnlySequenceSegment endSegment) 62 | { 63 | if (startSegment == null) 64 | return CreateArgumentNullException(ExceptionArgument.startSegment); 65 | else if (endSegment == null) 66 | return CreateArgumentNullException(ExceptionArgument.endSegment); 67 | else if (startSegment != endSegment && startSegment.RunningIndex > endSegment.RunningIndex) 68 | return CreateArgumentOutOfRangeException(ExceptionArgument.endSegment); 69 | else if ((uint)startSegment.Memory.Length < (uint)startIndex) 70 | return CreateArgumentOutOfRangeException(ExceptionArgument.startIndex); 71 | else 72 | return CreateArgumentOutOfRangeException(ExceptionArgument.endIndex); 73 | } 74 | 75 | public static void ThrowArgumentValidationException(Array array, int start) 76 | => throw CreateArgumentValidationException(array, start); 77 | 78 | private static Exception CreateArgumentValidationException(Array array, int start) 79 | { 80 | if (array == null) 81 | return CreateArgumentNullException(ExceptionArgument.array); 82 | else if ((uint)start > (uint)array.Length) 83 | return CreateArgumentOutOfRangeException(ExceptionArgument.start); 84 | else 85 | return CreateArgumentOutOfRangeException(ExceptionArgument.length); 86 | } 87 | 88 | // 89 | // ReadOnlySequence Slice validation Throws coalesced to enable inlining of the Slice 90 | // 91 | public static void ThrowStartOrEndArgumentValidationException(long start) 92 | => throw CreateStartOrEndArgumentValidationException(start); 93 | 94 | private static Exception CreateStartOrEndArgumentValidationException(long start) 95 | { 96 | if (start < 0) 97 | return CreateArgumentOutOfRangeException(ExceptionArgument.start); 98 | return CreateArgumentOutOfRangeException(ExceptionArgument.length); 99 | } 100 | 101 | } 102 | 103 | // 104 | // The convention for this enum is using the argument name as the enum name 105 | // 106 | internal enum ExceptionArgument 107 | { 108 | length, 109 | start, 110 | minimumBufferSize, 111 | elementIndex, 112 | comparable, 113 | comparer, 114 | destination, 115 | offset, 116 | startSegment, 117 | endSegment, 118 | startIndex, 119 | endIndex, 120 | array, 121 | culture, 122 | manager, 123 | count 124 | } 125 | } -------------------------------------------------------------------------------- /RSocket.Core/Transports/DuplexPipe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Pipelines; 4 | using System.Text; 5 | 6 | namespace RSocket.Transports 7 | { 8 | //IDuplexPipe is defined in System.IO.Pipelines, but they provide no default implementation!!! So something like the below occurs all over the place: SignalR, etc. Also, Input and Output are awful names, but I agree they're hard to name. 9 | 10 | // BACK // 11 | // output input // 12 | // --------------- // 13 | // writer reader // 14 | // || /\ // 15 | // || || // 16 | // || || // 17 | // || || // 18 | // \/ || // 19 | // reader writer // 20 | // --------------- // 21 | // input output // 22 | // FRONT // 23 | 24 | public class DuplexPipe : IDuplexPipe 25 | { 26 | public PipeReader Input { get; } 27 | public PipeWriter Output { get; } 28 | 29 | public DuplexPipe(PipeWriter writer, PipeReader reader) { Input = reader; Output = writer; } 30 | 31 | public static (IDuplexPipe Front, IDuplexPipe Back) CreatePair(PipeOptions fronttobackOptions, PipeOptions backtofrontOptions) 32 | { 33 | Pipe FrontToBack = new Pipe(backtofrontOptions ?? DefaultOptions), BackToFront = new Pipe(fronttobackOptions ?? DefaultOptions); 34 | return (new DuplexPipe(FrontToBack.Writer, BackToFront.Reader), new DuplexPipe(BackToFront.Writer, FrontToBack.Reader)); 35 | } 36 | 37 | public static readonly PipeOptions DefaultOptions = new PipeOptions(writerScheduler: PipeScheduler.ThreadPool, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false, pauseWriterThreshold: 0, resumeWriterThreshold: 0); 38 | public static readonly PipeOptions ImmediateOptions = new PipeOptions(writerScheduler: PipeScheduler.Inline, readerScheduler: PipeScheduler.Inline, useSynchronizationContext: true, pauseWriterThreshold: 0, resumeWriterThreshold: 0); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /RSocket.Core/Transports/LoopbackTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Pipelines; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace RSocket.Transports 9 | { 10 | public class LoopbackTransport : IRSocketTransport, IRSocketServerTransport 11 | { 12 | IDuplexPipe Front, Back; 13 | public PipeReader Input => Front.Input; 14 | public PipeWriter Output => Front.Output; 15 | //public IRSocketServerTransport Server => this; 16 | PipeReader IRSocketServerTransport.Input => Back.Input; 17 | PipeWriter IRSocketServerTransport.Output => Back.Output; 18 | 19 | public LoopbackTransport(PipeOptions inputoptions = default, PipeOptions outputoptions = default) 20 | { 21 | (Back, Front) = DuplexPipe.CreatePair(inputoptions, outputoptions); 22 | } 23 | 24 | //public Task ConnectAsync(CancellationToken cancel = default) => Task.CompletedTask; //This is a noop because they are already connected. 25 | 26 | public Task StartAsync(CancellationToken cancel = default) => Task.CompletedTask; //This is a noop because they are already connected. 27 | public Task StopAsync() => Task.CompletedTask; 28 | 29 | public IRSocketTransport Beyond => new ServerTransport(this); //TODO Maybe not Server? Backside? Otherside? 30 | 31 | struct ServerTransport : IRSocketTransport 32 | { 33 | IRSocketServerTransport Transport; 34 | public ServerTransport(IRSocketServerTransport transport) { Transport = transport; } 35 | public PipeReader Input => Transport.Input; 36 | public PipeWriter Output => Transport.Output; 37 | public Task StartAsync(CancellationToken cancel = default) => Transport.StartAsync(cancel); 38 | public Task StopAsync() => Transport.StopAsync(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RSocket.Core/Transports/RSocketTransportExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.IO.Pipelines; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace RSocket.Transports 8 | { 9 | using System.Net.Sockets; 10 | using System.Net.WebSockets; 11 | 12 | internal static partial class RSocketTransportExtensions 13 | { 14 | //Framing helper methods. They stay close to the original code as called, but add and remove the framing at the transport-socket boundary. 15 | public static Memory GetMemory(this PipeWriter output, out Memory memoryframe, bool haslength = false) { memoryframe = output.GetMemory(); return haslength ? memoryframe : memoryframe.Slice(RSocketProtocol.MESSAGEFRAMESIZE); } 16 | public static void Advance(this PipeWriter output, int bytes, bool endOfMessage, in Memory memoryframe) { RSocketProtocol.MessageFrameWrite(bytes, endOfMessage, memoryframe.Span); output.Advance(RSocketProtocol.MESSAGEFRAMESIZE + bytes); } 17 | static (int Length, bool IsEndOfMessage) PeekFrame(ReadOnlySequence sequence) { var reader = new SequenceReader(sequence); return reader.TryRead(out byte b1) && reader.TryRead(out byte b2) && reader.TryRead(out byte b3) ? ((b1 << 8 * 2) | (b2 << 8 * 1) | (b3 << 8 * 0), true) : (0, false); } 18 | 19 | public static async ValueTask SendAsync(this WebSocket socket, ReadOnlySequence buffer, SequencePosition position, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken = default) 20 | { 21 | for (var frame = PeekFrame(buffer.Slice(position)); frame.Length > 0; frame = PeekFrame(buffer.Slice(position))) 22 | { 23 | //Console.WriteLine($"Send Frame[{frame.Length}]"); 24 | var offset = buffer.GetPosition(RSocketProtocol.MESSAGEFRAMESIZE, position); 25 | if (buffer.Slice(offset).Length < frame.Length) { break; } //If there is a partial message in the buffer, yield to accumulate more. Can't compare SequencePositions... 26 | await socket.SendAsync(buffer.Slice(offset, frame.Length), webSocketMessageType, cancellationToken); 27 | position = buffer.GetPosition(frame.Length, offset); 28 | } 29 | 30 | //TODO Length may actually be on a boundary... Should use a reader. 31 | //while (buffer.TryGet(ref position, out var memory, advance: false)) 32 | //{ 33 | // var (length, isEndOfMessage) = RSocketProtocol.MessageFrame(System.Buffers.Binary.BinaryPrimitives.ReadInt32BigEndian(memory.Span)); 34 | // var offset = buffer.GetPosition(sizeof(int), position); 35 | // if (buffer.Slice(offset).Length < length) { break; } //If there is a partial message in the buffer, yield to accumulate more. 36 | // await socket.SendAsync(buffer.Slice(offset, length), webSocketMessageType, cancellationToken); 37 | // position = buffer.GetPosition(length, offset); 38 | //} 39 | return position; 40 | } 41 | 42 | public static async ValueTask SendAsync(this Socket socket, ReadOnlySequence buffer, SequencePosition position, SocketFlags socketFlags, CancellationToken cancellationToken = default) 43 | { 44 | for (var frame = PeekFrame(buffer.Slice(position)); frame.Length > 0; frame = PeekFrame(buffer.Slice(position))) 45 | { 46 | //Console.WriteLine($"Send Frame[{frame.Length}]"); 47 | var length = frame.Length + RSocketProtocol.FRAMELENGTHSIZE; 48 | var offset = buffer.GetPosition(RSocketProtocol.MESSAGEFRAMESIZE - RSocketProtocol.FRAMELENGTHSIZE, position); 49 | if (buffer.Slice(offset).Length < length) { break; } //If there is a partial message in the buffer, yield to accumulate more. Can't compare SequencePositions... 50 | await socket.SendAsync(buffer.Slice(offset, length), socketFlags, cancellationToken); 51 | position = buffer.GetPosition(length, offset); 52 | } 53 | return position; 54 | 55 | //buffer.TryGet(ref position, out var memory, advance: false); 56 | //var (length, isEndOfMessage) = RSocketProtocol.MessageFrame(System.Buffers.Binary.BinaryPrimitives.ReadInt32BigEndian(memory.Span)); 57 | //position = buffer.GetPosition(1, position); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RSocket.Core/Transports/SocketTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Collections.Generic; 4 | using System.IO.Pipelines; 5 | using System.Net.Sockets; 6 | using System.Runtime.InteropServices; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Extensions.Logging; 10 | using System.Diagnostics; 11 | using System.Buffers; 12 | 13 | namespace RSocket.Transports 14 | { 15 | //TODO Readd transport logging - worth it during debugging. 16 | public class SocketTransport : IRSocketTransport 17 | { 18 | private IPEndPoint Endpoint; 19 | private Socket Socket; 20 | 21 | internal Task Running { get; private set; } = Task.CompletedTask; 22 | //private CancellationTokenSource Cancellation; 23 | #pragma warning disable CS0649 24 | private volatile bool Aborted; //TODO Implement cooperative cancellation (and remove warning suppression) 25 | #pragma warning restore CS0649 26 | 27 | public Uri Url { get; private set; } 28 | private LoggerFactory Logger; 29 | 30 | IDuplexPipe Front, Back; 31 | public PipeReader Input => Front.Input; 32 | public PipeWriter Output => Front.Output; 33 | 34 | public SocketTransport(string url, PipeOptions outputoptions = default, PipeOptions inputoptions = default) : this(new Uri(url), outputoptions, inputoptions) { } 35 | public SocketTransport(Uri url, PipeOptions outputoptions = default, PipeOptions inputoptions = default, WebSocketOptions options = default) 36 | { 37 | Url = url; 38 | if (string.Compare(url.Scheme, "TCP", true) != 0) { throw new ArgumentException("Only TCP connections are supported.", nameof(Url)); } 39 | if (url.Port == -1) { throw new ArgumentException("TCP Port must be specified.", nameof(Url)); } 40 | 41 | //Options = options ?? WebSocketsTransport.DefaultWebSocketOptions; 42 | Logger = new Microsoft.Extensions.Logging.LoggerFactory(new[] { new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider() }); 43 | (Front, Back) = DuplexPipe.CreatePair(outputoptions, inputoptions); 44 | } 45 | 46 | public async Task StartAsync(CancellationToken cancel = default) 47 | { 48 | var dns = await Dns.GetHostEntryAsync(Url.Host); 49 | if (dns.AddressList.Length == 0) { throw new InvalidOperationException($"Unable to resolve address."); } 50 | Endpoint = new IPEndPoint(dns.AddressList[0], Url.Port); 51 | 52 | Socket = new Socket(Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 53 | Socket.Connect(dns.AddressList, Url.Port); //TODO Would like this to be async... Why so serious??? 54 | 55 | Running = ProcessSocketAsync(Socket); 56 | } 57 | 58 | public Task StopAsync() => Task.CompletedTask; //TODO More graceful shutdown 59 | 60 | private async Task ProcessSocketAsync(Socket socket) 61 | { 62 | // Begin sending and receiving. Receiving must be started first because ExecuteAsync enables SendAsync. 63 | var receiving = StartReceiving(socket); 64 | var sending = StartSending(socket); 65 | 66 | var trigger = await Task.WhenAny(receiving, sending); 67 | 68 | //if (trigger == receiving) 69 | //{ 70 | // Log.WaitingForSend(_logger); 71 | 72 | // // We're waiting for the application to finish and there are 2 things it could be doing 73 | // // 1. Waiting for application data 74 | // // 2. Waiting for a websocket send to complete 75 | 76 | // // Cancel the application so that ReadAsync yields 77 | // _application.Input.CancelPendingRead(); 78 | 79 | // using (var delayCts = new CancellationTokenSource()) 80 | // { 81 | // var resultTask = await Task.WhenAny(sending, Task.Delay(_options.CloseTimeout, delayCts.Token)); 82 | 83 | // if (resultTask != sending) 84 | // { 85 | // // We timed out so now we're in ungraceful shutdown mode 86 | // Log.CloseTimedOut(_logger); 87 | 88 | // // Abort the websocket if we're stuck in a pending send to the client 89 | // _aborted = true; 90 | 91 | // socket.Abort(); 92 | // } 93 | // else 94 | // { 95 | // delayCts.Cancel(); 96 | // } 97 | // } 98 | //} 99 | //else 100 | //{ 101 | // Log.WaitingForClose(_logger); 102 | 103 | // // We're waiting on the websocket to close and there are 2 things it could be doing 104 | // // 1. Waiting for websocket data 105 | // // 2. Waiting on a flush to complete (backpressure being applied) 106 | 107 | // using (var delayCts = new CancellationTokenSource()) 108 | // { 109 | // var resultTask = await Task.WhenAny(receiving, Task.Delay(_options.CloseTimeout, delayCts.Token)); 110 | 111 | // if (resultTask != receiving) 112 | // { 113 | // // Abort the websocket if we're stuck in a pending receive from the client 114 | // _aborted = true; 115 | 116 | // socket.Abort(); 117 | 118 | // // Cancel any pending flush so that we can quit 119 | // _application.Output.CancelPendingFlush(); 120 | // } 121 | // else 122 | // { 123 | // delayCts.Cancel(); 124 | // } 125 | // } 126 | //} 127 | } 128 | 129 | 130 | private async Task StartReceiving(Socket socket) 131 | { 132 | var token = default(CancellationToken); //Cancellation?.Token ?? default; 133 | 134 | try 135 | { 136 | while (!token.IsCancellationRequested) 137 | { 138 | #if NETCOREAPP3_0 139 | // Do a 0 byte read so that idle connections don't allocate a buffer when waiting for a read 140 | var received = await socket.ReceiveAsync(Memory.Empty, token); 141 | if(received == 0) { continue; } 142 | var memory = Back.Output.GetMemory(out var memoryframe, haslength: true); //RSOCKET Framing 143 | var received = await socket.ReceiveAsync(memory, token); 144 | #else 145 | var memory = Back.Output.GetMemory(out var memoryframe, haslength: true); //RSOCKET Framing 146 | var isArray = MemoryMarshal.TryGetArray(memory, out var arraySegment); Debug.Assert(isArray); 147 | var received = await socket.ReceiveAsync(arraySegment, SocketFlags.None); //TODO Cancellation? 148 | #endif 149 | //Log.MessageReceived(_logger, receive.MessageType, receive.Count, receive.EndOfMessage); 150 | Back.Output.Advance(received); 151 | var flushResult = await Back.Output.FlushAsync(); 152 | if (flushResult.IsCanceled || flushResult.IsCompleted) { break; } 153 | } 154 | } 155 | //catch (SocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) 156 | //{ 157 | // // Client has closed the WebSocket connection without completing the close handshake 158 | // Log.ClosedPrematurely(_logger, ex); 159 | //} 160 | catch (OperationCanceledException) 161 | { 162 | // Ignore aborts, don't treat them like transport errors 163 | } 164 | catch (Exception ex) 165 | { 166 | if (!Aborted && !token.IsCancellationRequested) { Back.Output.Complete(ex); throw; } 167 | } 168 | finally { Back.Output.Complete(); } 169 | } 170 | 171 | 172 | private async Task StartSending(Socket socket) 173 | { 174 | Exception error = null; 175 | 176 | try 177 | { 178 | while (true) 179 | { 180 | var result = await Back.Input.ReadAsync(); 181 | var buffer = result.Buffer; 182 | var consumed = buffer.Start; //RSOCKET Framing 183 | 184 | try 185 | { 186 | if (result.IsCanceled) { break; } 187 | if (!buffer.IsEmpty) 188 | { 189 | try 190 | { 191 | //Log.SendPayload(_logger, buffer.Length); 192 | consumed = await socket.SendAsync(buffer, buffer.Start, SocketFlags.None); //RSOCKET Framing 193 | } 194 | catch (Exception) 195 | { 196 | if (!Aborted) { /*Log.ErrorWritingFrame(_logger, ex);*/ } 197 | break; 198 | } 199 | } 200 | else if (result.IsCompleted) { break; } 201 | } 202 | finally 203 | { 204 | Back.Input.AdvanceTo(consumed, buffer.End); //RSOCKET Framing 205 | } 206 | } 207 | } 208 | catch (Exception ex) 209 | { 210 | error = ex; 211 | } 212 | finally 213 | { 214 | //// Send the close frame before calling into user code 215 | //if (WebSocketCanSend(socket)) 216 | //{ 217 | // // We're done sending, send the close frame to the client if the websocket is still open 218 | // await socket.CloseOutputAsync(error != null ? WebSocketCloseStatus.InternalServerError : WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); 219 | //} 220 | Back.Input.Complete(); 221 | } 222 | 223 | } 224 | } 225 | } 226 | 227 | 228 | namespace System.Net.Sockets 229 | { 230 | internal static class SocketExtensions 231 | { 232 | public static ValueTask SendAsync(this Socket socket, ReadOnlySequence buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) 233 | { 234 | #if NETCOREAPP3_0 235 | if (buffer.IsSingleSegment) 236 | { 237 | return socket.SendAsync(buffer.First, webSocketMessageType, endOfMessage: true, cancellationToken); 238 | } 239 | else { return SendMultiSegmentAsync(socket, buffer, socketFlags, cancellationToken); } 240 | #else 241 | if (buffer.IsSingleSegment) 242 | { 243 | var isArray = MemoryMarshal.TryGetArray(buffer.First, out var segment); 244 | Debug.Assert(isArray); 245 | return new ValueTask(socket.SendAsync(segment, socketFlags)); //TODO Cancellation? 246 | } 247 | else { return SendMultiSegmentAsync(socket, buffer, socketFlags, cancellationToken); } 248 | #endif 249 | } 250 | 251 | static async ValueTask SendMultiSegmentAsync(Socket socket, ReadOnlySequence buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) 252 | { 253 | #if NETCOREAPP3_0 254 | var position = buffer.Start; 255 | buffer.TryGet(ref position, out var prevSegment); 256 | while (buffer.TryGet(ref position, out var segment)) 257 | { 258 | await socket.SendAsync(prevSegment, socketFlags); 259 | prevSegment = segment; 260 | } 261 | await socket.SendAsync(prevSegment, socketFlags); 262 | #else 263 | var position = buffer.Start; 264 | buffer.TryGet(ref position, out var prevSegment); 265 | while (buffer.TryGet(ref position, out var segment)) 266 | { 267 | var isArray = MemoryMarshal.TryGetArray(prevSegment, out var arraySegment); 268 | Debug.Assert(isArray); 269 | await socket.SendAsync(arraySegment, socketFlags); 270 | prevSegment = segment; 271 | } 272 | var isArrayEnd = MemoryMarshal.TryGetArray(prevSegment, out var arraySegmentEnd); 273 | Debug.Assert(isArrayEnd); 274 | await socket.SendAsync(arraySegmentEnd, socketFlags); 275 | #endif 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /RSocket.Core/Transports/WebSocketTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Pipelines; 4 | using System.Net.WebSockets; 5 | using System.Runtime.InteropServices; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.Extensions.Logging; 9 | using System.Diagnostics; 10 | using System.Buffers; 11 | 12 | namespace RSocket.Transports 13 | { 14 | public class WebSocketTransport : IRSocketTransport 15 | { 16 | private readonly WebSocketOptions Options; 17 | private readonly WebSocketsTransport Transport; 18 | public Uri Url { get; private set; } 19 | private LoggerFactory Logger; 20 | internal Task Running { get; private set; } = Task.CompletedTask; 21 | 22 | IDuplexPipe Front, Back; 23 | public PipeReader Input => Front.Input; 24 | public PipeWriter Output => Front.Output; 25 | 26 | public WebSocketTransport(string url, PipeOptions outputoptions = default, PipeOptions inputoptions = default) : this(new Uri(url), outputoptions, inputoptions) { } 27 | public WebSocketTransport(Uri url, PipeOptions outputoptions = default, PipeOptions inputoptions = default, WebSocketOptions options = default) 28 | { 29 | Url = url; 30 | Options = options ?? WebSocketsTransport.DefaultWebSocketOptions; 31 | Logger = new Microsoft.Extensions.Logging.LoggerFactory(new[] { new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider() }); 32 | (Front, Back) = DuplexPipe.CreatePair(outputoptions, inputoptions); 33 | Transport = new WebSocketsTransport(Options, Back, WebSocketsTransport.HttpConnectionContext.Default, Logger); 34 | } 35 | 36 | public Task StartAsync(CancellationToken cancel = default) 37 | { 38 | Running = Transport.ProcessRequestAsync(new WebSocketsTransport.HttpContext(this), cancel); 39 | return Task.CompletedTask; 40 | } 41 | 42 | public Task StopAsync() => Task.CompletedTask; //TODO More graceful shutdown 43 | 44 | 45 | //This class is based heavily on the SignalR WebSocketsTransport class from the AspNetCore project. It was merged as release/2.2 as of this snapshot. 46 | //When designing Pipelines, for some reason, standard adapters for Sockets, WebSockets, etc were not published. 47 | //The SignalR team seems to be the driver for Pipelines, so it's expected that this code is the best implementation available. 48 | //Future updaters should review their progress here and switch to a standard WebSockets => Pipelines adapter when possible. 49 | 50 | //The verbatim copyright messages are below even though the code has been altered. The license is below as well. 51 | 52 | //Adapters for keeping the SignalR source as close to verbatim as possible. The SignalR code is a little junky here because it takes deeper dependencies than needed - most of this should be extracted in the constructor, but it's saved as a heavy reference only to be consumed once. 53 | public interface IHttpTransport { } 54 | partial class WebSocketsTransport 55 | { 56 | static public readonly WebSocketOptions DefaultWebSocketOptions = new WebSocketOptions(); 57 | 58 | public enum TransferFormat { Binary = 1, Text = 2, } 59 | public class HttpConnectionContext 60 | { 61 | static public readonly HttpConnectionContext Default = new HttpConnectionContext(); 62 | public readonly CancellationTokenSource Cancellation = default; 63 | public readonly TransferFormat ActiveFormat = TransferFormat.Binary; 64 | } 65 | 66 | public class HttpContext //https://github.com/aspnet/AspNetCore/blob/master/src/Http/Http.Abstractions/src/HttpContext.cs 67 | { 68 | public readonly CancellationTokenSource Cancellation = default; 69 | public WebSocketManager WebSockets { get; } 70 | 71 | public HttpContext(WebSocketTransport transport, CancellationToken cancel = default) { WebSockets = new WebSocketManager(transport, cancel); } 72 | 73 | public class WebSocketManager //https://github.com/aspnet/AspNetCore/blob/master/src/Http/Http/src/Internal/DefaultWebSocketManager.cs 74 | { 75 | public WebSocketTransport Transport; 76 | public CancellationToken Cancel; 77 | public bool IsWebSocketRequest => true; 78 | public IList WebSocketRequestedProtocols => null; 79 | 80 | public WebSocketManager(WebSocketTransport transport, CancellationToken cancel = default) { Transport = transport; Cancel = cancel; } 81 | 82 | public async Task AcceptWebSocketAsync(string subprotocol, IDictionary headers) //https://github.com/aspnet/AspNetCore/blob/master/src/Middleware/WebSockets/src/WebSocketMiddleware.cs 83 | { 84 | //So in the SignalR code, this is where the WebSocketOptions are actually applied. So junky, so overabstracted. This is why the constructors are inverted so short out all of this madness. 85 | var socket = new ClientWebSocket(); 86 | foreach (string key in headers.Keys) 87 | { 88 | socket.Options.SetRequestHeader(key, headers[key]); 89 | } 90 | 91 | if (subprotocol != null) 92 | { 93 | socket.Options.AddSubProtocol(subprotocol); 94 | } 95 | 96 | await socket.ConnectAsync(Transport.Url, Cancel); 97 | return socket; 98 | } 99 | } 100 | } 101 | } 102 | 103 | 104 | 105 | #region https://github.com/aspnet/AspNetCore/blob/master/src/SignalR/common/Http.Connections/src/Internal/Transports/WebSocketsTransport.cs 106 | 107 | // Copyright (c) .NET Foundation. All rights reserved. 108 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 109 | 110 | //using System; 111 | //using System.Diagnostics; 112 | //using System.IO.Pipelines; 113 | //using System.Net.WebSockets; 114 | //using System.Runtime.InteropServices; 115 | //using System.Threading; 116 | //using System.Threading.Tasks; 117 | //using Microsoft.AspNetCore.Http; 118 | //using Microsoft.AspNetCore.Connections; 119 | //using Microsoft.Extensions.Logging; 120 | 121 | public partial class WebSocketsTransport : IHttpTransport 122 | { 123 | private readonly WebSocketOptions _options; 124 | private readonly ILogger _logger; 125 | private readonly IDuplexPipe _application; 126 | private readonly HttpConnectionContext _connection; 127 | private volatile bool _aborted; 128 | 129 | public WebSocketsTransport(WebSocketOptions options, IDuplexPipe application, HttpConnectionContext connection, ILoggerFactory loggerFactory) 130 | { 131 | if (options == null) 132 | { 133 | throw new ArgumentNullException(nameof(options)); 134 | } 135 | 136 | if (application == null) 137 | { 138 | throw new ArgumentNullException(nameof(application)); 139 | } 140 | 141 | if (loggerFactory == null) 142 | { 143 | throw new ArgumentNullException(nameof(loggerFactory)); 144 | } 145 | 146 | _options = options; 147 | _application = application; 148 | _connection = connection; 149 | _logger = loggerFactory.CreateLogger(); 150 | } 151 | 152 | public async Task ProcessRequestAsync(HttpContext context, CancellationToken token) 153 | { 154 | Debug.Assert(context.WebSockets.IsWebSocketRequest, "Not a websocket request"); 155 | 156 | var subProtocol = _options.SubProtocolSelector?.Invoke(context.WebSockets.WebSocketRequestedProtocols); 157 | var headers = _options.Headers != null ? _options.Headers() : new Dictionary(); 158 | using (var ws = await context.WebSockets.AcceptWebSocketAsync(subProtocol, headers)) 159 | { 160 | Log.SocketOpened(_logger, subProtocol); 161 | 162 | try 163 | { 164 | await ProcessSocketAsync(ws); 165 | } 166 | finally 167 | { 168 | Log.SocketClosed(_logger); 169 | } 170 | } 171 | } 172 | 173 | public async Task ProcessSocketAsync(WebSocket socket) 174 | { 175 | // Begin sending and receiving. Receiving must be started first because ExecuteAsync enables SendAsync. 176 | var receiving = StartReceiving(socket); 177 | var sending = StartSending(socket); 178 | 179 | // Wait for send or receive to complete 180 | var trigger = await Task.WhenAny(receiving, sending); 181 | 182 | if (trigger == receiving) 183 | { 184 | Log.WaitingForSend(_logger); 185 | 186 | // We're waiting for the application to finish and there are 2 things it could be doing 187 | // 1. Waiting for application data 188 | // 2. Waiting for a websocket send to complete 189 | 190 | // Cancel the application so that ReadAsync yields 191 | _application.Input.CancelPendingRead(); 192 | 193 | using (var delayCts = new CancellationTokenSource()) 194 | { 195 | var resultTask = await Task.WhenAny(sending, Task.Delay(_options.CloseTimeout, delayCts.Token)); 196 | 197 | if (resultTask != sending) 198 | { 199 | // We timed out so now we're in ungraceful shutdown mode 200 | Log.CloseTimedOut(_logger); 201 | 202 | // Abort the websocket if we're stuck in a pending send to the client 203 | _aborted = true; 204 | 205 | socket.Abort(); 206 | } 207 | else 208 | { 209 | delayCts.Cancel(); 210 | } 211 | } 212 | } 213 | else 214 | { 215 | Log.WaitingForClose(_logger); 216 | 217 | // We're waiting on the websocket to close and there are 2 things it could be doing 218 | // 1. Waiting for websocket data 219 | // 2. Waiting on a flush to complete (backpressure being applied) 220 | 221 | using (var delayCts = new CancellationTokenSource()) 222 | { 223 | var resultTask = await Task.WhenAny(receiving, Task.Delay(_options.CloseTimeout, delayCts.Token)); 224 | 225 | if (resultTask != receiving) 226 | { 227 | // Abort the websocket if we're stuck in a pending receive from the client 228 | _aborted = true; 229 | 230 | socket.Abort(); 231 | 232 | // Cancel any pending flush so that we can quit 233 | _application.Output.CancelPendingFlush(); 234 | } 235 | else 236 | { 237 | delayCts.Cancel(); 238 | } 239 | } 240 | } 241 | } 242 | 243 | private async Task StartReceiving(WebSocket socket) 244 | { 245 | var token = _connection.Cancellation?.Token ?? default; 246 | 247 | try 248 | { 249 | while (!token.IsCancellationRequested) 250 | { 251 | #if NETCOREAPP3_0 252 | // Do a 0 byte read so that idle connections don't allocate a buffer when waiting for a read 253 | var result = await socket.ReceiveAsync(Memory.Empty, token); 254 | 255 | if (result.MessageType == WebSocketMessageType.Close) 256 | { 257 | return; 258 | } 259 | #endif 260 | var memory = _application.Output.GetMemory(out var memoryframe); //RSOCKET Framing 261 | 262 | #if NETCOREAPP3_0 263 | var receiveResult = await socket.ReceiveAsync(memory, token); 264 | #else 265 | var isArray = MemoryMarshal.TryGetArray(memory, out var arraySegment); 266 | Debug.Assert(isArray); 267 | 268 | // Exceptions are handled above where the send and receive tasks are being run. 269 | var receiveResult = await socket.ReceiveAsync(arraySegment, token); 270 | #endif 271 | // Need to check again for netcoreapp3.0 because a close can happen between a 0-byte read and the actual read 272 | if (receiveResult.MessageType == WebSocketMessageType.Close) 273 | { 274 | return; 275 | } 276 | 277 | Log.MessageReceived(_logger, receiveResult.MessageType, receiveResult.Count, receiveResult.EndOfMessage); 278 | 279 | _application.Output.Advance(receiveResult.Count, receiveResult.EndOfMessage, memoryframe); //RSOCKET Framing 280 | 281 | var flushResult = await _application.Output.FlushAsync(); 282 | 283 | // We canceled in the middle of applying back pressure 284 | // or if the consumer is done 285 | if (flushResult.IsCanceled || flushResult.IsCompleted) 286 | { 287 | break; 288 | } 289 | } 290 | } 291 | catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) 292 | { 293 | // Client has closed the WebSocket connection without completing the close handshake 294 | Log.ClosedPrematurely(_logger, ex); 295 | } 296 | catch (OperationCanceledException) 297 | { 298 | // Ignore aborts, don't treat them like transport errors 299 | } 300 | catch (Exception ex) 301 | { 302 | if (!_aborted && !token.IsCancellationRequested) 303 | { 304 | _application.Output.Complete(ex); 305 | 306 | // We re-throw here so we can communicate that there was an error when sending 307 | // the close frame 308 | throw; 309 | } 310 | } 311 | finally 312 | { 313 | // We're done writing 314 | _application.Output.Complete(); 315 | } 316 | } 317 | 318 | private async Task StartSending(WebSocket socket) 319 | { 320 | Exception error = null; 321 | 322 | try 323 | { 324 | while (true) 325 | { 326 | var result = await _application.Input.ReadAsync(); 327 | var buffer = result.Buffer; 328 | var consumed = buffer.Start; //RSOCKET Framing 329 | // Get a frame from the application 330 | 331 | try 332 | { 333 | if (result.IsCanceled) 334 | { 335 | break; 336 | } 337 | 338 | if (!buffer.IsEmpty) 339 | { 340 | try 341 | { 342 | Log.SendPayload(_logger, buffer.Length); 343 | 344 | var webSocketMessageType = (_connection.ActiveFormat == TransferFormat.Binary 345 | ? WebSocketMessageType.Binary 346 | : WebSocketMessageType.Text); 347 | 348 | if (WebSocketCanSend(socket)) 349 | { 350 | consumed = await socket.SendAsync(buffer, buffer.Start, webSocketMessageType); //RSOCKET Framing 351 | } 352 | else 353 | { 354 | break; 355 | } 356 | } 357 | catch (Exception ex) 358 | { 359 | if (!_aborted) 360 | { 361 | Log.ErrorWritingFrame(_logger, ex); 362 | } 363 | break; 364 | } 365 | } 366 | else if (result.IsCompleted) 367 | { 368 | break; 369 | } 370 | } 371 | finally 372 | { 373 | _application.Input.AdvanceTo(consumed, buffer.End); //RSOCKET Framing 374 | } 375 | } 376 | } 377 | catch (Exception ex) 378 | { 379 | error = ex; 380 | } 381 | finally 382 | { 383 | // Send the close frame before calling into user code 384 | if (WebSocketCanSend(socket)) 385 | { 386 | // We're done sending, send the close frame to the client if the websocket is still open 387 | await socket.CloseOutputAsync(error != null ? WebSocketCloseStatus.InternalServerError : WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); 388 | } 389 | 390 | _application.Input.Complete(); 391 | } 392 | 393 | } 394 | 395 | private static bool WebSocketCanSend(WebSocket ws) 396 | { 397 | return !(ws.State == WebSocketState.Aborted || 398 | ws.State == WebSocketState.Closed || 399 | ws.State == WebSocketState.CloseSent); 400 | } 401 | } 402 | #endregion 403 | 404 | #region https://github.com/aspnet/AspNetCore/blob/master/src/SignalR/common/Http.Connections/src/Internal/Transports/WebSocketsTransport.Log.cs 405 | // Copyright (c) .NET Foundation. All rights reserved. 406 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 407 | 408 | public partial class WebSocketsTransport 409 | { 410 | private static class Log 411 | { 412 | private static readonly Action _socketOpened = 413 | LoggerMessage.Define(LogLevel.Debug, new EventId(1, "SocketOpened"), "Socket opened using Sub-Protocol: '{SubProtocol}'."); 414 | 415 | private static readonly Action _socketClosed = 416 | LoggerMessage.Define(LogLevel.Debug, new EventId(2, "SocketClosed"), "Socket closed."); 417 | 418 | private static readonly Action _clientClosed = 419 | LoggerMessage.Define(LogLevel.Debug, new EventId(3, "ClientClosed"), "Client closed connection with status code '{Status}' ({Description}). Signaling end-of-input to application."); 420 | 421 | private static readonly Action _waitingForSend = 422 | LoggerMessage.Define(LogLevel.Debug, new EventId(4, "WaitingForSend"), "Waiting for the application to finish sending data."); 423 | 424 | private static readonly Action _failedSending = 425 | LoggerMessage.Define(LogLevel.Debug, new EventId(5, "FailedSending"), "Application failed during sending. Sending InternalServerError close frame."); 426 | 427 | private static readonly Action _finishedSending = 428 | LoggerMessage.Define(LogLevel.Debug, new EventId(6, "FinishedSending"), "Application finished sending. Sending close frame."); 429 | 430 | private static readonly Action _waitingForClose = 431 | LoggerMessage.Define(LogLevel.Debug, new EventId(7, "WaitingForClose"), "Waiting for the client to close the socket."); 432 | 433 | private static readonly Action _closeTimedOut = 434 | LoggerMessage.Define(LogLevel.Debug, new EventId(8, "CloseTimedOut"), "Timed out waiting for client to send the close frame, aborting the connection."); 435 | 436 | private static readonly Action _messageReceived = 437 | LoggerMessage.Define(LogLevel.Trace, new EventId(9, "MessageReceived"), "Message received. Type: {MessageType}, size: {Size}, EndOfMessage: {EndOfMessage}."); 438 | 439 | private static readonly Action _messageToApplication = 440 | LoggerMessage.Define(LogLevel.Trace, new EventId(10, "MessageToApplication"), "Passing message to application. Payload size: {Size}."); 441 | 442 | private static readonly Action _sendPayload = 443 | LoggerMessage.Define(LogLevel.Trace, new EventId(11, "SendPayload"), "Sending payload: {Size} bytes."); 444 | 445 | private static readonly Action _errorWritingFrame = 446 | LoggerMessage.Define(LogLevel.Error, new EventId(12, "ErrorWritingFrame"), "Error writing frame."); 447 | 448 | private static readonly Action _sendFailed = 449 | LoggerMessage.Define(LogLevel.Error, new EventId(13, "SendFailed"), "Socket failed to send."); 450 | 451 | private static readonly Action _closedPrematurely = 452 | LoggerMessage.Define(LogLevel.Debug, new EventId(14, "ClosedPrematurely"), "Socket connection closed prematurely."); 453 | 454 | public static void SocketOpened(ILogger logger, string subProtocol) 455 | { 456 | _socketOpened(logger, subProtocol, null); 457 | } 458 | 459 | public static void SocketClosed(ILogger logger) 460 | { 461 | _socketClosed(logger, null); 462 | } 463 | 464 | public static void ClientClosed(ILogger logger, WebSocketCloseStatus? closeStatus, string closeDescription) 465 | { 466 | _clientClosed(logger, closeStatus, closeDescription, null); 467 | } 468 | 469 | public static void WaitingForSend(ILogger logger) 470 | { 471 | _waitingForSend(logger, null); 472 | } 473 | 474 | public static void FailedSending(ILogger logger) 475 | { 476 | _failedSending(logger, null); 477 | } 478 | 479 | public static void FinishedSending(ILogger logger) 480 | { 481 | _finishedSending(logger, null); 482 | } 483 | 484 | public static void WaitingForClose(ILogger logger) 485 | { 486 | _waitingForClose(logger, null); 487 | } 488 | 489 | public static void CloseTimedOut(ILogger logger) 490 | { 491 | _closeTimedOut(logger, null); 492 | } 493 | 494 | public static void MessageReceived(ILogger logger, WebSocketMessageType type, int size, bool endOfMessage) 495 | { 496 | _messageReceived(logger, type, size, endOfMessage, null); 497 | } 498 | 499 | public static void MessageToApplication(ILogger logger, int size) 500 | { 501 | _messageToApplication(logger, size, null); 502 | } 503 | 504 | public static void SendPayload(ILogger logger, long size) 505 | { 506 | _sendPayload(logger, size, null); 507 | } 508 | 509 | public static void ErrorWritingFrame(ILogger logger, Exception ex) 510 | { 511 | _errorWritingFrame(logger, ex); 512 | } 513 | 514 | public static void SendFailed(ILogger logger, Exception ex) 515 | { 516 | _sendFailed(logger, ex); 517 | } 518 | 519 | public static void ClosedPrematurely(ILogger logger, Exception ex) 520 | { 521 | _closedPrematurely(logger, ex); 522 | } 523 | } 524 | } 525 | #endregion 526 | } 527 | #region https://github.com/aspnet/AspNetCore/blob/master/src/SignalR/common/Http.Connections/src/WebSocketOptions.cs 528 | public class WebSocketOptions 529 | { 530 | public TimeSpan CloseTimeout { get; set; } = TimeSpan.FromSeconds(5); 531 | 532 | /// 533 | /// Gets or sets a delegate that will be called when a new WebSocket is established to select the value 534 | /// for the 'Sec-WebSocket-Protocol' response header. The delegate will be called with a list of the protocols provided 535 | /// by the client in the 'Sec-WebSocket-Protocol' request header. 536 | /// 537 | /// 538 | /// See RFC 6455 section 1.3 for more details on the WebSocket handshake: https://tools.ietf.org/html/rfc6455#section-1.3 539 | /// 540 | // WebSocketManager's list of sub protocols is an IList: 541 | // https://github.com/aspnet/HttpAbstractions/blob/a6bdb9b1ec6ed99978a508e71a7f131be7e4d9fb/src/Microsoft.AspNetCore.Http.Abstractions/WebSocketManager.cs#L23 542 | // Unfortunately, IList does not implement IReadOnlyList :( 543 | public Func, string> SubProtocolSelector { get; set; } 544 | 545 | public Func> Headers { get; set; } 546 | } 547 | #endregion 548 | } 549 | 550 | //namespace RSocket.Transports 551 | //{ 552 | // internal static partial class RSocketTransportExtensions 553 | // { 554 | // //Framing helper methods. They stay close to the original code as called, but add and remove the framing at the transport-socket boundary. 555 | // public static Memory GetMemory(this PipeWriter output, out Memory memoryframe) { memoryframe = output.GetMemory(); return memoryframe.Slice(sizeof(int)); } 556 | // public static void Advance(this PipeWriter output, int bytes, bool endOfMessage, in Memory memoryframe) { System.Buffers.Binary.BinaryPrimitives.WriteInt32BigEndian(memoryframe.Span, RSocketProtocol.MessageFrame(bytes, endOfMessage)); output.Advance(sizeof(int) + bytes); } 557 | // public static ValueTask SendAsync(this WebSocket webSocket, ReadOnlySequence buffer, SequencePosition position, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken = default) 558 | // { 559 | // buffer.TryGet(ref position, out var memory, advance: false); 560 | // var (length, isEndOfMessage) = RSocketProtocol.MessageFrame(System.Buffers.Binary.BinaryPrimitives.ReadInt32BigEndian(memory.Span)); 561 | // position = buffer.GetPosition(sizeof(int), position); 562 | // return webSocket.SendAsync(buffer.Slice(position, length), webSocketMessageType, cancellationToken); 563 | // } 564 | // } 565 | //} 566 | 567 | 568 | #region https://github.com/aspnet/AspNetCore/blob/master/src/SignalR/common/Shared/WebSocketExtensions.cs 569 | // Copyright (c) .NET Foundation. All rights reserved. 570 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 571 | 572 | namespace System.Net.WebSockets 573 | { 574 | internal static class WebSocketExtensions 575 | { 576 | public static ValueTask SendAsync(this WebSocket webSocket, ReadOnlySequence buffer, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken = default) 577 | { 578 | #if NETCOREAPP3_0 579 | if (buffer.IsSingleSegment) 580 | { 581 | return webSocket.SendAsync(buffer.First, webSocketMessageType, endOfMessage: true, cancellationToken); 582 | } 583 | else 584 | { 585 | return SendMultiSegmentAsync(webSocket, buffer, webSocketMessageType, cancellationToken); 586 | } 587 | #else 588 | if (buffer.IsSingleSegment) 589 | { 590 | var isArray = MemoryMarshal.TryGetArray(buffer.First, out var segment); 591 | Debug.Assert(isArray); 592 | return new ValueTask(webSocket.SendAsync(segment, webSocketMessageType, endOfMessage: true, cancellationToken)); 593 | } 594 | else 595 | { 596 | return SendMultiSegmentAsync(webSocket, buffer, webSocketMessageType, cancellationToken); 597 | } 598 | #endif 599 | } 600 | 601 | private static async ValueTask SendMultiSegmentAsync(WebSocket webSocket, ReadOnlySequence buffer, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken = default) 602 | { 603 | var position = buffer.Start; 604 | // Get a segment before the loop so we can be one segment behind while writing 605 | // This allows us to do a non-zero byte write for the endOfMessage = true send 606 | buffer.TryGet(ref position, out var prevSegment); 607 | while (buffer.TryGet(ref position, out var segment)) 608 | { 609 | #if NETCOREAPP3_0 610 | await webSocket.SendAsync(prevSegment, webSocketMessageType, endOfMessage: false, cancellationToken); 611 | #else 612 | var isArray = MemoryMarshal.TryGetArray(prevSegment, out var arraySegment); 613 | Debug.Assert(isArray); 614 | await webSocket.SendAsync(arraySegment, webSocketMessageType, endOfMessage: false, cancellationToken); 615 | #endif 616 | prevSegment = segment; 617 | } 618 | 619 | // End of message frame 620 | #if NETCOREAPP3_0 621 | await webSocket.SendAsync(prevSegment, webSocketMessageType, endOfMessage: true, cancellationToken); 622 | #else 623 | var isArrayEnd = MemoryMarshal.TryGetArray(prevSegment, out var arraySegmentEnd); 624 | Debug.Assert(isArrayEnd); 625 | await webSocket.SendAsync(arraySegmentEnd, webSocketMessageType, endOfMessage: true, cancellationToken); 626 | #endif 627 | } 628 | } 629 | } 630 | #endregion 631 | 632 | #region LICENSE for AspNetCore 633 | /* 634 | Apache License 635 | 636 | Version 2.0, January 2004 637 | http://www.apache.org/licenses/ 638 | 639 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 640 | 641 | 1. Definitions. 642 | 643 | "License" shall mean the terms and conditions for use, reproduction, 644 | and distribution as defined by Sections 1 through 9 of this document. 645 | 646 | "Licensor" shall mean the copyright owner or entity authorized by 647 | the copyright owner that is granting the License. 648 | 649 | "Legal Entity" shall mean the union of the acting entity and all 650 | 651 | other entities that control, are controlled by, or are under common 652 | 653 | control with that entity. For the purposes of this definition, 654 | "control" means (i) the power, direct or indirect, to cause the 655 | direction or management of such entity, whether by contract or 656 | 657 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 658 | outstanding shares, or (iii) beneficial ownership of such entity. 659 | 660 | "You" (or "Your") shall mean an individual or Legal Entity 661 | exercising permissions granted by this License. 662 | 663 | "Source" form shall mean the preferred form for making modifications, 664 | including but not limited to software source code, documentation 665 | source, and configuration files. 666 | 667 | "Object" form shall mean any form resulting from mechanical 668 | transformation or translation of a Source form, including but 669 | not limited to compiled object code, generated documentation, 670 | and conversions to other media types. 671 | 672 | "Work" shall mean the work of authorship, whether in Source or 673 | Object form, made available under the License, as indicated by a 674 | copyright notice that is included in or attached to the work 675 | (an example is provided in the Appendix below). 676 | 677 | "Derivative Works" shall mean any work, whether in Source or Object 678 | form, that is based on (or derived from) the Work and for which the 679 | editorial revisions, annotations, elaborations, or other modifications 680 | represent, as a whole, an original work of authorship. For the purposes 681 | of this License, Derivative Works shall not include works that remain 682 | separable from, or merely link (or bind by name) to the interfaces of, 683 | the Work and Derivative Works thereof. 684 | 685 | "Contribution" shall mean any work of authorship, including 686 | the original version of the Work and any modifications or additions 687 | to that Work or Derivative Works thereof, that is intentionally 688 | submitted to Licensor for inclusion in the Work by the copyright owner 689 | or by an individual or Legal Entity authorized to submit on behalf of 690 | the copyright owner. For the purposes of this definition, "submitted" 691 | means any form of electronic, verbal, or written communication sent 692 | to the Licensor or its representatives, including but not limited to 693 | communication on electronic mailing lists, source code control systems, 694 | and issue tracking systems that are managed by, or on behalf of, the 695 | Licensor for the purpose of discussing and improving the Work, but 696 | excluding communication that is conspicuously marked or otherwise 697 | designated in writing by the copyright owner as "Not a Contribution." 698 | 699 | "Contributor" shall mean Licensor and any individual or Legal Entity 700 | on behalf of whom a Contribution has been received by Licensor and 701 | subsequently incorporated within the Work. 702 | 703 | 2. Grant of Copyright License. Subject to the terms and conditions of 704 | this License, each Contributor hereby grants to You a perpetual, 705 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 706 | copyright license to reproduce, prepare Derivative Works of, 707 | publicly display, publicly perform, sublicense, and distribute the 708 | Work and such Derivative Works in Source or Object form. 709 | 710 | 3. Grant of Patent License. Subject to the terms and conditions of 711 | this License, each Contributor hereby grants to You a perpetual, 712 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 713 | (except as stated in this section) patent license to make, have made, 714 | use, offer to sell, sell, import, and otherwise transfer the Work, 715 | where such license applies only to those patent claims licensable 716 | by such Contributor that are necessarily infringed by their 717 | Contribution(s) alone or by combination of their Contribution(s) 718 | with the Work to which such Contribution(s) was submitted. If You 719 | institute patent litigation against any entity (including a 720 | cross-claim or counterclaim in a lawsuit) alleging that the Work 721 | or a Contribution incorporated within the Work constitutes direct 722 | or contributory patent infringement, then any patent licenses 723 | granted to You under this License for that Work shall terminate 724 | as of the date such litigation is filed. 725 | 726 | 4. Redistribution. You may reproduce and distribute copies of the 727 | Work or Derivative Works thereof in any medium, with or without 728 | modifications, and in Source or Object form, provided that You 729 | meet the following conditions: 730 | 731 | (a) You must give any other recipients of the Work or 732 | Derivative Works a copy of this License; and 733 | 734 | (b) You must cause any modified files to carry prominent notices 735 | stating that You changed the files; and 736 | 737 | (c) You must retain, in the Source form of any Derivative Works 738 | that You distribute, all copyright, patent, trademark, and 739 | attribution notices from the Source form of the Work, 740 | excluding those notices that do not pertain to any part of 741 | the Derivative Works; and 742 | 743 | (d) If the Work includes a "NOTICE" text file as part of its 744 | distribution, then any Derivative Works that You distribute must 745 | include a readable copy of the attribution notices contained 746 | within such NOTICE file, excluding those notices that do not 747 | pertain to any part of the Derivative Works, in at least one 748 | of the following places: within a NOTICE text file distributed 749 | as part of the Derivative Works; within the Source form or 750 | documentation, if provided along with the Derivative Works; or, 751 | within a display generated by the Derivative Works, if and 752 | wherever such third-party notices normally appear. The contents 753 | of the NOTICE file are for informational purposes only and 754 | do not modify the License. You may add Your own attribution 755 | notices within Derivative Works that You distribute, alongside 756 | 757 | or as an addendum to the NOTICE text from the Work, provided 758 | 759 | that such additional attribution notices cannot be construed 760 | 761 | as modifying the License. 762 | 763 | 764 | You may add Your own copyright statement to Your modifications and 765 | may provide additional or different license terms and conditions 766 | 767 | for use, reproduction, or distribution of Your modifications, or 768 | 769 | for any such Derivative Works as a whole, provided Your use, 770 | reproduction, and distribution of the Work otherwise complies with 771 | 772 | the conditions stated in this License. 773 | 774 | 5.Submission of Contributions.Unless You explicitly state otherwise, 775 | any Contribution intentionally submitted for inclusion in the Work 776 | 777 | by You to the Licensor shall be under the terms and conditions of 778 | 779 | this License, without any additional terms or conditions. 780 | 781 | Notwithstanding the above, nothing herein shall supersede or modify 782 | 783 | the terms of any separate license agreement you may have executed 784 | 785 | with Licensor regarding such Contributions. 786 | 787 | 6.Trademarks.This License does not grant permission to use the trade 788 | 789 | names, trademarks, service marks, or product names of the Licensor, 790 | except as required for reasonable and customary use in describing the 791 | 792 | origin of the Work and reproducing the content of the NOTICE file. 793 | 794 | 7.Disclaimer of Warranty.Unless required by applicable law or 795 | 796 | agreed to in writing, Licensor provides the Work(and each 797 | 798 | Contributor provides its Contributions) on an "AS IS" BASIS, 799 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 800 | 801 | implied, including, without limitation, any warranties or conditions 802 | 803 | of TITLE, NON - INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 804 | 805 | PARTICULAR PURPOSE.You are solely responsible for determining the 806 | 807 | appropriateness of using or redistributing the Work and assume any 808 | risks associated with Your exercise of permissions under this License. 809 | 810 | 8.Limitation of Liability. In no event and under no legal theory, 811 | whether in tort(including negligence), contract, or otherwise, 812 | unless required by applicable law(such as deliberate and grossly 813 | 814 | negligent acts) or agreed to in writing, shall any Contributor be 815 | 816 | liable to You for damages, including any direct, indirect, special, 817 | incidental, or consequential damages of any character arising as a 818 | 819 | result of this License or out of the use or inability to use the 820 | 821 | Work(including but not limited to damages for loss of goodwill, 822 | work stoppage, computer failure or malfunction, or any and all 823 | 824 | other commercial damages or losses), even if such Contributor 825 | has been advised of the possibility of such damages. 826 | 827 | 9.Accepting Warranty or Additional Liability. While redistributing 828 | 829 | the Work or Derivative Works thereof, You may choose to offer, 830 | and charge a fee for, acceptance of support, warranty, indemnity, 831 | or other liability obligations and / or rights consistent with this 832 | 833 | License.However, in accepting such obligations, You may act only 834 | 835 | on Your own behalf and on Your sole responsibility, not on behalf 836 | 837 | of any other Contributor, and only if You agree to indemnify, 838 | defend, and hold each Contributor harmless for any liability 839 | 840 | incurred by, or claims asserted against, such Contributor by reason 841 | 842 | of your accepting any such warranty or additional liability. 843 | 844 | END OF TERMS AND CONDITIONS 845 | 846 | APPENDIX: How to apply the Apache License to your work. 847 | 848 | 849 | To apply the Apache License to your work, attach the following 850 | 851 | boilerplate notice, with the fields enclosed by brackets "[]" 852 | 853 | replaced with your own identifying information. (Don't include 854 | 855 | the brackets!) The text should be enclosed in the appropriate 856 | 857 | comment syntax for the file format.We also recommend that a 858 | 859 | file or class name and description of purpose be included on the 860 | 861 | same "printed page" as the copyright notice for easier 862 | identification within third-party archives. 863 | 864 | Copyright(c) .NET Foundation and Contributors 865 | 866 | Licensed under the Apache License, Version 2.0 (the "License"); 867 | you may not use this file except in compliance with the License. 868 | You may obtain a copy of the License at 869 | 870 | http://www.apache.org/licenses/LICENSE-2.0 871 | 872 | Unless required by applicable law or agreed to in writing, software 873 | distributed under the License is distributed on an "AS IS" BASIS, 874 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 875 | See the License for the specific language governing permissions and 876 | limitations under the License. 877 | */ 878 | #endregion 879 | -------------------------------------------------------------------------------- /RSocket.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2046 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSocketSample", "RSocketSample\RSocketSample.csproj", "{67F2AAD9-EF80-4B9D-AAA7-AF519E1E257C}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSocket.Core", "RSocket.Core\RSocket.Core.csproj", "{8575B6E4-9E7F-4473-8D56-9FE21240B1C4}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSocket.Core.Tests", "RSocket.Core.Tests\RSocket.Core.Tests.csproj", "{AF95925C-C011-4EB8-BDEE-A28736042974}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {67F2AAD9-EF80-4B9D-AAA7-AF519E1E257C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {67F2AAD9-EF80-4B9D-AAA7-AF519E1E257C}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {67F2AAD9-EF80-4B9D-AAA7-AF519E1E257C}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {67F2AAD9-EF80-4B9D-AAA7-AF519E1E257C}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {8575B6E4-9E7F-4473-8D56-9FE21240B1C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {8575B6E4-9E7F-4473-8D56-9FE21240B1C4}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {8575B6E4-9E7F-4473-8D56-9FE21240B1C4}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {8575B6E4-9E7F-4473-8D56-9FE21240B1C4}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {AF95925C-C011-4EB8-BDEE-A28736042974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {AF95925C-C011-4EB8-BDEE-A28736042974}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {AF95925C-C011-4EB8-BDEE-A28736042974}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {AF95925C-C011-4EB8-BDEE-A28736042974}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {454B7F31-FD91-4CCC-95CA-35A741A80D11} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /RSocketSample/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using ProtoBuf; 5 | 6 | namespace RSocketSample 7 | { 8 | [ProtoContract] 9 | class Person 10 | { 11 | [ProtoMember(1)] public int Id { get; set; } 12 | [ProtoMember(2)] public string Name { get; set; } 13 | [ProtoMember(3)] public Address Address { get; set; } 14 | public override string ToString() => $"{Id}:{Name} ({Address})"; 15 | } 16 | 17 | [ProtoContract] 18 | class Address 19 | { 20 | [ProtoMember(1)] public string Line1 { get; set; } 21 | [ProtoMember(2)] public string Line2 { get; set; } 22 | public override string ToString() => $"{Line1},{Line2}"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RSocketSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reactive.Linq; 4 | using System.Threading.Tasks; 5 | using RSocket; 6 | using RSocket.Transports; 7 | 8 | namespace RSocketSample 9 | { 10 | class Program 11 | { 12 | //TODO Connection Cleanup on Unsubscribe/failure/etc 13 | //TODO General Error handling -> OnError 14 | 15 | static async Task Main(string[] args) 16 | { 17 | var loopback = new LoopbackTransport(); 18 | var server = new EchoServer(loopback.Beyond); 19 | await server.ConnectAsync(); 20 | 21 | //var client = new RSocketClient(new SocketTransport("tcp://localhost:9091/"), new RSocketOptions() { InitialRequestSize = 3 }); 22 | //var client = new RSocketClient(new WebSocketTransport("ws://localhost:9092/"), new RSocketOptions() { InitialRequestSize = 3 }); 23 | var client = new RSocketClient(loopback); 24 | await client.ConnectAsync(); 25 | 26 | 27 | Console.WriteLine("Requesting Raw Protobuf Stream..."); 28 | 29 | var persondata = new Person() { Id = 1234, Name = "Someone Person", Address = new Address() { Line1 = "123 Any Street", Line2 = "Somewhere, LOC" } }; 30 | var personmetadata = new Person() { Id = 567, Name = "Meta Person", Address = new Address() { Line1 = "", Line2 = "" } }; 31 | 32 | //Make a Raw binary call just to show how it's done. 33 | var stream = client.RequestStream( 34 | resultmapper: result => (Data: ProtobufNetSerializer.Deserialize(result.data), Metadata: ProtobufNetSerializer.Deserialize(result.metadata)), 35 | data: ProtobufNetSerializer.Serialize(persondata), metadata: ProtobufNetSerializer.Serialize(personmetadata)); 36 | 37 | await stream.ForEachAsync(persons => Console.WriteLine($"RawDemo.OnNext===>[{persons.Metadata}]{persons.Data}")); 38 | 39 | 40 | Console.WriteLine("\nRequesting String Serializer Stream..."); 41 | 42 | var stringclient = new RSocketClient.ForStrings(client); //A simple client that uses UTF8 strings instead of bytes. 43 | await stringclient.RequestStream("A Demo Payload") 44 | .ForEachAsync(result => Console.WriteLine($"StringDemo.OnNext===>{result}")); 45 | 46 | Console.ReadKey(); 47 | 48 | //var sender = from index in Observable.Interval(TimeSpan.FromSeconds(1)) select new Person() { Id = (int)index, Name = $"Person #{index:0000}" }; 49 | //using (personclient.RequestChannel(obj).Subscribe( 50 | // onNext: value => Console.WriteLine($"RequestChannel.OnNext ===>{value}"), onCompleted: () => Console.WriteLine($"RequestChannel.OnComplete!"))) 51 | //{ 52 | // Console.ReadKey(); 53 | //} 54 | } 55 | } 56 | 57 | class EchoServer : RSocketServer 58 | { 59 | public EchoServer(IRSocketTransport transport, RSocketOptions options = default, int echoes = 2) : base(transport, options) 60 | { 61 | Stream(request => request, 62 | request => AsyncEnumerable.Repeat(request, echoes), 63 | result => result); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RSocketSample/ProtobufNetSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.IO; 4 | 5 | namespace RSocketSample 6 | { 7 | public static class ProtobufNetSerializer 8 | { 9 | static public ReadOnlySequence Serialize(in T item) 10 | { 11 | if (item == null) { return default; } 12 | using (var stream = new MemoryStream()) //According to source, access to the buffer is safe after disposal (See. Dispose()): https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs#L133 13 | { 14 | ProtoBuf.Serializer.Serialize(stream, item); //TODO Probably need a Stream -> IBufferWriter. I think the .NET folks have made one... 15 | if (stream.TryGetBuffer(out var buffer)) 16 | { 17 | var seq = new ReadOnlySequence(buffer.Array, buffer.Offset, buffer.Count); 18 | 19 | return new ReadOnlySequence(stream.ToArray()); //TODO not great, but the stream is disposing, so we'd lose the buffer. 20 | } 21 | else { throw new InvalidOperationException("Unable to get MemoryStream buffer"); } 22 | } 23 | } 24 | 25 | static public T Deserialize(in ReadOnlySequence data) 26 | { 27 | using (var stream = new MemoryStream(data.ToArray(), false)) //TODO A Span backed stream would be better here. 28 | { 29 | try { return ProtoBuf.Serializer.Deserialize(stream); } 30 | catch (ProtoBuf.ProtoException ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); throw; } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /RSocketSample/RSocketSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | latest 7 | Apache-2.0 8 | RSocket-Net Samples 9 | RSocket Team 10 | RSocket Protocol implementation for .NET 11 | RSocket Authors 12 | http://rsocket.io/ 13 | https://github.com/rsocket/rsocket-artwork/raw/master/rsocket-logo/PNG/r-socket-pink.png 14 | https://github.com/rsocket/rsocket-net 15 | RSocket, Protocol, Network, Reactive, Reactive-Streams 16 | 0.2.7 17 | 0.2.7 18 | 0.2.7 19 | 0.2.7 20 | 0.2.7 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | --------------------------------------------------------------------------------