├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── BedrockFramework.sln ├── Directory.Build.props ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── docfx.json ├── index.md └── toc.yml ├── global.json ├── nuget.config ├── samples ├── Certs │ └── testcert.pfx ├── ClientApplication │ ├── ClientApplication.csproj │ ├── DnsCachingConnectionFactory.cs │ ├── HubBuilderExtensions.cs │ └── Program.cs ├── Directory.Build.props └── ServerApplication │ ├── Chat.cs │ ├── EchoServerApplication.cs │ ├── HttpApplication.cs │ ├── LengthPrefixedProtocol.cs │ ├── MqttApplication.cs │ ├── MyCustomProtocol.cs │ ├── Program.cs │ └── ServerApplication.csproj ├── src ├── Bedrock.Framework.Experimental │ ├── Bedrock.Framework.Experimental.csproj │ ├── Client │ │ └── ClientExtensions.cs │ ├── Infrastructure │ │ ├── BufferExtensions.cs │ │ ├── BufferWriter.cs │ │ ├── SequenceReaderExtensions.cs │ │ └── TaskCompletionSourceWithCancellation.cs │ ├── Protocols │ │ ├── ChunkedHttpBodyReader.cs │ │ ├── ContentLengthHttpBodyReader.cs │ │ ├── Http1Header.cs │ │ ├── Http1HeaderReader.cs │ │ ├── Http1RequestMessageReader.cs │ │ ├── Http1RequestMessageWriter.cs │ │ ├── Http1ResponseMessageReader.cs │ │ ├── Http1ResponseMessageWriter.cs │ │ ├── Http2 │ │ │ ├── Bitshifter.cs │ │ │ ├── FlowControl │ │ │ │ ├── FlowControl.cs │ │ │ │ ├── InputFlowControl.cs │ │ │ │ ├── OutputFlowControl.cs │ │ │ │ ├── OutputFlowControlAwaitable.cs │ │ │ │ ├── StreamInputFlowControl.cs │ │ │ │ └── StreamOutputFlowControl.cs │ │ │ ├── HPack │ │ │ │ ├── DynamicTable.cs │ │ │ │ ├── HPackDecoder.cs │ │ │ │ ├── HPackDecodingException.cs │ │ │ │ ├── HPackEncoder.cs │ │ │ │ ├── HPackEncodingException.cs │ │ │ │ ├── HeaderField.cs │ │ │ │ ├── Huffman.cs │ │ │ │ ├── HuffmanDecodingException.cs │ │ │ │ ├── IntegerDecoder.cs │ │ │ │ ├── IntegerEncoder.cs │ │ │ │ ├── StaticTable.cs │ │ │ │ └── StatusCodes.cs │ │ │ ├── Http2ConnectionErrorException.cs │ │ │ ├── Http2ContinuationFrameFlags.cs │ │ │ ├── Http2DataFrameFlags.cs │ │ │ ├── Http2ErrorCode.cs │ │ │ ├── Http2Frame.Continuation.cs │ │ │ ├── Http2Frame.Data.cs │ │ │ ├── Http2Frame.GoAway.cs │ │ │ ├── Http2Frame.Headers.cs │ │ │ ├── Http2Frame.Ping.cs │ │ │ ├── Http2Frame.Priority.cs │ │ │ ├── Http2Frame.RstStream.cs │ │ │ ├── Http2Frame.Settings.cs │ │ │ ├── Http2Frame.WindowUpdate.cs │ │ │ ├── Http2Frame.cs │ │ │ ├── Http2FrameReader.cs │ │ │ ├── Http2FrameType.cs │ │ │ ├── Http2FrameWriter.cs │ │ │ ├── Http2HeadersFrameFlags.cs │ │ │ ├── Http2PeerSetting.cs │ │ │ ├── Http2PeerSettings.cs │ │ │ ├── Http2PingFrameFlags.cs │ │ │ ├── Http2SettingsFrameFlags.cs │ │ │ ├── Http2SettingsParameter.cs │ │ │ └── Http2SettingsParameterOutOfRangeException.cs │ │ ├── Http2ClientProtocol.cs │ │ ├── Http2Protocol.cs │ │ ├── Http2ServerProtocol.cs │ │ ├── HttpBodyContent.cs │ │ ├── HttpBodyStream.cs │ │ ├── HttpClientProtocol.cs │ │ ├── HttpServerProtocol.cs │ │ ├── HubHandshakeMessageReader.cs │ │ ├── HubMessageReader.cs │ │ ├── HubMessageWriter.cs │ │ ├── HubProtocol.cs │ │ ├── Memcached │ │ │ ├── Constants.cs │ │ │ ├── Enums.cs │ │ │ ├── Expiration.cs │ │ │ ├── MemcachedMessageReader.cs │ │ │ ├── MemcachedMessageWriter.cs │ │ │ ├── MemcachedProtocol.cs │ │ │ ├── MemcachedRequest.cs │ │ │ ├── MemcachedRequestHeader.cs │ │ │ ├── MemcachedResponse.cs │ │ │ └── MemcachedResponseHeader.cs │ │ ├── ParseResult.cs │ │ ├── RabbitMQ │ │ │ ├── FrameType.cs │ │ │ ├── IRabbitMQMessage.cs │ │ │ ├── Methods │ │ │ │ ├── ChannelOpen.cs │ │ │ │ ├── ChannelOpenOk.cs │ │ │ │ ├── ConnectionOk.cs │ │ │ │ ├── ConnectionOpen.cs │ │ │ │ ├── ConnectionOpenOk.cs │ │ │ │ ├── ConnectionStart.cs │ │ │ │ ├── ConnectionTune.cs │ │ │ │ ├── ConnectionTuneOk.cs │ │ │ │ ├── MethodBase.cs │ │ │ │ ├── QueueDeclare.cs │ │ │ │ ├── QueueDeclareOk.cs │ │ │ │ ├── QueueDelete.cs │ │ │ │ └── QueueDeleteOk.cs │ │ │ ├── ProtocolHelper.cs │ │ │ ├── RabbitMQClientProtocol.cs │ │ │ ├── RabbitMQMessageFormatter.cs │ │ │ └── RabbitMQProtocolVersionHeader.cs │ │ └── WebSocketProtocol.cs │ └── Transports │ │ ├── Http2 │ │ ├── Http2ConnectionFactory.cs │ │ ├── Http2ConnectionListener.cs │ │ └── Http2ConnectionListenerFactory.cs │ │ ├── Pipes │ │ ├── NamedPipeConnectionContext.cs │ │ ├── NamedPipeConnectionFactory.cs │ │ ├── NamedPipeConnectionListener.cs │ │ ├── NamedPipeConnectionListenerFactory.cs │ │ └── NamedPipeEndPoint.cs │ │ ├── Pooling │ │ ├── ConnectionPoolingFactory.cs │ │ ├── EndPointPool.cs │ │ ├── HttpConnectionKind.cs │ │ ├── HttpEndPoint.cs │ │ ├── IEndPointPool.cs │ │ ├── IMaxConnectionFeature.cs │ │ └── PooledConnectionContext.cs │ │ ├── ServerBuilderExtensions.cs │ │ └── WebSockets │ │ ├── WebSocketConnectionFactory.cs │ │ ├── WebSocketConnectionListener.cs │ │ └── WebSocketConnectionListenerFactory.cs └── Bedrock.Framework │ ├── Bedrock.Framework.csproj │ ├── Client │ ├── Client.cs │ └── ClientBuilder.cs │ ├── ConnectionContextWithDelegate.cs │ ├── Hosting │ ├── ServerHostedService.cs │ ├── ServerHostedServiceOptions.cs │ └── ServiceCollectionExtensions.cs │ ├── Infrastructure │ ├── DuplexPipe.cs │ ├── DuplexPipeStream.cs │ ├── DuplexPipeStreamAdapter.cs │ ├── EmptyServiceProvder.cs │ ├── MemoryPoolExtensions.cs │ ├── TaskExtensions.cs │ └── TaskToApm.cs │ ├── InternalsVisibleTo.cs │ ├── Middleware │ ├── ConnectionBuilderExtensions.cs │ ├── ConnectionLimitMiddleware.cs │ ├── Internal │ │ └── LoggingStream.cs │ ├── LoggingConnectionMiddleware.cs │ └── Tls │ │ ├── CertificateLoader.cs │ │ ├── ITlsApplicationProtocolFeature.cs │ │ ├── ITlsConnectionFeature.cs │ │ ├── ITlsHandshakeFeature.cs │ │ ├── RemoteCertificateMode.cs │ │ ├── SslDuplexPipe.cs │ │ ├── TlsClientConnectionMiddleware.cs │ │ ├── TlsConnectionFeature.cs │ │ ├── TlsOptions.cs │ │ └── TlsServerConnectionMiddleware.cs │ ├── Protocols │ ├── ConsumableArrayBufferWriter.cs │ ├── IMessageReader.cs │ ├── IMessageWriter.cs │ ├── MessagePipeReader.cs │ ├── Protocol.cs │ ├── ProtocolReadResult.cs │ ├── ProtocolReader.cs │ ├── ProtocolWriter.cs │ └── WebSockets │ │ ├── WebSocketFrameException.cs │ │ ├── WebSocketFrameReader.cs │ │ ├── WebSocketFrameWriter.cs │ │ ├── WebSocketHeader.cs │ │ ├── WebSocketOpcode.cs │ │ ├── WebSocketPayloadEncoder.cs │ │ ├── WebSocketPayloadReader.cs │ │ ├── WebSocketReadFrame.cs │ │ └── WebSocketWriteFrame.cs │ ├── Server │ ├── EndPointBinding.cs │ ├── LocalHostBinding.cs │ ├── Server.cs │ ├── ServerBinding.cs │ ├── ServerBuilder.cs │ ├── ServerConnection.cs │ └── ServiceProviderExtensions.cs │ └── Transports │ ├── Memory │ ├── MemoryEndPoint.cs │ └── MemoryTransport.cs │ ├── ServerBuilderExtensions.cs │ └── Sockets │ ├── BufferExtensions.cs │ ├── ServerBuilderExtensions.cs │ ├── SocketAwaitable.cs │ ├── SocketConnection.cs │ ├── SocketConnectionFactory.cs │ ├── SocketReceiver.cs │ ├── SocketSender.cs │ └── SocketsServerBuilder.cs ├── tests ├── Bedrock.Framework.Benchmarks │ ├── Bedrock.Framework.Benchmarks.csproj │ ├── BenchmarkConfig.cs │ ├── HttpHeaderReaderBenchmarks.cs │ ├── MessagePipeReaderBenchmarks.cs │ ├── Program.cs │ └── ProtocolReaderBenchmarks.cs ├── Bedrock.Framework.Tests │ ├── Bedrock.Framework.Tests.csproj │ ├── ConnectionPoolingTests.cs │ ├── ConsumableArrayBufferWriterTests.cs │ ├── Http1Tests.cs │ ├── Infrastructure │ │ └── TestSequenceSegment.cs │ ├── MessagePipeReaderTests.cs │ ├── ProtocolTests.cs │ └── Protocols │ │ ├── Http1HeaderReaderTests.cs │ │ ├── WebSocketFrameReaderTests.cs │ │ ├── WebSocketFrameWriterTests.cs │ │ └── WebSocketPayloadReaderTests.cs └── Directory.Build.props └── version.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.props, *.targets, *.csproj] 4 | indent_style = space 5 | indent_size = 2 6 | 7 | [*.cs] 8 | indent_style = space 9 | indent_size = 4 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Make sh files under the build directory always have LF as line endings 8 | ############################################################################### 9 | *.sh eol=lf 10 | 11 | ############################################################################### 12 | # Set default behavior for command prompt diff. 13 | # 14 | # This is need for earlier builds of msysgit that does not have it on by 15 | # default for csharp files. 16 | # Note: This is only used by command line 17 | ############################################################################### 18 | #*.cs diff=csharp 19 | 20 | ############################################################################### 21 | # Set the merge driver for project and solution files 22 | # 23 | # Merging from the command prompt will add diff markers to the files if there 24 | # are conflicts (Merging from VS is not affected by the settings below, in VS 25 | # the diff markers are never inserted). Diff markers may cause the following 26 | # file extensions to fail to load in VS. An alternative would be to treat 27 | # these files as binary and thus will always conflict and require user 28 | # intervention with every merge. To do so, just uncomment the entries below 29 | ############################################################################### 30 | #*.sln merge=binary 31 | #*.csproj merge=binary 32 | #*.vbproj merge=binary 33 | #*.vcxproj merge=binary 34 | #*.vcproj merge=binary 35 | #*.dbproj merge=binary 36 | #*.fsproj merge=binary 37 | #*.lsproj merge=binary 38 | #*.wixproj merge=binary 39 | #*.modelproj merge=binary 40 | #*.sqlproj merge=binary 41 | #*.wwaproj merge=binary 42 | 43 | ############################################################################### 44 | # behavior for image files 45 | # 46 | # image files are treated as binary by default. 47 | ############################################################################### 48 | #*.jpg binary 49 | #*.png binary 50 | #*.gif binary 51 | 52 | ############################################################################### 53 | # diff behavior for common document formats 54 | # 55 | # Convert binary document formats to text before diffing them. This feature 56 | # is only available from the command line. Turn it on by uncommenting the 57 | # entries below. 58 | ############################################################################### 59 | #*.doc diff=astextplain 60 | #*.DOC diff=astextplain 61 | #*.docx diff=astextplain 62 | #*.DOCX diff=astextplain 63 | #*.dot diff=astextplain 64 | #*.DOT diff=astextplain 65 | #*.pdf diff=astextplain 66 | #*.PDF diff=astextplain 67 | #*.rtf diff=astextplain 68 | #*.RTF diff=astextplain 69 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | 15 | - uses: dotnet/nbgv@v0.4.2 16 | with: 17 | setAllVars: true 18 | 19 | - name: Setup .NET Core SDK 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: | 23 | 8.0.x 24 | 9.0.x 25 | 26 | - name: dotnet build 27 | run: dotnet build BedrockFramework.sln -c Release 28 | 29 | - name: dotnet test 30 | run: dotnet test BedrockFramework.sln -c Release --no-build 31 | 32 | - name: dotnet pack 33 | run: dotnet pack BedrockFramework.sln -c Release --no-build --include-source --include-symbols 34 | 35 | - name: setup nuget 36 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 37 | uses: NuGet/setup-nuget@v1.0.5 38 | with: 39 | nuget-version: latest 40 | 41 | - name: Set API key 42 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 43 | run: nuget setapikey ${{ secrets.FEEDZ_TOKEN }} -Config nuget.config -Source https://f.feedz.io/davidfowl/bedrockframework/nuget/index.json 44 | 45 | - name: Set symbols API key 46 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 47 | run: nuget setapikey ${{ secrets.FEEDZ_TOKEN }} -Config nuget.config -Source https://f.feedz.io/davidfowl/bedrockframework/symbols 48 | 49 | - name: push packages 50 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 51 | run: dotnet nuget push **/*.nupkg -s https://f.feedz.io/davidfowl/bedrockframework/nuget/index.json -ss https://f.feedz.io/davidfowl/bedrockframework/symbols --skip-duplicate 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | artifacts/ 3 | bin/ 4 | obj/ 5 | .dotnet/ 6 | .nuget/ 7 | .packages/ 8 | .tools/ 9 | .vs/ 10 | .vscode/ 11 | node_modules/ 12 | BenchmarkDotNet.Artifacts/ 13 | .gradle/ 14 | src/SignalR/clients/**/dist/ 15 | modules/ 16 | 17 | # File extensions 18 | *.aps 19 | *.binlog 20 | *.dll 21 | *.DS_Store 22 | *.exe 23 | *.idb 24 | *.lib 25 | *.log 26 | *.pch 27 | *.pdb 28 | *.pidb 29 | *.psess 30 | *.res 31 | *.snk 32 | *.suo 33 | *.tlog 34 | *.user 35 | *.userprefs 36 | *.vspx 37 | 38 | # Specific files, typically generated by tools 39 | launchSettings.json 40 | msbuild.ProjectImports.zip 41 | StyleCop.Cache 42 | UpgradeLog.htm 43 | 44 | # DocFX site generation (for now) 45 | _site/ -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | $(NoWarn);NU5105 6 | https://github.com/davidfowl/BedrockFramework 7 | https://github.com/davidfowl/BedrockFramework 8 | https://github.com/davidfowl/BedrockFramework/releases 9 | MIT 10 | git 11 | cloud microservice networking sockets websockets tcp 12 | 13 | 14 | 15 | 3.6.146 16 | all 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 David Fowler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bedrock Framework 2 | 3 | [![feedz.io](https://img.shields.io/badge/endpoint.svg?url=https%3A%2F%2Ff.feedz.io%2Fdavidfowl%2Fbedrockframework%2Fshield%2FBedrock.Framework%2Flatest&label=Bedrock.Framework)](https://f.feedz.io/davidfowl/bedrockframework/packages/Bedrock.Framework/latest/download) 4 | 5 | [![Gitter](https://badges.gitter.im/BedrockFramework/BedrockFramework.svg)](https://gitter.im/BedrockFramework/BedrockFramework?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 6 | 7 | [Project Bedrock](https://github.com/aspnet/AspNetCore/issues/4772) is a set of .NET Core APIs for doing transport agnostic networking. In .NET Core 3.0 we've introduced some new abstractions 8 | as part of [Microsoft.AspNetCore.Connections.Abstractions](https://www.nuget.org/packages/Microsoft.AspNetCore.Connections.Abstractions) for client-server communication. 9 | 10 | See the presentation [here](https://speakerdeck.com/davidfowl/project-bedrock) 11 | 12 | This project is split into 2 packages: 13 | - **Bedrock.Framework** - The core framework, server and client builder APIs, built in middleware and transports (sockets and memory). 14 | - **Bedrock.Framework.Experimental** - A set of protocol and transport implementations that may eventually make their way into core. Some of them are incomplete at this time. 15 | 16 | ## Using CI builds 17 | 18 | To use CI builds add the following nuget feed: 19 | 20 | ```xml 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | _site 10 | -------------------------------------------------------------------------------- /docs/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": [ "**/**.csproj" ], 7 | "exclude": [ "**/bin/**", "**/obj/**" ], 8 | "src": "../src" 9 | } 10 | ], 11 | "dest": "obj/api" 12 | } 13 | ], 14 | "build": { 15 | "content": [ 16 | { 17 | "files": [ "**/*.yml" ], 18 | "src": "obj/api", 19 | "dest": "api" 20 | }, 21 | { 22 | "files": [ 23 | "*.md", 24 | "toc.yml" 25 | ] 26 | } 27 | ], 28 | "resource": [ 29 | { 30 | "files": [ 31 | "images/**" 32 | ] 33 | } 34 | ], 35 | "globalMetadata": { 36 | "_appTitle": "Bedrock Framework docs", 37 | "_enableSearch": true 38 | }, 39 | "dest": "_site", 40 | "xrefService": [ "https://xref.docs.microsoft.com/query?uid={uid}" ], 41 | "template": [ 42 | "default" 43 | ] 44 | } 45 | } -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Bedrock Framework 2 | 3 | Project Bedrock is a set of .NET Core APIs for doing transport agnostic networking. In .NET Core 3.0 we've introduced some new abstractions as part of Microsoft.AspNetCore.Connections.Abstractions for client-server communication. 4 | 5 | Check out the full [API documentation available online](/api/Bedrock.Framework.html). -------------------------------------------------------------------------------- /docs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Api Documentation 2 | href: api/ 3 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.100" 4 | } 5 | } -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /samples/Certs/testcert.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidfowl/BedrockFramework/58e81e930a5d7a26095336dc91dfb935aca021f2/samples/Certs/testcert.pfx -------------------------------------------------------------------------------- /samples/ClientApplication/ClientApplication.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /samples/ClientApplication/HubBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Text; 5 | using Bedrock.Framework; 6 | using Microsoft.AspNetCore.Connections; 7 | using Microsoft.AspNetCore.SignalR.Client; 8 | using Microsoft.Extensions.DependencyInjection; 9 | 10 | namespace Microsoft.AspNetCore.SignalR.Client 11 | { 12 | public static class HubBuilderExtensions 13 | { 14 | public static IHubConnectionBuilder WithConnectionFactory(this IHubConnectionBuilder hubConnectionBuilder, IConnectionFactory connectionFactory, EndPoint endPoint) 15 | { 16 | hubConnectionBuilder.Services.AddSingleton(connectionFactory); 17 | hubConnectionBuilder.Services.AddSingleton(endPoint); 18 | return hubConnectionBuilder; 19 | } 20 | 21 | public static IHubConnectionBuilder WithClientBuilder(this IHubConnectionBuilder hubConnectionBuilder, EndPoint endPoint, Action configure) 22 | { 23 | hubConnectionBuilder.Services.AddSingleton(sp => 24 | { 25 | var builder = new ClientBuilder(sp); 26 | configure(builder); 27 | return builder.Build(); 28 | }); 29 | 30 | hubConnectionBuilder.Services.AddSingleton(endPoint); 31 | return hubConnectionBuilder; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | -------------------------------------------------------------------------------- /samples/ServerApplication/Chat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.SignalR; 6 | 7 | namespace ServerApplication 8 | { 9 | public class Chat : Hub 10 | { 11 | public Task Send(string message) 12 | { 13 | return Clients.All.SendAsync("Send", message); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/ServerApplication/EchoServerApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Bedrock.Framework.Middleware.Tls; 6 | using Microsoft.AspNetCore.Connections; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace Bedrock.Framework 10 | { 11 | public class EchoServerApplication : ConnectionHandler 12 | { 13 | private readonly ILogger _logger; 14 | 15 | public EchoServerApplication(ILogger logger) 16 | { 17 | _logger = logger; 18 | } 19 | 20 | public override async Task OnConnectedAsync(ConnectionContext connection) 21 | { 22 | try 23 | { 24 | _logger.LogInformation("{ConnectionId} connected", connection.ConnectionId); 25 | 26 | var handshake = connection.Features.Get(); 27 | 28 | if (handshake != null) 29 | { 30 | _logger.LogInformation("TLS enabled, TLS Verson={TLSVersion}, HashAlgorithm={HashAlgorithm}", handshake.Protocol, handshake.HashAlgorithm); 31 | } 32 | 33 | await connection.Transport.Input.CopyToAsync(connection.Transport.Output); 34 | } 35 | finally 36 | { 37 | _logger.LogInformation("{ConnectionId} disconnected", connection.ConnectionId); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/ServerApplication/HttpApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using Bedrock.Framework.Protocols; 7 | using Microsoft.AspNetCore.Connections; 8 | 9 | namespace ServerApplication 10 | { 11 | public class HttpApplication : ConnectionHandler 12 | { 13 | public override async Task OnConnectedAsync(ConnectionContext connection) 14 | { 15 | var httpConnection = new HttpServerProtocol(connection); 16 | 17 | while (true) 18 | { 19 | var request = await httpConnection.ReadRequestAsync(); 20 | 21 | Console.WriteLine(request); 22 | 23 | // Consume the request body 24 | await request.Content.CopyToAsync(Stream.Null); 25 | 26 | await httpConnection.WriteResponseAsync(new HttpResponseMessage(HttpStatusCode.OK) 27 | { 28 | Content = new StringContent("Hello World") 29 | }); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/ServerApplication/LengthPrefixedProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using Bedrock.Framework.Protocols; 5 | 6 | namespace Protocols 7 | { 8 | public class LengthPrefixedProtocol : IMessageReader, IMessageWriter 9 | { 10 | public bool TryParseMessage(in ReadOnlySequence input, ref SequencePosition consumed, ref SequencePosition examined, out Message message) 11 | { 12 | var reader = new SequenceReader(input); 13 | if (!reader.TryReadBigEndian(out int length) || reader.Remaining < length) 14 | { 15 | message = default; 16 | return false; 17 | } 18 | 19 | var payload = input.Slice(reader.Position, length); 20 | message = new Message(payload); 21 | 22 | consumed = payload.End; 23 | examined = consumed; 24 | return true; 25 | } 26 | 27 | public void WriteMessage(Message message, IBufferWriter output) 28 | { 29 | var lengthBuffer = output.GetSpan(4); 30 | BinaryPrimitives.WriteInt32BigEndian(lengthBuffer, (int)message.Payload.Length); 31 | output.Advance(4); 32 | foreach (var memory in message.Payload) 33 | { 34 | output.Write(memory.Span); 35 | } 36 | } 37 | } 38 | 39 | public struct Message 40 | { 41 | public Message(byte[] payload) : this(new ReadOnlySequence(payload)) 42 | { 43 | 44 | } 45 | 46 | public Message(ReadOnlySequence payload) 47 | { 48 | Payload = payload; 49 | } 50 | 51 | public ReadOnlySequence Payload { get; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /samples/ServerApplication/MqttApplication.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Connections; 3 | using MQTTnet.Adapter; 4 | using MQTTnet.AspNetCore; 5 | using MQTTnet.Packets; 6 | using MQTTnet.Protocol; 7 | 8 | namespace ServerApplication 9 | { 10 | public class MqttApplication : ConnectionHandler 11 | { 12 | private MqttConnectionHandler _mqttHandler = new(); 13 | 14 | public MqttApplication() 15 | { 16 | _mqttHandler.ClientHandler = OnClientConnectedAsync; 17 | } 18 | 19 | public override async Task OnConnectedAsync(ConnectionContext connection) 20 | { 21 | await _mqttHandler.OnConnectedAsync(connection); 22 | } 23 | 24 | private async Task OnClientConnectedAsync(IMqttChannelAdapter adapter) 25 | { 26 | while (true) 27 | { 28 | var packet = await adapter.ReceivePacketAsync(default); 29 | 30 | switch (packet) 31 | { 32 | case MqttConnectPacket connectPacket: 33 | await adapter.SendPacketAsync(new MqttConnAckPacket 34 | { 35 | ReturnCode = MqttConnectReturnCode.ConnectionAccepted, 36 | ReasonCode = MqttConnectReasonCode.Success, 37 | IsSessionPresent = false 38 | }, 39 | default); 40 | break; 41 | case MqttDisconnectPacket disconnectPacket: 42 | break; 43 | case MqttAuthPacket mqttAuthPacket: 44 | break; 45 | case MqttConnAckPacket connAckPacket: 46 | break; 47 | case MqttPublishPacket mqttPublishPacket: 48 | break; 49 | case MqttSubscribePacket mqttSubscribePacket: 50 | var ack = new MqttSubAckPacket 51 | { 52 | PacketIdentifier = mqttSubscribePacket.PacketIdentifier 53 | }; 54 | ack.ReasonCodes.Add(MqttSubscribeReasonCode.GrantedQoS0); 55 | 56 | await adapter.SendPacketAsync(ack, default); 57 | break; 58 | default: 59 | break; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /samples/ServerApplication/MyCustomProtocol.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Bedrock.Framework.Protocols; 3 | using Microsoft.AspNetCore.Connections; 4 | using Microsoft.Extensions.Logging; 5 | using Protocols; 6 | 7 | namespace ServerApplication 8 | { 9 | public class MyCustomProtocol : ConnectionHandler 10 | { 11 | private readonly ILogger _logger; 12 | 13 | public MyCustomProtocol(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | public override async Task OnConnectedAsync(ConnectionContext connection) 19 | { 20 | // Use a length prefixed protocol 21 | var protocol = new LengthPrefixedProtocol(); 22 | var reader = connection.CreateReader(); 23 | var writer = connection.CreateWriter(); 24 | 25 | while (true) 26 | { 27 | try 28 | { 29 | var result = await reader.ReadAsync(protocol); 30 | var message = result.Message; 31 | 32 | _logger.LogInformation("Received a message of {Length} bytes", message.Payload.Length); 33 | 34 | if (result.IsCompleted) 35 | { 36 | break; 37 | } 38 | 39 | await writer.WriteAsync(protocol, message); 40 | } 41 | finally 42 | { 43 | reader.Advance(); 44 | } 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /samples/ServerApplication/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Security.Cryptography.X509Certificates; 3 | using Bedrock.Framework; 4 | using Microsoft.AspNetCore.Connections; 5 | using Microsoft.AspNetCore.SignalR; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | using ServerApplication; 10 | 11 | var builder = Host.CreateApplicationBuilder(); 12 | 13 | builder.Logging.SetMinimumLevel(LogLevel.Debug); 14 | 15 | builder.Services.AddSignalR(); 16 | 17 | builder.ConfigureServer(server => 18 | { 19 | server.UseSockets(sockets => 20 | { 21 | // Echo server 22 | sockets.ListenLocalhost(5000, 23 | builder => builder.UseConnectionLogging().UseConnectionHandler()); 24 | 25 | // HTTP/1.1 server 26 | sockets.Listen(IPAddress.Loopback, 5001, 27 | builder => builder.UseConnectionLogging().UseConnectionHandler()); 28 | 29 | // SignalR Hub 30 | sockets.Listen(IPAddress.Loopback, 5002, 31 | builder => builder.UseConnectionLogging().UseHub()); 32 | 33 | // MQTT application 34 | sockets.Listen(IPAddress.Loopback, 5003, 35 | builder => builder.UseConnectionLogging().UseConnectionHandler()); 36 | 37 | // Echo Server with TLS 38 | sockets.Listen(IPAddress.Loopback, 5004, 39 | builder => builder.UseServerTls(options => 40 | { 41 | options.LocalCertificate = X509CertificateLoader.LoadPkcs12FromFile("testcert.pfx", "testcert"); 42 | 43 | // NOTE: Do not do this in a production environment 44 | options.AllowAnyRemoteCertificate(); 45 | }) 46 | .UseConnectionLogging().UseConnectionHandler()); 47 | 48 | sockets.Listen(IPAddress.Loopback, 5005, 49 | builder => builder.UseConnectionLogging().UseConnectionHandler()); 50 | }); 51 | }); 52 | 53 | var host = builder.Build(); 54 | 55 | host.Run(); -------------------------------------------------------------------------------- /samples/ServerApplication/ServerApplication.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Bedrock.Framework.Experimental.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0;net9.0 5 | true 6 | Experimental protocols and transports for Bedrock.Framework. 7 | David Fowler 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Client/ClientExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Bedrock.Framework 3 | { 4 | public static class ClientExtensions 5 | { 6 | public static ClientBuilder UseConnectionPooling(this ClientBuilder builder) 7 | { 8 | // TODO right now, this must be called after UseSockets 9 | builder.Use(factory => new ConnectionPoolingFactory(factory)); 10 | return builder; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Infrastructure/BufferWriter.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.Runtime.CompilerServices; 7 | 8 | namespace Bedrock.Framework.Infrastructure 9 | { 10 | public ref struct BufferWriter where T : IBufferWriter 11 | { 12 | private T _output; 13 | private Span _span; 14 | private int _buffered; 15 | 16 | public BufferWriter(T output) 17 | { 18 | _buffered = 0; 19 | _output = output; 20 | _span = output.GetSpan(); 21 | } 22 | 23 | public Span Span => _span; 24 | 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public void Commit() 27 | { 28 | var buffered = _buffered; 29 | if (buffered > 0) 30 | { 31 | _buffered = 0; 32 | _output.Advance(buffered); 33 | } 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public void Advance(int count) 38 | { 39 | _buffered += count; 40 | _span = _span.Slice(count); 41 | } 42 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public void Write(ReadOnlySpan source) 45 | { 46 | if (_span.Length >= source.Length) 47 | { 48 | source.CopyTo(_span); 49 | Advance(source.Length); 50 | } 51 | else 52 | { 53 | WriteMultiBuffer(source); 54 | } 55 | } 56 | 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public void Ensure(int count = 1) 59 | { 60 | if (_span.Length < count) 61 | { 62 | EnsureMore(count); 63 | } 64 | } 65 | 66 | [MethodImpl(MethodImplOptions.NoInlining)] 67 | private void EnsureMore(int count = 0) 68 | { 69 | if (_buffered > 0) 70 | { 71 | Commit(); 72 | } 73 | 74 | _span = _output.GetSpan(count); 75 | } 76 | 77 | private void WriteMultiBuffer(ReadOnlySpan source) 78 | { 79 | while (source.Length > 0) 80 | { 81 | if (_span.Length == 0) 82 | { 83 | EnsureMore(); 84 | } 85 | 86 | var writable = Math.Min(source.Length, _span.Length); 87 | source.Slice(0, writable).CopyTo(_span); 88 | source = source.Slice(writable); 89 | Advance(writable); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Infrastructure/SequenceReaderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System.Buffers 2 | { 3 | internal static class SequenceReaderExtensions 4 | { 5 | public static bool TryReadTo(this ref SequenceReader sequenceReader, out ReadOnlySpan span, ReadOnlySpan delimiter, bool advancePastDelimiter = true) where T : unmanaged, IEquatable 6 | { 7 | if (sequenceReader.TryReadTo(out ReadOnlySequence sequence, delimiter, advancePastDelimiter)) 8 | { 9 | span = sequence.IsSingleSegment ? sequence.FirstSpan : sequence.ToArray(); 10 | return true; 11 | } 12 | span = default; 13 | return false; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Infrastructure/TaskCompletionSourceWithCancellation.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Bedrock.Framework.Infrastructure 5 | { 6 | internal class TaskCompletionSourceWithCancellation : TaskCompletionSource 7 | { 8 | private CancellationToken _cancellationToken; 9 | 10 | public TaskCompletionSourceWithCancellation() : base(TaskCreationOptions.RunContinuationsAsynchronously) 11 | { 12 | } 13 | 14 | private void OnCancellation() 15 | { 16 | TrySetCanceled(_cancellationToken); 17 | } 18 | 19 | public async ValueTask WaitWithCancellationAsync(CancellationToken cancellationToken) 20 | { 21 | _cancellationToken = cancellationToken; 22 | using (cancellationToken.UnsafeRegister(s => ((TaskCompletionSourceWithCancellation)s).OnCancellation(), this)) 23 | { 24 | return await Task.ConfigureAwait(false); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/ContentLengthHttpBodyReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Bedrock.Framework.Protocols 7 | { 8 | public class ContentLengthHttpBodyReader : IMessageReader> 9 | { 10 | private long _remaining; 11 | 12 | public ContentLengthHttpBodyReader(long contentLength) 13 | { 14 | _remaining = contentLength; 15 | } 16 | 17 | public bool TryParseMessage(in ReadOnlySequence input, ref SequencePosition consumed, ref SequencePosition examined, out ReadOnlySequence message) 18 | { 19 | if (_remaining == 0) 20 | { 21 | message = default; 22 | return true; 23 | } 24 | 25 | var read = Math.Min(_remaining, input.Length); 26 | 27 | _remaining -= read; 28 | 29 | message = input.Slice(0, read); 30 | consumed = message.End; 31 | examined = consumed; 32 | 33 | return message.Length > 0; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http1Header.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Bedrock.Framework.Protocols.Http.Http1 4 | { 5 | public readonly struct Http1Header 6 | { 7 | private readonly ReadOnlyMemory _name; 8 | private readonly ReadOnlyMemory _value; 9 | 10 | public Http1Header(ReadOnlyMemory name, ReadOnlyMemory value) 11 | { 12 | _name = name; 13 | _value = value; 14 | } 15 | 16 | public ReadOnlySpan Name => _name.Span; 17 | public ReadOnlySpan Value => _value.Span; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http1RequestMessageWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Diagnostics; 4 | using System.Net.Http; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using Bedrock.Framework.Infrastructure; 8 | 9 | namespace Bedrock.Framework.Protocols 10 | { 11 | public class Http1RequestMessageWriter : IMessageWriter 12 | { 13 | private ReadOnlySpan Http11 => new byte[] { (byte)'H', (byte)'T', (byte)'T', (byte)'P', (byte)'/', (byte)'1', (byte)'.', (byte)'1' }; 14 | private ReadOnlySpan NewLine => new byte[] { (byte)'\r', (byte)'\n' }; 15 | private ReadOnlySpan Space => new byte[] { (byte)' ' }; 16 | private ReadOnlySpan Colon => new byte[] { (byte)':' }; 17 | private ReadOnlySpan Host => new byte[] { (byte)'H', (byte)'o', (byte)'s', (byte)'t' }; 18 | 19 | private readonly byte[] _hostHeaderValueBytes; 20 | 21 | public Http1RequestMessageWriter(string host, int port) 22 | { 23 | // Precalculate ASCII bytes for Host header 24 | string hostHeader = $"{host}:{port}"; 25 | _hostHeaderValueBytes = Encoding.ASCII.GetBytes(hostHeader); 26 | } 27 | 28 | public void WriteMessage(HttpRequestMessage message, IBufferWriter output) 29 | { 30 | Debug.Assert(message.Method != null); 31 | Debug.Assert(message.RequestUri != null); 32 | 33 | var writer = new BufferWriter>(output); 34 | writer.WriteAsciiNoValidation(message.Method.Method); 35 | writer.Write(Space); 36 | // REVIEW: This isn't right 37 | writer.WriteAsciiNoValidation(message.RequestUri.ToString()); 38 | writer.Write(Space); 39 | writer.Write(Http11); 40 | writer.Write(NewLine); 41 | 42 | if (message.Headers == null || message.Headers.Host == null) 43 | { 44 | writer.Write(Host); 45 | writer.Write(Colon); 46 | writer.Write(Space); 47 | writer.Write(_hostHeaderValueBytes); 48 | writer.Write(NewLine); 49 | } 50 | 51 | foreach (var header in message.Headers) 52 | { 53 | foreach (var value in header.Value) 54 | { 55 | writer.WriteAsciiNoValidation(header.Key); 56 | writer.Write(Colon); 57 | writer.Write(Space); 58 | writer.WriteAsciiNoValidation(value); 59 | writer.Write(NewLine); 60 | } 61 | } 62 | 63 | if (message.Content != null) 64 | { 65 | foreach (var header in message.Content.Headers) 66 | { 67 | foreach (var value in header.Value) 68 | { 69 | writer.WriteAsciiNoValidation(header.Key); 70 | writer.Write(Colon); 71 | writer.Write(Space); 72 | writer.WriteAsciiNoValidation(value); 73 | writer.Write(NewLine); 74 | } 75 | } 76 | } 77 | 78 | writer.Write(NewLine); 79 | writer.Commit(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http1ResponseMessageWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Net.Http; 4 | using System.Runtime.InteropServices; 5 | using Bedrock.Framework.Infrastructure; 6 | 7 | namespace Bedrock.Framework.Protocols 8 | { 9 | public class Http1ResponseMessageWriter : IMessageWriter 10 | { 11 | private ReadOnlySpan NewLine => new byte[] { (byte)'\r', (byte)'\n' }; 12 | private ReadOnlySpan Space => new byte[] { (byte)' ' }; 13 | 14 | public void WriteMessage(HttpResponseMessage message, IBufferWriter output) 15 | { 16 | var writer = new BufferWriter>(output); 17 | writer.WriteAsciiNoValidation("HTTP/1.1"); 18 | writer.Write(Space); 19 | writer.WriteNumeric((uint)message.StatusCode); 20 | writer.Write(Space); 21 | writer.WriteAsciiNoValidation(message.StatusCode.ToString()); 22 | writer.Write(NewLine); 23 | 24 | var colon = (byte)':'; 25 | 26 | foreach (var header in message.Headers) 27 | { 28 | foreach (var value in header.Value) 29 | { 30 | writer.WriteAsciiNoValidation(header.Key); 31 | writer.Write(MemoryMarshal.CreateReadOnlySpan(ref colon, 1)); 32 | writer.Write(Space); 33 | writer.WriteAsciiNoValidation(value); 34 | writer.Write(NewLine); 35 | } 36 | } 37 | 38 | if (message.Content != null) 39 | { 40 | // Access the property to compute the content length (if there is any) 41 | _ = message.Content.Headers.ContentLength; 42 | 43 | foreach (var header in message.Content.Headers) 44 | { 45 | foreach (var value in header.Value) 46 | { 47 | writer.WriteAsciiNoValidation(header.Key); 48 | writer.Write(MemoryMarshal.CreateReadOnlySpan(ref colon, 1)); 49 | writer.Write(Space); 50 | writer.WriteAsciiNoValidation(value); 51 | writer.Write(NewLine); 52 | } 53 | } 54 | } 55 | 56 | writer.Write(NewLine); 57 | writer.Commit(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Bitshifter.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.Binary; 6 | using System.Diagnostics; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace Bedrock.Framework.Protocols.Http2 10 | { 11 | // Mimics BinaryPrimitives with oddly sized units 12 | internal static class Bitshifter 13 | { 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public static uint ReadUInt24BigEndian(ReadOnlySpan source) 16 | { 17 | return (uint)((source[0] << 16) | (source[1] << 8) | source[2]); 18 | } 19 | 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | public static void WriteUInt24BigEndian(Span destination, uint value) 22 | { 23 | Debug.Assert(value <= 0xFF_FF_FF, value.ToString()); 24 | destination[0] = (byte)((value & 0xFF_00_00) >> 16); 25 | destination[1] = (byte)((value & 0x00_FF_00) >> 8); 26 | destination[2] = (byte)(value & 0x00_00_FF); 27 | } 28 | 29 | // Drops the highest order bit 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public static uint ReadUInt31BigEndian(ReadOnlySpan source) 32 | { 33 | return BinaryPrimitives.ReadUInt32BigEndian(source) & 0x7F_FF_FF_FF; 34 | } 35 | 36 | // Does not overwrite the highest order bit 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | public static void WriteUInt31BigEndian(Span destination, uint value) 39 | => WriteUInt31BigEndian(destination, value, true); 40 | 41 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 42 | public static void WriteUInt31BigEndian(Span destination, uint value, bool preserveHighestBit) 43 | { 44 | Debug.Assert(value <= 0x7F_FF_FF_FF, value.ToString()); 45 | 46 | if (preserveHighestBit) 47 | { 48 | // Keep the highest bit 49 | value |= (destination[0] & 0x80u) << 24; 50 | } 51 | 52 | BinaryPrimitives.WriteUInt32BigEndian(destination, value); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/FlowControl/FlowControl.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.Diagnostics; 5 | 6 | namespace Bedrock.Framework.Protocols.Http2.FlowControl 7 | { 8 | internal struct FlowControl 9 | { 10 | public FlowControl(uint initialWindowSize) 11 | { 12 | Debug.Assert(initialWindowSize <= Http2PeerSettings.MaxWindowSize, $"{nameof(initialWindowSize)} too large."); 13 | 14 | Available = (int)initialWindowSize; 15 | IsAborted = false; 16 | } 17 | 18 | public int Available { get; private set; } 19 | public bool IsAborted { get; private set; } 20 | 21 | public void Advance(int bytes) 22 | { 23 | Debug.Assert(!IsAborted, $"({nameof(Advance)} called after abort."); 24 | Debug.Assert(bytes == 0 || (bytes > 0 && bytes <= Available), $"{nameof(Advance)}({bytes}) called with {Available} bytes available."); 25 | 26 | Available -= bytes; 27 | } 28 | 29 | // bytes can be negative when SETTINGS_INITIAL_WINDOW_SIZE decreases mid-connection. 30 | // This can also cause Available to become negative which MUST be allowed. 31 | // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.2 32 | public bool TryUpdateWindow(int bytes) 33 | { 34 | var maxUpdate = Http2PeerSettings.MaxWindowSize - Available; 35 | 36 | if (bytes > maxUpdate) 37 | { 38 | return false; 39 | } 40 | 41 | Available += bytes; 42 | 43 | return true; 44 | } 45 | 46 | public void Abort() 47 | { 48 | IsAborted = true; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/FlowControl/OutputFlowControl.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.Collections.Generic; 5 | using System.Diagnostics; 6 | 7 | namespace Bedrock.Framework.Protocols.Http2.FlowControl 8 | { 9 | internal class OutputFlowControl 10 | { 11 | private FlowControl _flow; 12 | private Queue _awaitableQueue; 13 | 14 | public OutputFlowControl(uint initialWindowSize) 15 | { 16 | _flow = new FlowControl(initialWindowSize); 17 | } 18 | 19 | public int Available => _flow.Available; 20 | public bool IsAborted => _flow.IsAborted; 21 | 22 | public OutputFlowControlAwaitable AvailabilityAwaitable 23 | { 24 | get 25 | { 26 | Debug.Assert(!_flow.IsAborted, $"({nameof(AvailabilityAwaitable)} accessed after abort."); 27 | Debug.Assert(_flow.Available <= 0, $"({nameof(AvailabilityAwaitable)} accessed with {Available} bytes available."); 28 | 29 | if (_awaitableQueue == null) 30 | { 31 | _awaitableQueue = new Queue(); 32 | } 33 | 34 | var awaitable = new OutputFlowControlAwaitable(); 35 | _awaitableQueue.Enqueue(awaitable); 36 | return awaitable; 37 | } 38 | } 39 | 40 | public void Advance(int bytes) 41 | { 42 | _flow.Advance(bytes); 43 | } 44 | 45 | // bytes can be negative when SETTINGS_INITIAL_WINDOW_SIZE decreases mid-connection. 46 | // This can also cause Available to become negative which MUST be allowed. 47 | // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.2 48 | public bool TryUpdateWindow(int bytes) 49 | { 50 | if (_flow.TryUpdateWindow(bytes)) 51 | { 52 | while (_flow.Available > 0 && _awaitableQueue?.Count > 0) 53 | { 54 | _awaitableQueue.Dequeue().Complete(); 55 | } 56 | 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | public void Abort() 64 | { 65 | // Make sure to set the aborted flag before running any continuations. 66 | _flow.Abort(); 67 | 68 | while (_awaitableQueue?.Count > 0) 69 | { 70 | _awaitableQueue.Dequeue().Complete(); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/FlowControl/OutputFlowControlAwaitable.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.Diagnostics; 6 | using System.Runtime.CompilerServices; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Bedrock.Framework.Protocols.Http2.FlowControl 11 | { 12 | internal class OutputFlowControlAwaitable : ICriticalNotifyCompletion 13 | { 14 | private static readonly Action _callbackCompleted = () => { }; 15 | 16 | private Action _callback; 17 | 18 | public OutputFlowControlAwaitable GetAwaiter() => this; 19 | public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); 20 | 21 | public void GetResult() 22 | { 23 | Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); 24 | 25 | _callback = null; 26 | } 27 | 28 | public void OnCompleted(Action continuation) 29 | { 30 | if (ReferenceEquals(_callback, _callbackCompleted) || 31 | ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted)) 32 | { 33 | Task.Run(continuation); 34 | } 35 | } 36 | 37 | public void UnsafeOnCompleted(Action continuation) 38 | { 39 | OnCompleted(continuation); 40 | } 41 | 42 | public void Complete() 43 | { 44 | var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); 45 | 46 | continuation?.Invoke(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/HPack/HPackDecodingException.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 | 6 | namespace Bedrock.Framework.Protocols.Http2.HPack 7 | { 8 | internal sealed class HPackDecodingException : Exception 9 | { 10 | public HPackDecodingException(string message) 11 | : base(message) 12 | { 13 | } 14 | public HPackDecodingException(string message, Exception innerException) 15 | : base(message, innerException) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/HPack/HPackEncodingException.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 | 6 | namespace Bedrock.Framework.Protocols.Http2.HPack 7 | { 8 | internal sealed class HPackEncodingException : Exception 9 | { 10 | public HPackEncodingException(string message) 11 | : base(message) 12 | { 13 | } 14 | public HPackEncodingException(string message, Exception innerException) 15 | : base(message, innerException) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/HPack/HeaderField.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 | 6 | namespace Bedrock.Framework.Protocols.Http2.HPack 7 | { 8 | internal readonly struct HeaderField 9 | { 10 | // http://httpwg.org/specs/rfc7541.html#rfc.section.4.1 11 | public const int RfcOverhead = 32; 12 | 13 | public HeaderField(Span name, Span value) 14 | { 15 | Name = new byte[name.Length]; 16 | name.CopyTo(Name); 17 | 18 | Value = new byte[value.Length]; 19 | value.CopyTo(Value); 20 | } 21 | 22 | public byte[] Name { get; } 23 | 24 | public byte[] Value { get; } 25 | 26 | public int Length => GetLength(Name.Length, Value.Length); 27 | 28 | public static int GetLength(int nameLength, int valueLength) => nameLength + valueLength + 32; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/HPack/HuffmanDecodingException.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 | 6 | namespace Bedrock.Framework.Protocols.Http2.HPack 7 | { 8 | internal sealed class HuffmanDecodingException : Exception 9 | { 10 | public HuffmanDecodingException(string message) 11 | : base(message) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/HPack/IntegerDecoder.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 | namespace Bedrock.Framework.Protocols.Http2.HPack 5 | { 6 | /// 7 | /// The maximum we will decode is Int32.MaxValue, which is also the maximum request header field size. 8 | /// 9 | internal class IntegerDecoder 10 | { 11 | private int _i; 12 | private int _m; 13 | 14 | /// 15 | /// Callers must ensure higher bits above the prefix are cleared before calling this method. 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | public bool BeginTryDecode(byte b, int prefixLength, out int result) 22 | { 23 | if (b < ((1 << prefixLength) - 1)) 24 | { 25 | result = b; 26 | return true; 27 | } 28 | 29 | _i = b; 30 | _m = 0; 31 | result = 0; 32 | return false; 33 | } 34 | 35 | public bool TryDecode(byte b, out int result) 36 | { 37 | var m = _m; // Enregister 38 | var i = _i + ((b & 0x7f) << m); // Enregister 39 | 40 | if ((b & 0x80) == 0) 41 | { 42 | // Int32.MaxValue only needs a maximum of 5 bytes to represent and the last byte cannot have any value set larger than 0x7 43 | if ((m > 21 && b > 0x7) || i < 0) 44 | { 45 | ThrowIntegerTooBigException(); 46 | } 47 | 48 | result = i; 49 | return true; 50 | } 51 | else if (m > 21) 52 | { 53 | // Int32.MaxValue only needs a maximum of 5 bytes to represent 54 | ThrowIntegerTooBigException(); 55 | } 56 | 57 | _m = m + 7; 58 | _i = i; 59 | 60 | result = 0; 61 | return false; 62 | } 63 | 64 | public static void ThrowIntegerTooBigException() 65 | => throw new HPackDecodingException("CoreStrings.HPackErrorIntegerTooBig"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/HPack/IntegerEncoder.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.Diagnostics; 6 | 7 | namespace Bedrock.Framework.Protocols.Http2.HPack 8 | { 9 | internal static class IntegerEncoder 10 | { 11 | public static bool Encode(int i, int n, Span buffer, out int length) 12 | { 13 | Debug.Assert(i >= 0); 14 | Debug.Assert(n >= 1 && n <= 8); 15 | 16 | var j = 0; 17 | length = 0; 18 | 19 | if (buffer.Length == 0) 20 | { 21 | return false; 22 | } 23 | 24 | if (i < (1 << n) - 1) 25 | { 26 | buffer[j] &= MaskHigh(8 - n); 27 | buffer[j++] |= (byte)i; 28 | } 29 | else 30 | { 31 | buffer[j] &= MaskHigh(8 - n); 32 | buffer[j++] |= (byte)((1 << n) - 1); 33 | 34 | if (j == buffer.Length) 35 | { 36 | return false; 37 | } 38 | 39 | i -= ((1 << n) - 1); 40 | while (i >= 128) 41 | { 42 | var ui = (uint)i; // Use unsigned for optimizations 43 | buffer[j++] = (byte)((ui % 128) + 128); 44 | 45 | if (j >= buffer.Length) 46 | { 47 | return false; 48 | } 49 | 50 | i = (int)(ui / 128); // Jit converts unsigned divide by power-of-2 constant to clean shift 51 | } 52 | buffer[j++] = (byte)i; 53 | } 54 | 55 | length = j; 56 | return true; 57 | } 58 | 59 | private static byte MaskHigh(int n) 60 | { 61 | return (byte)(sbyte.MinValue >> (n - 1)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2ConnectionErrorException.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 | 6 | namespace Bedrock.Framework.Protocols.Http2 7 | { 8 | internal sealed class Http2ConnectionErrorException : Exception 9 | { 10 | public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode) 11 | : base($"HTTP/2 connection error ({errorCode}): {message}") 12 | { 13 | ErrorCode = errorCode; 14 | } 15 | 16 | public Http2ErrorCode ErrorCode { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2ContinuationFrameFlags.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 | 6 | namespace Bedrock.Framework.Protocols.Http2 7 | { 8 | [Flags] 9 | internal enum Http2ContinuationFrameFlags : byte 10 | { 11 | NONE = 0x0, 12 | END_HEADERS = 0x4, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2DataFrameFlags.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 | 6 | namespace Bedrock.Framework.Protocols.Http2 7 | { 8 | [Flags] 9 | internal enum Http2DataFrameFlags : byte 10 | { 11 | NONE = 0x0, 12 | END_STREAM = 0x1, 13 | PADDED = 0x8 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2ErrorCode.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 | 5 | namespace Bedrock.Framework.Protocols.Http2 6 | { 7 | internal enum Http2ErrorCode : uint 8 | { 9 | NO_ERROR = 0x0, 10 | PROTOCOL_ERROR = 0x1, 11 | INTERNAL_ERROR = 0x2, 12 | FLOW_CONTROL_ERROR = 0x3, 13 | SETTINGS_TIMEOUT = 0x4, 14 | STREAM_CLOSED = 0x5, 15 | FRAME_SIZE_ERROR = 0x6, 16 | REFUSED_STREAM = 0x7, 17 | CANCEL = 0x8, 18 | COMPRESSION_ERROR = 0x9, 19 | CONNECT_ERROR = 0xa, 20 | ENHANCE_YOUR_CALM = 0xb, 21 | INADEQUATE_SECURITY = 0xc, 22 | HTTP_1_1_REQUIRED = 0xd, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.Continuation.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* https://tools.ietf.org/html/rfc7540#section-6.10 7 | +---------------------------------------------------------------+ 8 | | Header Block Fragment (*) ... 9 | +---------------------------------------------------------------+ 10 | */ 11 | internal partial class Http2Frame 12 | { 13 | public Http2ContinuationFrameFlags ContinuationFlags 14 | { 15 | get => (Http2ContinuationFrameFlags)Flags; 16 | set => Flags = (byte)value; 17 | } 18 | 19 | public bool ContinuationEndHeaders => (ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS; 20 | 21 | public void PrepareContinuation(Http2ContinuationFrameFlags flags, int streamId) 22 | { 23 | PayloadLength = 0; 24 | Type = Http2FrameType.CONTINUATION; 25 | ContinuationFlags = flags; 26 | StreamId = streamId; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.Data.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* 7 | +---------------+ 8 | |Pad Length? (8)| 9 | +---------------+-----------------------------------------------+ 10 | | Data (*) ... 11 | +---------------------------------------------------------------+ 12 | | Padding (*) ... 13 | +---------------------------------------------------------------+ 14 | */ 15 | internal partial class Http2Frame 16 | { 17 | public Http2DataFrameFlags DataFlags 18 | { 19 | get => (Http2DataFrameFlags)Flags; 20 | set => Flags = (byte)value; 21 | } 22 | 23 | public bool DataEndStream => (DataFlags & Http2DataFrameFlags.END_STREAM) == Http2DataFrameFlags.END_STREAM; 24 | 25 | public bool DataHasPadding => (DataFlags & Http2DataFrameFlags.PADDED) == Http2DataFrameFlags.PADDED; 26 | 27 | public byte DataPadLength { get; set; } 28 | 29 | private int DataPayloadOffset => DataHasPadding ? 1 : 0; 30 | 31 | public int DataPayloadLength => PayloadLength - DataPayloadOffset - DataPadLength; 32 | 33 | public void PrepareData(int streamId, byte? padLength = null) 34 | { 35 | PayloadLength = 0; 36 | Type = Http2FrameType.DATA; 37 | DataFlags = padLength.HasValue ? Http2DataFrameFlags.PADDED : Http2DataFrameFlags.NONE; 38 | StreamId = streamId; 39 | DataPadLength = padLength ?? 0; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.GoAway.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* https://tools.ietf.org/html/rfc7540#section-6.8 7 | +-+-------------------------------------------------------------+ 8 | |R| Last-Stream-ID (31) | 9 | +-+-------------------------------------------------------------+ 10 | | Error Code (32) | 11 | +---------------------------------------------------------------+ 12 | | Additional Debug Data (*) | 13 | +---------------------------------------------------------------+ 14 | */ 15 | internal partial class Http2Frame 16 | { 17 | public int GoAwayLastStreamId { get; set; } 18 | 19 | public Http2ErrorCode GoAwayErrorCode { get; set; } 20 | 21 | public void PrepareGoAway(int lastStreamId, Http2ErrorCode errorCode) 22 | { 23 | PayloadLength = 8; 24 | Type = Http2FrameType.GOAWAY; 25 | Flags = 0; 26 | StreamId = 0; 27 | GoAwayLastStreamId = lastStreamId; 28 | GoAwayErrorCode = errorCode; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.Headers.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* https://tools.ietf.org/html/rfc7540#section-6.2 7 | +---------------+ 8 | |Pad Length? (8)| 9 | +-+-------------+-----------------------------------------------+ 10 | |E| Stream Dependency? (31) | 11 | +-+-------------+-----------------------------------------------+ 12 | | Weight? (8) | 13 | +-+-------------+-----------------------------------------------+ 14 | | Header Block Fragment (*) ... 15 | +---------------------------------------------------------------+ 16 | | Padding (*) ... 17 | +---------------------------------------------------------------+ 18 | */ 19 | internal partial class Http2Frame 20 | { 21 | public Http2HeadersFrameFlags HeadersFlags 22 | { 23 | get => (Http2HeadersFrameFlags)Flags; 24 | set => Flags = (byte)value; 25 | } 26 | 27 | public bool HeadersEndHeaders => (HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS; 28 | 29 | public bool HeadersEndStream => (HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM; 30 | 31 | public bool HeadersHasPadding => (HeadersFlags & Http2HeadersFrameFlags.PADDED) == Http2HeadersFrameFlags.PADDED; 32 | 33 | public bool HeadersHasPriority => (HeadersFlags & Http2HeadersFrameFlags.PRIORITY) == Http2HeadersFrameFlags.PRIORITY; 34 | 35 | public byte HeadersPadLength { get; set; } 36 | 37 | public int HeadersStreamDependency { get; set; } 38 | 39 | public byte HeadersPriorityWeight { get; set; } 40 | 41 | private int HeadersPayloadOffset => (HeadersHasPadding ? 1 : 0) + (HeadersHasPriority ? 5 : 0); 42 | 43 | public int HeadersPayloadLength => PayloadLength - HeadersPayloadOffset - HeadersPadLength; 44 | 45 | public void PrepareHeaders(Http2HeadersFrameFlags flags, int streamId) 46 | { 47 | PayloadLength = 0; 48 | Type = Http2FrameType.HEADERS; 49 | HeadersFlags = flags; 50 | StreamId = streamId; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.Ping.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* https://tools.ietf.org/html/rfc7540#section-6.7 7 | +---------------------------------------------------------------+ 8 | | | 9 | | Opaque Data (64) | 10 | | | 11 | +---------------------------------------------------------------+ 12 | */ 13 | internal partial class Http2Frame 14 | { 15 | public Http2PingFrameFlags PingFlags 16 | { 17 | get => (Http2PingFrameFlags)Flags; 18 | set => Flags = (byte)value; 19 | } 20 | 21 | public bool PingAck => (PingFlags & Http2PingFrameFlags.ACK) == Http2PingFrameFlags.ACK; 22 | 23 | public void PreparePing(Http2PingFrameFlags flags) 24 | { 25 | PayloadLength = 8; 26 | Type = Http2FrameType.PING; 27 | PingFlags = flags; 28 | StreamId = 0; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.Priority.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* https://tools.ietf.org/html/rfc7540#section-6.3 7 | +-+-------------------------------------------------------------+ 8 | |E| Stream Dependency (31) | 9 | +-+-------------+-----------------------------------------------+ 10 | | Weight (8) | 11 | +-+-------------+ 12 | */ 13 | internal partial class Http2Frame 14 | { 15 | public int PriorityStreamDependency { get; set; } 16 | 17 | public bool PriorityIsExclusive { get; set; } 18 | 19 | public byte PriorityWeight { get; set; } 20 | 21 | public void PreparePriority(int streamId, int streamDependency, bool exclusive, byte weight) 22 | { 23 | PayloadLength = 5; 24 | Type = Http2FrameType.PRIORITY; 25 | StreamId = streamId; 26 | PriorityStreamDependency = streamDependency; 27 | PriorityIsExclusive = exclusive; 28 | PriorityWeight = weight; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.RstStream.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* https://tools.ietf.org/html/rfc7540#section-6.4 7 | +---------------------------------------------------------------+ 8 | | Error Code (32) | 9 | +---------------------------------------------------------------+ 10 | */ 11 | internal partial class Http2Frame 12 | { 13 | public Http2ErrorCode RstStreamErrorCode { get; set; } 14 | 15 | public void PrepareRstStream(int streamId, Http2ErrorCode errorCode) 16 | { 17 | PayloadLength = 4; 18 | Type = Http2FrameType.RST_STREAM; 19 | Flags = 0; 20 | StreamId = streamId; 21 | RstStreamErrorCode = errorCode; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.Settings.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* https://tools.ietf.org/html/rfc7540#section-6.5.1 7 | List of: 8 | +-------------------------------+ 9 | | Identifier (16) | 10 | +-------------------------------+-------------------------------+ 11 | | Value (32) | 12 | +---------------------------------------------------------------+ 13 | */ 14 | internal partial class Http2Frame 15 | { 16 | public Http2SettingsFrameFlags SettingsFlags 17 | { 18 | get => (Http2SettingsFrameFlags)Flags; 19 | set => Flags = (byte)value; 20 | } 21 | 22 | public bool SettingsAck => (SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK; 23 | 24 | public void PrepareSettings(Http2SettingsFrameFlags flags) 25 | { 26 | PayloadLength = 0; 27 | Type = Http2FrameType.SETTINGS; 28 | SettingsFlags = flags; 29 | StreamId = 0; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.WindowUpdate.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* https://tools.ietf.org/html/rfc7540#section-6.9 7 | +-+-------------------------------------------------------------+ 8 | |R| Window Size Increment (31) | 9 | +-+-------------------------------------------------------------+ 10 | */ 11 | internal partial class Http2Frame 12 | { 13 | public int WindowUpdateSizeIncrement { get; set; } 14 | 15 | public void PrepareWindowUpdate(int streamId, int sizeIncrement) 16 | { 17 | PayloadLength = 4; 18 | Type = Http2FrameType.WINDOW_UPDATE; 19 | Flags = 0; 20 | StreamId = streamId; 21 | WindowUpdateSizeIncrement = sizeIncrement; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2Frame.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | /* https://tools.ietf.org/html/rfc7540#section-4.1 7 | +-----------------------------------------------+ 8 | | Length (24) | 9 | +---------------+---------------+---------------+ 10 | | Type (8) | Flags (8) | 11 | +-+-------------+---------------+-------------------------------+ 12 | |R| Stream Identifier (31) | 13 | +=+=============================================================+ 14 | | Frame Payload (0...) ... 15 | +---------------------------------------------------------------+ 16 | */ 17 | internal partial class Http2Frame 18 | { 19 | public int PayloadLength { get; set; } 20 | 21 | public Http2FrameType Type { get; set; } 22 | 23 | public byte Flags { get; set; } 24 | 25 | public int StreamId { get; set; } 26 | 27 | internal object ShowFlags() 28 | { 29 | switch (Type) 30 | { 31 | case Http2FrameType.CONTINUATION: 32 | return ContinuationFlags; 33 | case Http2FrameType.DATA: 34 | return DataFlags; 35 | case Http2FrameType.HEADERS: 36 | return HeadersFlags; 37 | case Http2FrameType.SETTINGS: 38 | return SettingsFlags; 39 | case Http2FrameType.PING: 40 | return PingFlags; 41 | 42 | // Not Implemented 43 | case Http2FrameType.PUSH_PROMISE: 44 | 45 | // No flags defined 46 | case Http2FrameType.PRIORITY: 47 | case Http2FrameType.RST_STREAM: 48 | case Http2FrameType.GOAWAY: 49 | case Http2FrameType.WINDOW_UPDATE: 50 | default: 51 | return $"0x{Flags:x}"; 52 | } 53 | } 54 | 55 | public override string ToString() 56 | { 57 | return $"{Type} Stream: {StreamId} Length: {PayloadLength} Flags: {ShowFlags()}"; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2FrameType.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | internal enum Http2FrameType : byte 7 | { 8 | DATA = 0x0, 9 | HEADERS = 0x1, 10 | PRIORITY = 0x2, 11 | RST_STREAM = 0x3, 12 | SETTINGS = 0x4, 13 | PUSH_PROMISE = 0x5, 14 | PING = 0x6, 15 | GOAWAY = 0x7, 16 | WINDOW_UPDATE = 0x8, 17 | CONTINUATION = 0x9 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2HeadersFrameFlags.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 | 6 | namespace Bedrock.Framework.Protocols.Http2 7 | { 8 | [Flags] 9 | internal enum Http2HeadersFrameFlags : byte 10 | { 11 | NONE = 0x0, 12 | END_STREAM = 0x1, 13 | END_HEADERS = 0x4, 14 | PADDED = 0x8, 15 | PRIORITY = 0x20 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2PeerSetting.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | internal readonly struct Http2PeerSetting 7 | { 8 | public Http2PeerSetting(Http2SettingsParameter parameter, uint value) 9 | { 10 | Parameter = parameter; 11 | Value = value; 12 | } 13 | 14 | public Http2SettingsParameter Parameter { get; } 15 | 16 | public uint Value { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2PingFrameFlags.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 | 6 | namespace Bedrock.Framework.Protocols.Http2 7 | { 8 | [Flags] 9 | internal enum Http2PingFrameFlags : byte 10 | { 11 | NONE = 0x0, 12 | ACK = 0x1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2SettingsFrameFlags.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 | 6 | namespace Bedrock.Framework.Protocols.Http2 7 | { 8 | [Flags] 9 | internal enum Http2SettingsFrameFlags : byte 10 | { 11 | NONE = 0x0, 12 | ACK = 0x1, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2SettingsParameter.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 | namespace Bedrock.Framework.Protocols.Http2 5 | { 6 | internal enum Http2SettingsParameter : ushort 7 | { 8 | SETTINGS_HEADER_TABLE_SIZE = 0x1, 9 | SETTINGS_ENABLE_PUSH = 0x2, 10 | SETTINGS_MAX_CONCURRENT_STREAMS = 0x3, 11 | SETTINGS_INITIAL_WINDOW_SIZE = 0x4, 12 | SETTINGS_MAX_FRAME_SIZE = 0x5, 13 | SETTINGS_MAX_HEADER_LIST_SIZE = 0x6, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2/Http2SettingsParameterOutOfRangeException.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 | 6 | namespace Bedrock.Framework.Protocols.Http2 7 | { 8 | internal sealed class Http2SettingsParameterOutOfRangeException : Exception 9 | { 10 | public Http2SettingsParameterOutOfRangeException(Http2SettingsParameter parameter, long lowerBound, long upperBound) 11 | : base($"HTTP/2 SETTINGS parameter {parameter} must be set to a value between {lowerBound} and {upperBound}") 12 | { 13 | Parameter = parameter; 14 | } 15 | 16 | public Http2SettingsParameter Parameter { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Http2ClientProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Bedrock.Framework.Protocols.Http2; 8 | using Microsoft.AspNetCore.Connections; 9 | 10 | namespace Bedrock.Framework.Protocols 11 | { 12 | internal class Http2ClientProtocol : Http2Protocol 13 | { 14 | private const int DefaultInitialWindowSize = 65535; 15 | 16 | // We don't really care about limiting control flow at the connection level. 17 | // We limit it per stream, and the user controls how many streams are created. 18 | // So set the connection window size to a large value. 19 | private const int ConnectionWindowSize = 64 * 1024 * 1024; 20 | 21 | private bool _expectingSettingsAck; 22 | 23 | public Http2ClientProtocol(ConnectionContext connection) : base(connection) 24 | { 25 | _ = ProcessFramesAsync(); 26 | } 27 | 28 | public ValueTask SendAsync(HttpRequestMessage requestMessage, HttpCompletionOption completionOption = HttpCompletionOption.ResponseHeadersRead) 29 | { 30 | return default; 31 | } 32 | 33 | protected override async ValueTask ProcessFramesAsync() 34 | { 35 | _connection.Transport.Output.Write(ClientPreface); 36 | 37 | await FrameWriter.WriteSettingsAsync(new List 38 | { 39 | // First setting: Disable push promise 40 | new Http2PeerSetting(Http2SettingsParameter.SETTINGS_ENABLE_PUSH, 0), 41 | // Second setting: Set header table size to 0 to disable dynamic header compression 42 | new Http2PeerSetting(Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE, 0) 43 | }); 44 | 45 | await FrameWriter.WriteWindowUpdateAsync(0, ConnectionWindowSize - DefaultInitialWindowSize); 46 | 47 | await FrameWriter.FlushAsync(); 48 | 49 | _expectingSettingsAck = true; 50 | 51 | await base.ProcessFramesAsync(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/HttpBodyContent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Bedrock.Framework.Protocols 11 | { 12 | internal class HttpBodyContent : HttpContent 13 | { 14 | private Stream _stream; 15 | 16 | protected override Task CreateContentReadStreamAsync() 17 | { 18 | return Task.FromResult(_stream); 19 | } 20 | 21 | protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) 22 | { 23 | return _stream.CopyToAsync(stream); 24 | } 25 | 26 | protected override bool TryComputeLength(out long length) 27 | { 28 | length = 0; 29 | return false; 30 | } 31 | 32 | internal void SetStream(Stream stream) 33 | { 34 | _stream = stream; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/HttpBodyStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Bedrock.Framework.Protocols 8 | { 9 | internal class HttpBodyStream : Stream 10 | { 11 | private ProtocolReader _reader; 12 | private IMessageReader> _bodyReader; 13 | 14 | public HttpBodyStream(ProtocolReader reader, IMessageReader> bodyReader) 15 | { 16 | _reader = reader; 17 | _bodyReader = bodyReader; 18 | } 19 | 20 | public override bool CanRead => true; 21 | 22 | public override bool CanSeek => false; 23 | 24 | public override bool CanWrite => false; 25 | 26 | public override long Length => throw new System.NotImplementedException(); 27 | 28 | public override long Position { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } 29 | 30 | public override void Flush() 31 | { 32 | throw new System.NotImplementedException(); 33 | } 34 | 35 | public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) 36 | { 37 | var result = await _reader.ReadAsync(_bodyReader, maximumMessageSize: buffer.Length, cancellationToken).ConfigureAwait(false); 38 | var data = result.Message; 39 | 40 | // Connection died 41 | if (result.IsCompleted) 42 | { 43 | return 0; 44 | } 45 | 46 | if (data.Length > 0) 47 | { 48 | data.CopyTo(buffer.Span); 49 | } 50 | 51 | _reader.Advance(); 52 | 53 | return (int)data.Length; 54 | } 55 | 56 | public override int Read(byte[] buffer, int offset, int count) 57 | { 58 | throw new System.NotImplementedException(); 59 | } 60 | 61 | public override long Seek(long offset, SeekOrigin origin) 62 | { 63 | throw new System.NotImplementedException(); 64 | } 65 | 66 | public override void SetLength(long value) 67 | { 68 | throw new System.NotImplementedException(); 69 | } 70 | 71 | public override void Write(byte[] buffer, int offset, int count) 72 | { 73 | throw new System.NotImplementedException(); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/HttpClientProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Connections; 6 | 7 | namespace Bedrock.Framework.Protocols 8 | { 9 | public class HttpClientProtocol 10 | { 11 | private readonly ConnectionContext _connection; 12 | private readonly ProtocolReader _reader; 13 | private readonly Http1RequestMessageWriter _messageWriter; 14 | 15 | public HttpClientProtocol(ConnectionContext connection) 16 | { 17 | _connection = connection; 18 | _reader = connection.CreateReader(); 19 | 20 | (string host, int port) = connection.RemoteEndPoint switch 21 | { 22 | UriEndPoint uriEndPoint => (uriEndPoint.Uri.Host, uriEndPoint.Uri.Port), 23 | IPEndPoint ip => (ip.Address.ToString(), ip.Port), 24 | NamedPipeEndPoint np => (np.PipeName, 80), 25 | _ => throw new NotSupportedException($"{connection.RemoteEndPoint} not supported") 26 | }; 27 | _messageWriter = new Http1RequestMessageWriter(host, port); 28 | } 29 | 30 | public async ValueTask SendAsync(HttpRequestMessage requestMessage, HttpCompletionOption completionOption = HttpCompletionOption.ResponseHeadersRead, System.Threading.CancellationToken cancellationToken = default) 31 | { 32 | // Write request message headers 33 | _messageWriter.WriteMessage(requestMessage, _connection.Transport.Output); 34 | 35 | // Write the body directly 36 | if (requestMessage.Content != null) 37 | { 38 | await requestMessage.Content.CopyToAsync(_connection.Transport.Output.AsStream()).ConfigureAwait(false); 39 | } 40 | 41 | await _connection.Transport.Output.FlushAsync(cancellationToken).ConfigureAwait(false); 42 | 43 | var content = new HttpBodyContent(); 44 | var headerReader = new Http1ResponseMessageReader(content); 45 | 46 | var result = await _reader.ReadAsync(headerReader, cancellationToken).ConfigureAwait(false); 47 | 48 | if (result.IsCompleted) 49 | { 50 | throw new ConnectionAbortedException(); 51 | } 52 | 53 | var response = result.Message; 54 | 55 | // TODO: Handle upgrade 56 | if (content.Headers.ContentLength != null) 57 | { 58 | content.SetStream(new HttpBodyStream(_reader, new ContentLengthHttpBodyReader(response.Content.Headers.ContentLength.Value))); 59 | } 60 | else if (response.Headers.TransferEncodingChunked.HasValue) 61 | { 62 | content.SetStream(new HttpBodyStream(_reader, new ChunkedHttpBodyReader())); 63 | } 64 | else 65 | { 66 | content.SetStream(new HttpBodyStream(_reader, new ContentLengthHttpBodyReader(0))); 67 | } 68 | 69 | _reader.Advance(); 70 | 71 | return response; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/HttpServerProtocol.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Connections; 4 | 5 | namespace Bedrock.Framework.Protocols 6 | { 7 | public class HttpServerProtocol 8 | { 9 | private readonly ConnectionContext _connection; 10 | private readonly ProtocolReader _reader; 11 | 12 | private readonly Http1ResponseMessageWriter _writer = new Http1ResponseMessageWriter(); 13 | 14 | public HttpServerProtocol(ConnectionContext connection) 15 | { 16 | _connection = connection; 17 | _reader = connection.CreateReader(); 18 | } 19 | 20 | public async ValueTask ReadRequestAsync(System.Threading.CancellationToken cancellationToken = default) 21 | { 22 | var content = new HttpBodyContent(); 23 | var headerReader = new Http1RequestMessageReader(content); 24 | 25 | var result = await _reader.ReadAsync(headerReader, cancellationToken).ConfigureAwait(false); 26 | 27 | if (result.IsCompleted) 28 | { 29 | throw new ConnectionAbortedException(); 30 | } 31 | 32 | var request = result.Message; 33 | 34 | // TODO: Handle upgrade 35 | if (content.Headers.ContentLength != null) 36 | { 37 | content.SetStream(new HttpBodyStream(_reader, new ContentLengthHttpBodyReader(request.Content.Headers.ContentLength.Value))); 38 | } 39 | else if (request.Headers.TransferEncodingChunked.HasValue) 40 | { 41 | content.SetStream(new HttpBodyStream(_reader, new ChunkedHttpBodyReader())); 42 | } 43 | else 44 | { 45 | content.SetStream(new HttpBodyStream(_reader, new ContentLengthHttpBodyReader(0))); 46 | } 47 | 48 | _reader.Advance(); 49 | 50 | return request; 51 | } 52 | 53 | public async ValueTask WriteResponseAsync(HttpResponseMessage responseMessage, System.Threading.CancellationToken cancellationToken = default) 54 | { 55 | _writer.WriteMessage(responseMessage, _connection.Transport.Output); 56 | 57 | if (responseMessage.Content != null) 58 | { 59 | await responseMessage.Content.CopyToAsync(_connection.Transport.Output.AsStream()).ConfigureAwait(false); 60 | } 61 | } 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/HubHandshakeMessageReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using Microsoft.AspNetCore.SignalR.Protocol; 4 | 5 | namespace Bedrock.Framework.Protocols 6 | { 7 | public class HubHandshakeMessageReader : IMessageReader 8 | { 9 | public bool TryParseMessage(in ReadOnlySequence input, ref SequencePosition consumed, ref SequencePosition examined, out HandshakeRequestMessage message) 10 | { 11 | var buffer = input; 12 | if (HandshakeProtocol.TryParseRequestMessage(ref buffer, out message)) 13 | { 14 | consumed = buffer.End; 15 | examined = consumed; 16 | return true; 17 | } 18 | 19 | return false; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/HubMessageReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using Microsoft.AspNetCore.SignalR; 4 | using Microsoft.AspNetCore.SignalR.Protocol; 5 | 6 | namespace Bedrock.Framework.Protocols 7 | { 8 | public class HubMessageReader : IMessageReader 9 | { 10 | private readonly IHubProtocol _hubProtocol; 11 | private readonly IInvocationBinder _invocationBinder; 12 | 13 | public HubMessageReader(IHubProtocol hubProtocol, IInvocationBinder invocationBinder) 14 | { 15 | _hubProtocol = hubProtocol; 16 | _invocationBinder = invocationBinder; 17 | } 18 | 19 | public bool TryParseMessage(in ReadOnlySequence input, ref SequencePosition consumed, ref SequencePosition examined, out HubMessage message) 20 | { 21 | var buffer = input; 22 | if (_hubProtocol.TryParseMessage(ref buffer, _invocationBinder, out message)) 23 | { 24 | consumed = buffer.End; 25 | examined = consumed; 26 | return true; 27 | } 28 | 29 | return false; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/HubMessageWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Microsoft.AspNetCore.SignalR.Protocol; 6 | 7 | namespace Bedrock.Framework.Protocols 8 | { 9 | public class HubMessageWriter : IMessageWriter 10 | { 11 | private readonly IHubProtocol _hubProtocol; 12 | 13 | public HubMessageWriter(IHubProtocol hubProtocol) 14 | { 15 | _hubProtocol = hubProtocol; 16 | } 17 | 18 | public void WriteMessage(HubMessage message, IBufferWriter output) 19 | { 20 | _hubProtocol.WriteMessage(message, output); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/HubProtocol.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Connections; 4 | using Microsoft.AspNetCore.SignalR; 5 | using Microsoft.AspNetCore.SignalR.Protocol; 6 | 7 | namespace Bedrock.Framework.Protocols 8 | { 9 | public class HubProtocol 10 | { 11 | private readonly ConnectionContext _connection; 12 | private readonly ProtocolReader _protocolReader; 13 | private readonly ProtocolWriter _protocolWriter; 14 | private readonly IMessageReader _hubMessageReader; 15 | private readonly IMessageWriter _hubMessageWriter; 16 | private readonly int? _maximumMessageSize; 17 | 18 | private HubProtocol(ConnectionContext connection, int? maximumMessageSize, IHubProtocol hubProtocol, IInvocationBinder invocationBinder) 19 | { 20 | _connection = connection; 21 | _protocolReader = connection.CreateReader(); 22 | _protocolWriter = connection.CreateWriter(); 23 | _hubMessageReader = new HubMessageReader(hubProtocol, invocationBinder); 24 | _hubMessageWriter = new HubMessageWriter(hubProtocol); 25 | _maximumMessageSize = maximumMessageSize; 26 | } 27 | 28 | public static HubProtocol CreateFromConnection(ConnectionContext connection, IHubProtocol hubProtocol, IInvocationBinder invocationBinder, int? maximumMessageSize = null) 29 | { 30 | return new HubProtocol(connection, maximumMessageSize, hubProtocol, invocationBinder); 31 | } 32 | 33 | public async ValueTask ReadHandshakeAsync(CancellationToken cancellationToken = default) 34 | { 35 | var result = await _protocolReader.ReadAsync(new HubHandshakeMessageReader(), _maximumMessageSize, cancellationToken).ConfigureAwait(false); 36 | 37 | var message = result.Message; 38 | 39 | _protocolReader.Advance(); 40 | 41 | return message; 42 | } 43 | 44 | public async ValueTask ReadAsync(CancellationToken cancellationToken = default) 45 | { 46 | var result = await _protocolReader.ReadAsync(_hubMessageReader, cancellationToken).ConfigureAwait(false); 47 | 48 | var message = result.Message; 49 | 50 | _protocolReader.Advance(); 51 | 52 | return message; 53 | } 54 | 55 | public ValueTask WriteAsync(HubMessage message, CancellationToken cancellationToken = default) 56 | { 57 | return _protocolWriter.WriteAsync(_hubMessageWriter, message, cancellationToken); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Memcached/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Bedrock.Framework.Experimental.Protocols.Memcached 6 | { 7 | public class Constants 8 | { 9 | public const int HeaderLength = 24; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Memcached/Enums.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Bedrock.Framework.Experimental.Protocols.Memcached 6 | { 7 | public class Enums 8 | { 9 | /// 10 | /// see https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#data-types 11 | /// 12 | public enum Opcode : byte 13 | { 14 | Get = 0x00, 15 | Set = 0x01, 16 | Add = 0x02, 17 | Replace = 0x03, 18 | Delete = 0x04 19 | } 20 | 21 | public enum ResponseStatus : byte 22 | { 23 | NoError = 0x0000, 24 | KeyNotFound = 0x0001, 25 | KeyExists = 0x0002, 26 | ValueTooLarge = 0x0003, 27 | InvalidArguments = 0x0004, 28 | ItemNotStored = 0x0005, 29 | IncrOrDecrInvalid = 0x0006, 30 | VBucketBelongToAnotherServer = 0x0007, 31 | AuthenticationError = 0x0008, 32 | AuthenticationContinue = 0x0009, 33 | UnknowCommand = 0x0081, 34 | OutOfMemory = 0x0082, 35 | NotSupported = 0x0083, 36 | InternalError = 0x0084, 37 | Busy = 0x0085, 38 | TemporaryFailure = 0x0086 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Memcached/Expiration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Bedrock.Framework.Experimental.Protocols.Memcached 6 | { 7 | public readonly struct Expiration 8 | { 9 | public readonly uint Value { get; } 10 | 11 | private Expiration(uint value) 12 | { 13 | Value = value; 14 | } 15 | 16 | public static implicit operator Expiration(TimeSpan? expireIn) => SetExpiration(expireIn); 17 | 18 | private static Expiration SetExpiration(TimeSpan? expireIn) 19 | { 20 | uint value = 0; 21 | if (expireIn != null) 22 | { 23 | if (expireIn < TimeSpan.FromDays(30)) 24 | { 25 | value = (uint)expireIn.Value.TotalSeconds; 26 | } 27 | else 28 | { 29 | value = (uint)new DateTimeOffset(DateTime.UtcNow.Add(expireIn.Value)).ToUnixTimeSeconds(); 30 | } 31 | } 32 | return new Expiration(value); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Memcached/MemcachedMessageReader.cs: -------------------------------------------------------------------------------- 1 | using Bedrock.Framework.Infrastructure; 2 | using Bedrock.Framework.Protocols; 3 | using System; 4 | using System.Buffers; 5 | using System.Buffers.Binary; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using static Bedrock.Framework.Experimental.Protocols.Memcached.Enums; 9 | 10 | namespace Bedrock.Framework.Experimental.Protocols.Memcached 11 | { 12 | public class MemcachedMessageReader : IMessageReader 13 | { 14 | public bool TryParseMessage(in ReadOnlySequence input, ref SequencePosition consumed, ref SequencePosition examined, out MemcachedResponse message) 15 | { 16 | if (input.Length < Constants.HeaderLength) 17 | { 18 | message = default; 19 | return false; 20 | } 21 | message = new MemcachedResponse(); 22 | if (input.First.Length >= Constants.HeaderLength) 23 | { 24 | message.ReadHeader(input.First.Span); 25 | } 26 | else 27 | { 28 | Span header = stackalloc byte[Constants.HeaderLength]; 29 | input.Slice(0, Constants.HeaderLength).CopyTo(header); 30 | message.ReadHeader(header); 31 | } 32 | 33 | if (input.Length < message.Header.TotalBodyLength + Constants.HeaderLength) 34 | { 35 | message = default; 36 | return false; 37 | } 38 | 39 | message.ReadBody(input.Slice(Constants.HeaderLength, message.Header.TotalBodyLength)); 40 | consumed = input.Slice(Constants.HeaderLength + message.Header.TotalBodyLength).End; 41 | examined = consumed; 42 | return true; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Memcached/MemcachedMessageWriter.cs: -------------------------------------------------------------------------------- 1 | using Bedrock.Framework.Infrastructure; 2 | using Bedrock.Framework.Protocols; 3 | using System; 4 | using System.Buffers; 5 | using System.Buffers.Binary; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace Bedrock.Framework.Experimental.Protocols.Memcached 10 | { 11 | public class MemcachedMessageWriter : IMessageWriter 12 | { 13 | public void WriteMessage(MemcachedRequest message, IBufferWriter output) 14 | { 15 | Span headerSpan = stackalloc byte[Constants.HeaderLength]; 16 | byte extraLength = 0; 17 | 18 | if(message.Flags != TypeCode.Empty) 19 | { 20 | extraLength = 8; 21 | } 22 | 23 | var messageValueLength = 0; 24 | 25 | if (message.Value != null) 26 | { 27 | messageValueLength = message.Value.Length; 28 | } 29 | 30 | var header = new MemcachedRequestHeader() 31 | { 32 | KeyLength = (ushort)message.Key.Length, 33 | Opaque = message.Opaque, 34 | TotalBodyLength = (uint)(extraLength + message.Key.Length + messageValueLength), 35 | ExtraLength = extraLength 36 | }; 37 | 38 | if(extraLength != 0) 39 | { 40 | header.Extras = (message.Flags, message.ExpireIn); 41 | } 42 | 43 | headerSpan[0] = MemcachedRequestHeader.Magic; 44 | headerSpan[1] = (byte)message.Opcode; 45 | BinaryPrimitives.WriteUInt16BigEndian(headerSpan.Slice(2), header.KeyLength); 46 | headerSpan[4] = header.ExtraLength; 47 | headerSpan[5] = header.DataType; 48 | BinaryPrimitives.WriteUInt16BigEndian(headerSpan.Slice(6), header.VBucket); 49 | BinaryPrimitives.WriteUInt32BigEndian(headerSpan.Slice(8), header.TotalBodyLength); 50 | BinaryPrimitives.WriteUInt32BigEndian(headerSpan.Slice(12), header.Opaque); 51 | BinaryPrimitives.WriteUInt64BigEndian(headerSpan.Slice(16), header.Cas); 52 | 53 | output.Write(headerSpan); 54 | 55 | var body = output.GetSpan((int)header.TotalBodyLength); 56 | BinaryPrimitives.WriteUInt32BigEndian(body.Slice(0), (uint)header.Extras.Flags); 57 | BinaryPrimitives.WriteUInt32BigEndian(body.Slice(4), (uint)header.Extras.Expiration.Value); 58 | 59 | message.Key.CopyTo(body.Slice(header.ExtraLength)); 60 | message.Value.CopyTo(body.Slice(header.ExtraLength + message.Key.Length)); 61 | output.Advance((int)header.TotalBodyLength); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Memcached/MemcachedRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using static Bedrock.Framework.Experimental.Protocols.Memcached.Enums; 5 | 6 | namespace Bedrock.Framework.Experimental.Protocols.Memcached 7 | { 8 | public class MemcachedRequest 9 | { 10 | public Opcode Opcode { get; } 11 | public byte[] Key { get; } 12 | public uint Opaque { get; } 13 | public byte[] Value { get; set; } 14 | public TypeCode Flags { get; } 15 | public TimeSpan? ExpireIn { get; } 16 | 17 | public MemcachedRequest(Opcode opcode, byte[] key, uint opaque,byte[] value, TypeCode flags, TimeSpan ? expireIn=null) 18 | { 19 | Opcode = opcode; 20 | Key = key; 21 | Opaque = opaque; 22 | Value = value; 23 | Flags = flags; 24 | ExpireIn = expireIn; 25 | } 26 | 27 | public MemcachedRequest(Opcode opcode, byte[] key, uint opaque) 28 | { 29 | Opcode = opcode; 30 | Key = key; 31 | Opaque = opaque; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Memcached/MemcachedRequestHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Bedrock.Framework.Experimental.Protocols.Memcached 6 | { 7 | public class MemcachedRequestHeader 8 | { 9 | public const byte Magic = 0x80; 10 | public ushort KeyLength { get; set; } 11 | public byte ExtraLength { get; set; } 12 | public byte DataType { get; set; } 13 | public ushort VBucket { get; set; } 14 | public uint TotalBodyLength { get; set; } 15 | public uint Opaque { get; set; } 16 | public ulong Cas { get; set; } 17 | public (TypeCode Flags, Expiration Expiration) Extras; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Memcached/MemcachedResponse.cs: -------------------------------------------------------------------------------- 1 | using Bedrock.Framework.Infrastructure; 2 | using System; 3 | using System.Buffers; 4 | using System.Buffers.Binary; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using static Bedrock.Framework.Experimental.Protocols.Memcached.Enums; 8 | 9 | namespace Bedrock.Framework.Experimental.Protocols.Memcached 10 | { 11 | public class MemcachedResponse 12 | { 13 | public ReadOnlySequence Data { get; private set; } 14 | public TypeCode Flags { get; set; } 15 | public MemcachedResponseHeader Header { get; private set; } 16 | 17 | public void ReadHeader(ReadOnlySpan buffer) 18 | { 19 | if (buffer[0] != MemcachedResponseHeader.Magic) 20 | { 21 | throw new ArgumentException("Magic mismatch"); 22 | } 23 | 24 | this.Header = new MemcachedResponseHeader() 25 | { 26 | Opcode = (Opcode)buffer[1], 27 | KeyLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2)), 28 | ExtraLength = buffer[4], 29 | DataType = buffer[5], 30 | ResponseStatus = (ResponseStatus)BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(6)), 31 | TotalBodyLength = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(8)), 32 | Opaque = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(12)), 33 | Cas = BinaryPrimitives.ReadUInt64BigEndian(buffer.Slice(16)), 34 | }; 35 | } 36 | 37 | public void ReadBody(ReadOnlySequence sequence) 38 | { 39 | if (sequence.Length == 0) 40 | { 41 | return; 42 | } 43 | 44 | Data = sequence.Slice(Header.KeyLength + Header.ExtraLength); 45 | 46 | Span buffer = stackalloc byte[4]; 47 | sequence.Slice(0, 4).CopyTo(buffer); 48 | Flags = (TypeCode)BinaryPrimitives.ReadUInt32BigEndian(buffer); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/Memcached/MemcachedResponseHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using static Bedrock.Framework.Experimental.Protocols.Memcached.Enums; 5 | 6 | namespace Bedrock.Framework.Experimental.Protocols.Memcached 7 | { 8 | public class MemcachedResponseHeader 9 | { 10 | public const byte Magic = 0x81; 11 | public Opcode Opcode { get; set; } 12 | public ushort KeyLength { get; set; } 13 | public byte ExtraLength { get; set; } 14 | public byte DataType { get; set; } 15 | public ResponseStatus ResponseStatus { get; set; } 16 | public uint TotalBodyLength { get; set; } 17 | public uint Opaque { get; set; } 18 | public ulong Cas { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/ParseResult.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Bedrock.Framework.Protocols.Http.Http1 4 | { 5 | public struct ParseResult 6 | { 7 | private readonly T _value; 8 | private readonly ParseError _error; 9 | 10 | public ParseResult(T value) : this() 11 | { 12 | _value = value; 13 | } 14 | 15 | public ParseResult(ParseError error) : this() 16 | { 17 | _error = error; 18 | } 19 | 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | public bool TryGetValue(out T value) 22 | { 23 | value = _value; 24 | return _error is null; 25 | } 26 | 27 | public bool TryGetError(out ParseError error) 28 | { 29 | error = _error; 30 | return error is object; 31 | } 32 | } 33 | 34 | public class ParseError 35 | { 36 | public ParseError(string reason, string line) 37 | { 38 | Reason = reason; 39 | Line = line; 40 | } 41 | 42 | public string Reason { get; } 43 | public string Line { get; } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/FrameType.cs: -------------------------------------------------------------------------------- 1 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ 2 | { 3 | internal enum FrameType : byte 4 | { 5 | Method = 1, 6 | Header = 2, 7 | Body = 3, 8 | HeartBeat = 8, 9 | End = 206 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/IRabbitMQMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ 7 | { 8 | public interface IAmqpMessage 9 | { 10 | void Write(IBufferWriter output); 11 | bool TryParse(in ReadOnlySequence input, out SequencePosition end); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/ChannelOpen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class ChannelOpen : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 20; 12 | public override byte MethodId => 10; 13 | 14 | public ReadOnlyMemory Reserved1 { get; } 15 | public ushort Channel { get; private set; } 16 | 17 | public ChannelOpen(ReadOnlyMemory reserved1, ushort channel) 18 | { 19 | Reserved1 = reserved1; 20 | Channel = channel; 21 | } 22 | 23 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | public void Write(IBufferWriter output) 29 | { 30 | var payloadLength = 1 + Reserved1.Length + MethodHeaderLength; 31 | var buffer = output.GetSpan(RabbitMQMessageFormatter.HeaderLength + payloadLength + 1); 32 | 33 | WriteHeader(ref buffer, Channel, payloadLength); 34 | 35 | BinaryPrimitives.WriteUInt16BigEndian(buffer, ClassId); 36 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), MethodId); 37 | buffer[4] = (byte)Reserved1.Length; 38 | Reserved1.Span.CopyTo(buffer.Slice(5)); 39 | buffer[payloadLength]= (byte)FrameType.End; 40 | 41 | output.Advance(RabbitMQMessageFormatter.HeaderLength + payloadLength + sizeof(byte)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/ChannelOpenOk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class ChannelOpenOk : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 20; 12 | public override byte MethodId => 11; 13 | 14 | public ReadOnlyMemory Reserved1 { get; private set; } 15 | 16 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 17 | { 18 | var reader = new SequenceReader(input); 19 | try 20 | { 21 | Reserved1 = ProtocolHelper.ReadLongString(ref reader); 22 | end = reader.Position; 23 | return true; 24 | } 25 | catch (Exception ex) 26 | { 27 | //TODO trace error 28 | end = default; 29 | return false; 30 | } 31 | } 32 | 33 | public void Write(IBufferWriter output) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/ConnectionOk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class ConnectionOk : MethodBase, IAmqpMessage 10 | { 11 | public ReadOnlyMemory SecurityMechanism { get; private set; } 12 | public ReadOnlyMemory Credentials { get; private set; } 13 | public ReadOnlyMemory Locale { get; private set; } 14 | 15 | public override byte ClassId => 10; 16 | public override byte MethodId => 11; 17 | 18 | public ConnectionOk(ReadOnlyMemory securityMechanism, ReadOnlyMemory credentials, ReadOnlyMemory locale) 19 | { 20 | SecurityMechanism = securityMechanism; 21 | Credentials = credentials; 22 | Locale = locale; 23 | } 24 | 25 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | 30 | public void Write(IBufferWriter output) 31 | { 32 | int PayloadLength = SecurityMechanism.Length + Credentials.Length + Locale.Length + 14; 33 | var buffer = output.GetSpan(RabbitMQMessageFormatter.HeaderLength + PayloadLength + 1); 34 | 35 | WriteHeader(ref buffer, 0, PayloadLength); 36 | 37 | BinaryPrimitives.WriteUInt16BigEndian(buffer, ClassId); 38 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), MethodId); 39 | //TO DO replace by client properties 40 | BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4), 0); 41 | buffer = buffer.Slice(8); 42 | buffer[0] = (byte)SecurityMechanism.Length; 43 | SecurityMechanism.Span.CopyTo(buffer.Slice(1)); 44 | buffer = buffer.Slice(SecurityMechanism.Length+1); 45 | BinaryPrimitives.WriteInt32BigEndian(buffer, Credentials.Length); 46 | buffer = buffer.Slice(4); 47 | Credentials.Span.CopyTo(buffer); 48 | buffer = buffer.Slice(Credentials.Length); 49 | buffer[0] = (byte)Locale.Length; 50 | buffer = buffer.Slice(1); 51 | Locale.Span.CopyTo(buffer); 52 | buffer = buffer.Slice(Locale.Length); 53 | buffer[0] = (byte)FrameType.End; 54 | 55 | output.Advance(RabbitMQMessageFormatter.HeaderLength + PayloadLength + sizeof(byte)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/ConnectionOpen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class ConnectionOpen : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 10; 12 | public override byte MethodId => 40; 13 | 14 | public ReadOnlyMemory Vhost { get; private set; } 15 | public ReadOnlyMemory Reserved1 { get; private set; } 16 | 17 | public byte Reserved2 { get; } 18 | 19 | public ConnectionOpen(ReadOnlyMemory vhost, ReadOnlyMemory reserved1, byte reserved2) 20 | { 21 | Vhost = vhost; 22 | Reserved1 = reserved1; 23 | Reserved2 = reserved2; 24 | } 25 | 26 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 27 | { 28 | throw new NotImplementedException(); 29 | } 30 | 31 | public void Write(IBufferWriter output) 32 | { 33 | var payloadLength = 1 + Vhost.Length + 1 + Reserved1.Length + MethodHeaderLength + sizeof(byte); 34 | var buffer = output.GetSpan(RabbitMQMessageFormatter.HeaderLength + payloadLength + 1); 35 | 36 | WriteHeader(ref buffer, 0, payloadLength); 37 | 38 | BinaryPrimitives.WriteUInt16BigEndian(buffer, ClassId); 39 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), MethodId); 40 | buffer[4] = (byte)Vhost.Length; 41 | Vhost.Span.CopyTo(buffer.Slice(5)); 42 | buffer[5 + Vhost.Length] = (byte)Reserved1.Length; 43 | Reserved1.Span.CopyTo(buffer.Slice(5 + Vhost.Length + 1)); 44 | buffer[5 + Vhost.Length + 1 + Reserved1.Length] = Reserved2; 45 | buffer[payloadLength] = (byte)FrameType.End; 46 | 47 | output.Advance(RabbitMQMessageFormatter.HeaderLength + payloadLength + sizeof(byte)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/ConnectionOpenOk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class ConnectionOpenOk : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 10; 12 | public override byte MethodId => 41; 13 | public ReadOnlyMemory Reserved1 { get; private set; } 14 | 15 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 16 | { 17 | var reader = new SequenceReader(input); 18 | try 19 | { 20 | Reserved1 = ProtocolHelper.ReadShortString(ref reader); 21 | end = reader.Position; 22 | return true; 23 | } 24 | catch (Exception ex) 25 | { 26 | //TODO trace error 27 | end = default; 28 | return false; 29 | } 30 | } 31 | 32 | public void Write(IBufferWriter output) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/ConnectionStart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 7 | { 8 | public class ConnectionStart : MethodBase, IAmqpMessage 9 | { 10 | public override byte ClassId => 10; 11 | public override byte MethodId => 10; 12 | 13 | public byte VersionMajor { get; private set; } 14 | public byte VersionMinor { get; private set; } 15 | public Dictionary ServerProperties { get; private set; } 16 | public ReadOnlyMemory SecurityMechanims { get; private set; } 17 | public ReadOnlyMemory Locale { get; private set; } 18 | 19 | private ReadOnlySpan PlainAMQPPlain => new byte[] { (byte)'P', (byte)'L', (byte)'A', (byte)'I', (byte)'N', (byte)' ', (byte)'A', (byte)'M', (byte)'Q', (byte)'P', (byte)'L', (byte)'A', (byte)'I', (byte)'N' }; 20 | private ReadOnlyMemory Plain = new byte[] { (byte)'P', (byte)'L', (byte)'A', (byte)'I', (byte)'N' }; 21 | 22 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 23 | { 24 | var reader = new SequenceReader(input); 25 | 26 | if (!reader.TryRead(out var verionMajor)) 27 | { 28 | end = default; 29 | return false; 30 | } 31 | if (!reader.TryRead(out var versionMinor)) 32 | { 33 | end = default; 34 | return false; 35 | } 36 | 37 | VersionMajor = verionMajor; 38 | VersionMinor = versionMinor; 39 | try 40 | { 41 | ServerProperties = ProtocolHelper.ReadTable(ref reader); 42 | var security = ProtocolHelper.ReadLongString(ref reader); 43 | if(security.Span.SequenceEqual(PlainAMQPPlain)) 44 | { 45 | SecurityMechanims = Plain; 46 | } 47 | else 48 | { 49 | throw new Exception($"Unsupported security mechanism"); 50 | } 51 | Locale = ProtocolHelper.ReadLongString(ref reader); 52 | end = reader.Position; 53 | return true; 54 | } 55 | catch(Exception ex) 56 | { 57 | //TODO trace error 58 | end = default; 59 | return false; 60 | } 61 | } 62 | 63 | public void Write(IBufferWriter output) 64 | { 65 | throw new NotImplementedException(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/ConnectionTune.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class ConnectionTune : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 10; 12 | public override byte MethodId => 30; 13 | 14 | public short MaxChannel { get; private set; } 15 | public int MaxFrame { get; private set; } 16 | public short HeartBeat { get; private set; } 17 | 18 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 19 | { 20 | var reader = new SequenceReader(input); 21 | try 22 | { 23 | reader.TryReadBigEndian(out short maxChannel); 24 | reader.TryReadBigEndian(out int maxFrame); 25 | reader.TryReadBigEndian(out short heartBeat); 26 | 27 | MaxChannel = maxChannel; 28 | MaxFrame = maxFrame; 29 | HeartBeat = heartBeat; 30 | 31 | end = reader.Position; 32 | return true; 33 | } 34 | catch (Exception ex) 35 | { 36 | //TODO trace error 37 | end = default; 38 | return false; 39 | } 40 | } 41 | 42 | public void Write(IBufferWriter output) 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/ConnectionTuneOk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class ConnectionTuneOk : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 10; 12 | public override byte MethodId => 31; 13 | 14 | public short MaxChannel { get; private set; } 15 | public int MaxFrame { get; private set; } 16 | public short HeartBeat { get; private set; } 17 | 18 | public ConnectionTuneOk(short maxChannel, int maxFrame, short heartBeat) 19 | { 20 | this.MaxChannel = maxChannel; 21 | this.MaxFrame = maxFrame; 22 | this.HeartBeat = heartBeat; 23 | } 24 | 25 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | 30 | public void Write(IBufferWriter output) 31 | { 32 | var payloadLength = sizeof(ushort) + sizeof(uint) + sizeof(ushort) + MethodHeaderLength; 33 | var buffer = output.GetSpan(RabbitMQMessageFormatter.HeaderLength + payloadLength + 1); 34 | 35 | WriteHeader(ref buffer, 0, payloadLength); 36 | 37 | BinaryPrimitives.WriteUInt16BigEndian(buffer, ClassId); 38 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), MethodId); 39 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(4), (ushort)this.MaxChannel); 40 | BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(6), (uint)this.MaxFrame); 41 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(10), (ushort)this.HeartBeat); 42 | buffer[payloadLength]= (byte)FrameType.End; 43 | 44 | output.Advance(RabbitMQMessageFormatter.HeaderLength + payloadLength + sizeof(byte)); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/MethodBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers.Binary; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 7 | { 8 | public abstract class MethodBase 9 | { 10 | public abstract byte ClassId { get; } 11 | public abstract byte MethodId { get; } 12 | 13 | public const int MethodHeaderLength = 4; 14 | 15 | public void WriteHeader(ref Span buffer, ushort channel, int payloadLength) 16 | { 17 | buffer[0] = (byte)FrameType.Method; 18 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(1), channel); 19 | BinaryPrimitives.WriteInt32BigEndian(buffer.Slice(3), payloadLength); 20 | buffer = buffer.Slice(7); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/QueueDeclare.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class QueueDeclare : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 50; 12 | public override byte MethodId => 10; 13 | 14 | public ushort Channel { get; private set; } 15 | public ushort Reserved1 { get; private set; } 16 | public string QueueName { get; private set; } 17 | public bool Passive { get; private set; } 18 | public bool Durable { get; private set; } 19 | public bool Exclusive { get; private set; } 20 | public bool AutoDelete { get; private set; } 21 | public bool NoWait { get; private set; } 22 | public Dictionary Arguments { get; private set; } 23 | public ReadOnlyMemory Options { get; private set; } 24 | 25 | public QueueDeclare(ushort channel,ushort reserved1, string queueName, bool passive = false, bool durable = true, bool exclusive = false, bool autoDelete = false, bool noWait = false, Dictionary arguments = null ) 26 | { 27 | Channel = channel; 28 | Reserved1 = reserved1; 29 | QueueName = queueName; 30 | Passive = passive; 31 | Durable = durable; 32 | Exclusive = exclusive; 33 | AutoDelete = autoDelete; 34 | NoWait = noWait; 35 | Arguments = arguments; 36 | Options = new ReadOnlyMemory(new byte[] { Convert.ToByte(passive), Convert.ToByte(durable), Convert.ToByte(exclusive), Convert.ToByte(autoDelete), Convert.ToByte(noWait) }); 37 | } 38 | 39 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | 44 | public void Write(IBufferWriter output) 45 | { 46 | var payloadLength = 6 + 1 + QueueName.Length + sizeof(byte) + MethodHeaderLength; 47 | var buffer = output.GetSpan(RabbitMQMessageFormatter.HeaderLength + payloadLength + 1); 48 | 49 | WriteHeader(ref buffer, this.Channel, payloadLength); 50 | 51 | BinaryPrimitives.WriteUInt16BigEndian(buffer, ClassId); 52 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), MethodId); 53 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(4), Reserved1); 54 | buffer[6] = (byte)QueueName.Length; 55 | Encoding.UTF8.GetBytes(QueueName).CopyTo(buffer.Slice(7)); 56 | var bytes = ProtocolHelper.BoolArrayToByte(Options); 57 | buffer = buffer.Slice(7 + QueueName.Length); 58 | buffer[0] = bytes; 59 | //TO DO write table 60 | BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(1), 0); 61 | buffer[5] = (byte)FrameType.End; 62 | 63 | output.Advance(RabbitMQMessageFormatter.HeaderLength + payloadLength + sizeof(byte)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/QueueDeclareOk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class QueueDeclareOk : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 50; 12 | public override byte MethodId => 11; 13 | public ReadOnlyMemory QueueName { get; private set; } 14 | public uint MessageCount { get; private set; } 15 | public uint ConsumerCount { get; private set; } 16 | 17 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 18 | { 19 | var reader = new SequenceReader(input); 20 | try 21 | { 22 | this.QueueName = ProtocolHelper.ReadShortString(ref reader); 23 | if (reader.TryReadBigEndian(out int messageCount)) 24 | this.MessageCount = (uint)messageCount; 25 | if (reader.TryReadBigEndian(out int consumerCount)) 26 | this.ConsumerCount = (uint)consumerCount; 27 | 28 | end = reader.Position; 29 | return true; 30 | } 31 | catch (Exception ex) 32 | { 33 | //TODO trace error 34 | end = default; 35 | return false; 36 | } 37 | } 38 | 39 | public void Write(IBufferWriter output) 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/QueueDelete.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class QueueDelete : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 50; 12 | public override byte MethodId => 40; 13 | 14 | public ushort Channel { get; private set; } 15 | public ushort Reserved1 { get; private set; } 16 | public string QueueName { get; private set; } 17 | public bool DeleteIfUnused { get; private set; } 18 | public bool DeleteIfEmpty { get; private set; } 19 | 20 | public ReadOnlyMemory Options { get; private set; } 21 | 22 | public QueueDelete(ushort channel,ushort reserved1, string queueName, bool deleteIfUnused = false, bool deleteIfEmpty = false ) 23 | { 24 | Channel = channel; 25 | Reserved1 = reserved1; 26 | QueueName = queueName; 27 | 28 | Options = new ReadOnlyMemory(new byte[] { Convert.ToByte(deleteIfUnused), Convert.ToByte(deleteIfEmpty) }); 29 | } 30 | 31 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | 36 | public void Write(IBufferWriter output) 37 | { 38 | var payloadLength = MethodHeaderLength + 2 + 1 + QueueName.Length + sizeof(byte); 39 | var buffer = output.GetSpan(RabbitMQMessageFormatter.HeaderLength + payloadLength + 1); 40 | 41 | WriteHeader(ref buffer, this.Channel, payloadLength); 42 | 43 | BinaryPrimitives.WriteUInt16BigEndian(buffer, ClassId); 44 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), MethodId); 45 | BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(4), Reserved1); 46 | buffer[6] = (byte)QueueName.Length; 47 | Encoding.UTF8.GetBytes(QueueName).CopyTo(buffer.Slice(7)); 48 | buffer[7 + QueueName.Length] = ProtocolHelper.BoolArrayToByte(Options); 49 | buffer[payloadLength] = (byte)FrameType.End; 50 | 51 | output.Advance(RabbitMQMessageFormatter.HeaderLength + payloadLength + sizeof(byte)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/Methods/QueueDeleteOk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ.Methods 8 | { 9 | public class QueueDeleteOk : MethodBase, IAmqpMessage 10 | { 11 | public override byte ClassId => 50; 12 | public override byte MethodId => 41; 13 | 14 | public uint MessageCount { get; private set; } 15 | 16 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 17 | { 18 | var reader = new SequenceReader(input); 19 | try 20 | { 21 | if (reader.TryReadBigEndian(out int messageCount)) 22 | this.MessageCount = (uint)messageCount; 23 | 24 | end = reader.Position; 25 | return true; 26 | } 27 | catch (Exception ex) 28 | { 29 | //TODO trace error 30 | end = default; 31 | return false; 32 | } 33 | } 34 | 35 | public void Write(IBufferWriter output) 36 | { 37 | throw new NotImplementedException(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/RabbitMQClientProtocol.cs: -------------------------------------------------------------------------------- 1 | using Bedrock.Framework.Protocols; 2 | using Microsoft.AspNetCore.Connections; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ 9 | { 10 | public class RabbitMQClientProtocol 11 | { 12 | private readonly ProtocolWriter _writer; 13 | private readonly ProtocolReader _reader; 14 | private readonly RabbitMQMessageFormatter _formatter; 15 | 16 | public RabbitMQClientProtocol(ConnectionContext connection) 17 | { 18 | _writer = connection.CreateWriter(); 19 | _reader = connection.CreateReader(); 20 | _formatter = new RabbitMQMessageFormatter(); 21 | } 22 | 23 | public ValueTask SendAsync(IAmqpMessage message) 24 | { 25 | return _writer.WriteAsync(_formatter, message); 26 | } 27 | 28 | public async Task ReceiveAsync() where T : IAmqpMessage 29 | { 30 | var result = await _reader.ReadAsync(_formatter); 31 | _reader.Advance(); 32 | return (T)result.Message; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/RabbitMQ/RabbitMQProtocolVersionHeader.cs: -------------------------------------------------------------------------------- 1 | using Bedrock.Framework.Infrastructure; 2 | using System; 3 | using System.Buffers; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Experimental.Protocols.RabbitMQ 8 | { 9 | public class RabbitMQProtocolVersionHeader : IAmqpMessage 10 | { 11 | private ReadOnlySpan ProtocolHeader => new byte[] { (byte)'A', (byte)'M', (byte)'Q', (byte)'P', 0, 0, 9, 1 }; 12 | 13 | public bool TryParse(in ReadOnlySequence input, out SequencePosition end) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public void Write(IBufferWriter output) 19 | { 20 | var writer = new BufferWriter>(output); 21 | writer.Write(ProtocolHeader); 22 | writer.Commit(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Protocols/WebSocketProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.WebSockets; 3 | using System.Threading; 4 | using System.Threading.Channels; 5 | using System.Threading.Tasks; 6 | using Bedrock.Framework.Infrastructure; 7 | using Microsoft.AspNetCore.Connections; 8 | 9 | namespace Bedrock.Framework.Protocols 10 | { 11 | public class WebSocketProtocol 12 | { 13 | public WebSocketProtocol(WebSocket websocket, ConnectionContext connection) 14 | { 15 | WebSocket = websocket; 16 | Connection = connection; 17 | } 18 | 19 | private SemaphoreSlim _semaphore = new SemaphoreSlim(1); 20 | 21 | private WebSocket WebSocket { get; } 22 | public ConnectionContext Connection { get; } 23 | 24 | public ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) 25 | { 26 | return WebSocket.ReceiveAsync(buffer, cancellationToken); 27 | } 28 | 29 | public async ValueTask WriteAsync(ReadOnlyMemory buffer, WebSocketMessageType webSocketMessageType, bool endOfMessage, CancellationToken cancellationToken = default) 30 | { 31 | await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); 32 | 33 | try 34 | { 35 | await WebSocket.SendAsync(buffer, webSocketMessageType, endOfMessage, cancellationToken).ConfigureAwait(false); 36 | } 37 | finally 38 | { 39 | _semaphore.Release(); 40 | } 41 | } 42 | 43 | public static WebSocketProtocol CreateFromConnection(ConnectionContext connection, bool isServer, string subProtocol, TimeSpan keepAliveInterval) 44 | { 45 | var websocket = WebSocket.CreateFromStream(new DuplexPipeStream(connection.Transport.Input, connection.Transport.Output), isServer, subProtocol, keepAliveInterval); 46 | return new WebSocketProtocol(websocket, connection); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Http2/Http2ConnectionListenerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Connections; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Server.Kestrel.Core; 8 | using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | 13 | namespace Bedrock.Framework 14 | { 15 | public partial class Http2ConnectionListenerFactory : IConnectionListenerFactory 16 | { 17 | private readonly ILoggerFactory _loggerFactory; 18 | 19 | public Http2ConnectionListenerFactory(ILoggerFactory loggerFactory) 20 | { 21 | _loggerFactory = loggerFactory; 22 | } 23 | 24 | public async ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) 25 | { 26 | IPEndPoint iPEndPoint = null; 27 | switch (endpoint) 28 | { 29 | // Kestrel doesn't natively support UriEndpoints as yet 30 | case UriEndPoint uriEndPoint: 31 | IPAddress address = null; 32 | if (uriEndPoint.Uri.Host == "localhost") 33 | { 34 | address = IPAddress.Loopback; 35 | } 36 | else 37 | { 38 | IPAddress.Parse(uriEndPoint.Uri.Host); 39 | } 40 | iPEndPoint = new IPEndPoint(address, uriEndPoint.Uri.Port); 41 | break; 42 | case IPEndPoint ip: 43 | iPEndPoint = ip; 44 | break; 45 | default: 46 | throw new NotSupportedException($"{endpoint} not supported"); 47 | } 48 | 49 | var services = new ServiceCollection(); 50 | services.AddSingleton(_loggerFactory); 51 | services.AddLogging(); 52 | var serverOptions = Options.Create(new KestrelServerOptions() { ApplicationServices = services.BuildServiceProvider() }); ; 53 | var socketOptions = Options.Create(new SocketTransportOptions()); 54 | var socketTransportFactory = new SocketTransportFactory(socketOptions, _loggerFactory); 55 | var server = new KestrelServer(serverOptions, socketTransportFactory, _loggerFactory); 56 | ListenOptions listenOptions = null; 57 | 58 | // Bind an HTTP/2 endpoint 59 | server.Options.Listen(iPEndPoint, options => 60 | { 61 | options.UseHttps(); 62 | options.Protocols = HttpProtocols.Http2; 63 | // Storing the options so we can get the resolved EndPoint later 64 | listenOptions = options; 65 | }); 66 | 67 | var listener = new Http2ConnectionListener(server); 68 | 69 | await listener.BindAsync(cancellationToken).ConfigureAwait(false); 70 | 71 | listener.EndPoint = listenOptions.IPEndPoint; 72 | 73 | return listener; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pipes/NamedPipeConnectionContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Pipelines; 4 | using System.IO.Pipes; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Connections; 8 | using Microsoft.AspNetCore.Http.Features; 9 | 10 | namespace Bedrock.Framework 11 | { 12 | internal class NamedPipeConnectionContext : ConnectionContext, IDuplexPipe 13 | { 14 | private readonly PipeStream _stream; 15 | 16 | public NamedPipeConnectionContext(PipeStream stream, NamedPipeEndPoint endPoint) 17 | { 18 | _stream = stream; 19 | Transport = this; 20 | ConnectionId = Guid.NewGuid().ToString(); 21 | RemoteEndPoint = endPoint; 22 | 23 | Input = PipeReader.Create(stream); 24 | Output = PipeWriter.Create(stream); 25 | } 26 | 27 | public PipeReader Input { get; } 28 | 29 | public PipeWriter Output { get; } 30 | public override string ConnectionId { get; set; } 31 | 32 | public override IFeatureCollection Features { get; } = new FeatureCollection(); 33 | 34 | public override IDictionary Items { get; set; } = new ConnectionItems(); 35 | public override IDuplexPipe Transport { get; set; } 36 | 37 | public override void Abort() 38 | { 39 | // TODO: Abort the named pipe. Do we dispose the Stream? 40 | base.Abort(); 41 | } 42 | 43 | public override async ValueTask DisposeAsync() 44 | { 45 | Input.Complete(); 46 | Output.Complete(); 47 | 48 | await _stream.DisposeAsync(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pipes/NamedPipeConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Pipes; 3 | using System.Net; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Connections; 7 | 8 | namespace Bedrock.Framework 9 | { 10 | public class NamedPipeConnectionFactory : IConnectionFactory 11 | { 12 | public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) 13 | { 14 | if (!(endpoint is NamedPipeEndPoint np)) 15 | { 16 | throw new NotSupportedException($"{endpoint.GetType()} is not supported"); 17 | } 18 | 19 | var pipeStream = new NamedPipeClientStream(np.ServerName, np.PipeName, PipeDirection.InOut, np.PipeOptions); 20 | await pipeStream.ConnectAsync(cancellationToken).ConfigureAwait(false); 21 | 22 | return new NamedPipeConnectionContext(pipeStream, np); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pipes/NamedPipeConnectionListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Pipes; 4 | using System.Net; 5 | using System.Threading; 6 | using System.Threading.Channels; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Connections; 9 | 10 | namespace Bedrock.Framework 11 | { 12 | public class NamedPipeConnectionListener : IConnectionListener 13 | { 14 | private readonly NamedPipeEndPoint _endpoint; 15 | private readonly CancellationTokenSource _listeningSource = new CancellationTokenSource(); 16 | private readonly Channel _acceptedQueue = Channel.CreateUnbounded(); 17 | private readonly Task _listeningTask; 18 | 19 | public NamedPipeConnectionListener(NamedPipeEndPoint endpoint) 20 | { 21 | _endpoint = endpoint; 22 | ListeningToken = _listeningSource.Token; 23 | _listeningTask = StartAsync(); 24 | } 25 | 26 | public EndPoint EndPoint => _endpoint; 27 | 28 | public CancellationToken ListeningToken { get; } 29 | 30 | private async Task StartAsync() 31 | { 32 | while (true) 33 | { 34 | if (ListeningToken.IsCancellationRequested) 35 | { 36 | // We're done listening 37 | break; 38 | } 39 | 40 | var stream = new NamedPipeServerStream(_endpoint.PipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, _endpoint.PipeOptions); 41 | 42 | try 43 | { 44 | await stream.WaitForConnectionAsync(ListeningToken).ConfigureAwait(false); 45 | } 46 | catch (OperationCanceledException) when (ListeningToken.IsCancellationRequested) 47 | { 48 | // Cancelled the current token 49 | break; 50 | } 51 | catch (ObjectDisposedException) 52 | { 53 | // Listener disposed 54 | break; 55 | } 56 | 57 | _acceptedQueue.Writer.TryWrite(new NamedPipeConnectionContext(stream, _endpoint)); 58 | } 59 | 60 | _acceptedQueue.Writer.TryComplete(); 61 | } 62 | 63 | public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) 64 | { 65 | while (await _acceptedQueue.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) 66 | { 67 | if (_acceptedQueue.Reader.TryRead(out var connection)) 68 | { 69 | return connection; 70 | } 71 | } 72 | return null; 73 | } 74 | 75 | public ValueTask DisposeAsync() 76 | { 77 | _listeningSource.Dispose(); 78 | return default; 79 | } 80 | 81 | public async ValueTask UnbindAsync(CancellationToken cancellationToken = default) 82 | { 83 | _listeningSource.Cancel(); 84 | 85 | await _listeningTask.ConfigureAwait(false); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pipes/NamedPipeConnectionListenerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Connections; 6 | 7 | namespace Bedrock.Framework 8 | { 9 | public class NamedPipeConnectionListenerFactory : IConnectionListenerFactory 10 | { 11 | public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) 12 | { 13 | if (!(endpoint is NamedPipeEndPoint np)) 14 | { 15 | throw new NotSupportedException($"{endpoint.GetType()} is not supported"); 16 | } 17 | var listener = new NamedPipeConnectionListener(np); 18 | return new ValueTask(listener); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pipes/NamedPipeEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace Bedrock.Framework 4 | { 5 | public class NamedPipeEndPoint : EndPoint 6 | { 7 | public NamedPipeEndPoint(string pipeName, string serverName = ".", System.IO.Pipes.PipeOptions pipeOptions = System.IO.Pipes.PipeOptions.Asynchronous) 8 | { 9 | ServerName = serverName; 10 | PipeName = pipeName; 11 | PipeOptions = pipeOptions; 12 | } 13 | 14 | public string ServerName { get; } 15 | public string PipeName { get; } 16 | public System.IO.Pipes.PipeOptions PipeOptions { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | return $"Server = {ServerName}, Pipe = {PipeName}"; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pooling/ConnectionPoolingFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Connections; 6 | 7 | namespace Bedrock.Framework 8 | { 9 | public class ConnectionPoolingFactory : IConnectionFactory 10 | { 11 | // Represents a mapping from an individual endpoint to a pool of connections 12 | // Every endpoint can have multiple connections associated with it. 13 | private ConcurrentDictionary _pool; 14 | 15 | // The factory to create connections from 16 | private IConnectionFactory _innerFactory; 17 | 18 | public ConnectionPoolingFactory(IConnectionFactory innerFactory) 19 | { 20 | _innerFactory = innerFactory; 21 | _pool = new ConcurrentDictionary(); 22 | } 23 | 24 | public ValueTask DisposeAsync() 25 | { 26 | // Dispose all connections 27 | return default; 28 | } 29 | 30 | public ValueTask ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default) 31 | { 32 | var endPointPool = _pool.GetOrAdd( 33 | endPoint, 34 | (ep, factory) => 35 | { 36 | var maxConnections = ep is IMaxConnectionFeature maxConnectionEndpoint ? maxConnectionEndpoint.MaxConnections : 1; 37 | return new EndPointPool(ep, factory, maxConnections); 38 | }, _innerFactory); 39 | 40 | return endPointPool.GetConnectionAsync(cancellationToken); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pooling/HttpConnectionKind.cs: -------------------------------------------------------------------------------- 1 | namespace Bedrock.Framework 2 | { 3 | public enum HttpConnectionKind : byte 4 | { 5 | Http, // Non-secure connection with no proxy. 6 | Https, // Secure connection with no proxy. 7 | Proxy, // HTTP proxy usage for non-secure (HTTP) requests. 8 | ProxyTunnel, // Non-secure websocket (WS) connection using CONNECT tunneling through proxy. 9 | SslProxyTunnel, // HTTP proxy usage for secure (HTTPS/WSS) requests using SSL and proxy CONNECT. 10 | ProxyConnect // Connection used for proxy CONNECT. Tunnel will be established on top of this. 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pooling/HttpEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace Bedrock.Framework 5 | { 6 | // A endpoint to route http traffic to. 7 | // Used to pool based on 8 | public class HttpEndPoint : IPEndPoint, IMaxConnectionFeature 9 | { 10 | // Same options that are provided to the HttpConnectionPool. 11 | public HttpEndPoint(HttpConnectionKind kind, string host, int port, string sslHostName, Uri proxyUri, int maxConnections) 12 | : base(IPAddress.Parse(host), port) 13 | { 14 | Kind = kind; 15 | Host = host; 16 | SslHostName = sslHostName; 17 | ProxyUri = proxyUri; 18 | MaxConnections = maxConnections; 19 | } 20 | 21 | public HttpConnectionKind Kind { get; } 22 | public string Host { get; } 23 | public string SslHostName { get; } 24 | public Uri ProxyUri { get; } 25 | public int MaxConnections { get; } 26 | 27 | public override bool Equals(object comparand) 28 | { 29 | return comparand is HttpEndPoint other 30 | && base.Equals(comparand) 31 | && other.Kind.Equals(Kind) 32 | && other.Host.Equals(Host) 33 | && other.SslHostName.Equals(SslHostName) 34 | && other.ProxyUri.Equals(ProxyUri) 35 | && other.MaxConnections == MaxConnections; 36 | } 37 | 38 | public override int GetHashCode() 39 | { 40 | return HashCode.Combine(base.GetType(), 41 | Kind, 42 | Host, 43 | SslHostName, 44 | ProxyUri, 45 | MaxConnections); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pooling/IEndPointPool.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Connections; 4 | 5 | namespace Bedrock.Framework 6 | { 7 | internal interface IEndPointPool 8 | { 9 | ValueTask ReturnAsync(ConnectionContext context); 10 | ValueTask GetConnectionAsync(CancellationToken cancellationToken = default); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pooling/IMaxConnectionFeature.cs: -------------------------------------------------------------------------------- 1 | namespace Bedrock.Framework 2 | { 3 | internal interface IMaxConnectionFeature 4 | { 5 | int MaxConnections { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/Pooling/PooledConnectionContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Pipelines; 4 | using System.Net; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Connections; 8 | using Microsoft.AspNetCore.Http.Features; 9 | 10 | namespace Bedrock.Framework 11 | { 12 | internal class PooledConnectionContext : ConnectionContext, IAsyncDisposable 13 | { 14 | private ConnectionContext _connection; 15 | private IEndPointPool _pool; 16 | 17 | public PooledConnectionContext(ConnectionContext context, IEndPointPool pool) 18 | { 19 | _connection = context; 20 | _pool = pool; 21 | } 22 | 23 | public override string ConnectionId 24 | { 25 | get => _connection.ConnectionId; 26 | set => _connection.ConnectionId = value; 27 | } 28 | 29 | public override IFeatureCollection Features => _connection.Features; 30 | 31 | public override IDictionary Items 32 | { 33 | get => _connection.Items; 34 | set => _connection.Items = value; 35 | } 36 | 37 | public override IDuplexPipe Transport 38 | { 39 | get => _connection.Transport; 40 | set => _connection.Transport = value; 41 | } 42 | 43 | public override EndPoint LocalEndPoint 44 | { 45 | get => _connection.LocalEndPoint; 46 | set => _connection.LocalEndPoint = value; 47 | } 48 | 49 | public override EndPoint RemoteEndPoint 50 | { 51 | get => _connection.RemoteEndPoint; 52 | set => _connection.RemoteEndPoint = value; 53 | } 54 | 55 | public override CancellationToken ConnectionClosed 56 | { 57 | get => _connection.ConnectionClosed; 58 | set => _connection.ConnectionClosed = value; 59 | } 60 | 61 | public override void Abort() 62 | { 63 | _connection.Abort(); 64 | } 65 | 66 | public override void Abort(ConnectionAbortedException abortReason) 67 | { 68 | _connection.Abort(abortReason); 69 | } 70 | 71 | public override async ValueTask DisposeAsync() 72 | { 73 | await _pool.ReturnAsync(_connection); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/ServerBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Microsoft.AspNetCore.Connections; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Bedrock.Framework 7 | { 8 | public static partial class ServerBuilderExtensions 9 | { 10 | public static ServerBuilder ListenWebSocket(this ServerBuilder builder, Uri uri, Action serverApplication) 11 | { 12 | return builder.Listen(new UriEndPoint(uri), serverApplication); 13 | } 14 | 15 | public static ServerBuilder ListenHttp2(this ServerBuilder builder, Uri uri, Action serverApplication) 16 | { 17 | return builder.Listen(new UriEndPoint(uri), serverApplication); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Bedrock.Framework.Experimental/Transports/WebSockets/WebSocketConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Connections; 6 | using Microsoft.AspNetCore.Http.Connections; 7 | using Microsoft.AspNetCore.Http.Connections.Client; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace Bedrock.Framework 11 | { 12 | public class WebSocketConnectionFactory : IConnectionFactory 13 | { 14 | private readonly ILoggerFactory _loggerFactory; 15 | 16 | public WebSocketConnectionFactory(ILoggerFactory loggerFactory) 17 | { 18 | _loggerFactory = loggerFactory; 19 | } 20 | 21 | public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) 22 | { 23 | if (!(endpoint is UriEndPoint uriEndpoint)) 24 | { 25 | throw new NotSupportedException($"{endpoint} is not supported"); 26 | } 27 | 28 | var options = new HttpConnectionOptions 29 | { 30 | Url = uriEndpoint.Uri, 31 | Transports = HttpTransportType.WebSockets, 32 | SkipNegotiation = true 33 | }; 34 | 35 | var httpConnection = new HttpConnection(options, _loggerFactory); 36 | await httpConnection.StartAsync(); 37 | return httpConnection; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Bedrock.Framework.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0;net9.0 5 | High performance, low level networking APIs for building custom severs and clients. 6 | David Fowler 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Client/Client.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Connections; 5 | 6 | namespace Bedrock.Framework; 7 | 8 | public class Client(IConnectionFactory connectionFactory, ConnectionDelegate application) : IConnectionFactory 9 | { 10 | public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) 11 | { 12 | var connection = await connectionFactory.ConnectAsync(endpoint, cancellationToken).ConfigureAwait(false); 13 | 14 | // Since nothing is being returned from this middleware, we need to wait for the last middleware to run 15 | // until we yield this call. Stash a tcs in the items bag that allows this code to get notified 16 | // when the middleware ran 17 | var connectionContextWithDelegate = new ConnectionContextWithDelegate(connection, application); 18 | 19 | // Execute the middleware pipeline 20 | connectionContextWithDelegate.Start(); 21 | 22 | // Wait for it the most inner middleware to run 23 | return await connectionContextWithDelegate.Initialized.Task.ConfigureAwait(false); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Hosting/ServerHostedService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace Bedrock.Framework; 7 | 8 | public class ServerHostedService(IOptions options) : IHostedService 9 | { 10 | private readonly Server _server = options.Value.ServerBuilder.Build(); 11 | 12 | public Task StartAsync(CancellationToken cancellationToken) 13 | { 14 | return _server.StartAsync(cancellationToken); 15 | } 16 | 17 | public Task StopAsync(CancellationToken cancellationToken) 18 | { 19 | return _server.StopAsync(cancellationToken); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Hosting/ServerHostedServiceOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Bedrock.Framework; 2 | 3 | public class ServerHostedServiceOptions 4 | { 5 | public ServerBuilder ServerBuilder { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Hosting/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace Bedrock.Framework; 6 | 7 | public static class ServiceCollectionExtensions 8 | { 9 | public static IHostBuilder ConfigureServer(this IHostBuilder builder, Action configure) => 10 | builder.ConfigureServices(services => ConfigureServices(configure, services)); 11 | 12 | public static IHostApplicationBuilder ConfigureServer(this IHostApplicationBuilder builder, Action configure) 13 | { 14 | ConfigureServices(configure, builder.Services); 15 | return builder; 16 | } 17 | 18 | private static void ConfigureServices(Action configure, IServiceCollection services) 19 | { 20 | services.AddHostedService(); 21 | 22 | services.AddOptions() 23 | .Configure((options, sp) => 24 | { 25 | options.ServerBuilder = new ServerBuilder(sp); 26 | configure(options.ServerBuilder); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Infrastructure/DuplexPipe.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace System.IO.Pipelines; 5 | 6 | internal class DuplexPipe(PipeReader reader, PipeWriter writer) : IDuplexPipe 7 | { 8 | public PipeReader Input { get; } = reader; 9 | 10 | public PipeWriter Output { get; } = writer; 11 | 12 | public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions) 13 | { 14 | var input = new Pipe(inputOptions); 15 | var output = new Pipe(outputOptions); 16 | 17 | var transportToApplication = new DuplexPipe(output.Reader, input.Writer); 18 | var applicationToTransport = new DuplexPipe(input.Reader, output.Writer); 19 | 20 | return new DuplexPipePair(applicationToTransport, transportToApplication); 21 | } 22 | 23 | // This class exists to work around issues with value tuple on .NET Framework 24 | public readonly struct DuplexPipePair(IDuplexPipe transport, IDuplexPipe application) 25 | { 26 | public IDuplexPipe Transport { get; } = transport; 27 | public IDuplexPipe Application { get; } = application; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Bedrock.Framework/Infrastructure/DuplexPipeStreamAdapter.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.IO; 6 | using System.IO.Pipelines; 7 | using System.Threading.Tasks; 8 | 9 | namespace Bedrock.Framework.Infrastructure; 10 | 11 | /// 12 | /// A helper for wrapping a Stream decorator from an . 13 | /// 14 | /// 15 | internal class DuplexPipeStreamAdapter : DuplexPipeStream, IDuplexPipe where TStream : Stream 16 | { 17 | private bool _disposed; 18 | private readonly object _disposeLock = new object(); 19 | 20 | public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, Func createStream) : 21 | this(duplexPipe, new StreamPipeReaderOptions(leaveOpen: true), new StreamPipeWriterOptions(leaveOpen: true), createStream) 22 | { 23 | } 24 | 25 | public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func createStream) : 26 | base(duplexPipe.Input, duplexPipe.Output) 27 | { 28 | var stream = createStream(this); 29 | Stream = stream; 30 | Input = PipeReader.Create(stream, readerOptions); 31 | Output = PipeWriter.Create(stream, writerOptions); 32 | } 33 | 34 | public TStream Stream { get; } 35 | 36 | public PipeReader Input { get; } 37 | 38 | public PipeWriter Output { get; } 39 | 40 | public override async ValueTask DisposeAsync() 41 | { 42 | lock (_disposeLock) 43 | { 44 | if (_disposed) 45 | { 46 | return; 47 | } 48 | _disposed = true; 49 | } 50 | 51 | await Input.CompleteAsync().ConfigureAwait(false); 52 | await Output.CompleteAsync().ConfigureAwait(false); 53 | } 54 | 55 | protected override void Dispose(bool disposing) 56 | { 57 | throw new NotSupportedException(); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Infrastructure/EmptyServiceProvder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Bedrock.Framework.Infrastructure; 4 | 5 | internal class EmptyServiceProvider : IServiceProvider 6 | { 7 | public static IServiceProvider Instance { get; } = new EmptyServiceProvider(); 8 | 9 | public object GetService(Type serviceType) => null; 10 | } 11 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Infrastructure/MemoryPoolExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | 4 | namespace Bedrock.Framework.Infrastructure; 5 | 6 | internal static class MemoryPoolExtensions 7 | { 8 | /// 9 | /// Computes a minimum segment size 10 | /// 11 | /// 12 | /// 13 | public static int GetMinimumSegmentSize(this MemoryPool pool) 14 | { 15 | if (pool == null) 16 | { 17 | return 4096; 18 | } 19 | 20 | return Math.Min(4096, pool.MaxBufferSize); 21 | } 22 | 23 | public static int GetMinimumAllocSize(this MemoryPool pool) 24 | { 25 | // 1/2 of a segment 26 | return pool.GetMinimumSegmentSize() / 2; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Infrastructure/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Bedrock.Framework; 6 | 7 | internal static class TaskExtensions 8 | { 9 | public static async Task WithCancellation(this Task task, CancellationToken cancellationToken) 10 | { 11 | try 12 | { 13 | await task.WaitAsync(cancellationToken); 14 | return true; 15 | } 16 | catch (OperationCanceledException) 17 | { 18 | return false; 19 | } 20 | } 21 | 22 | public static async Task TimeoutAfter(this Task task, TimeSpan timeout) 23 | { 24 | try 25 | { 26 | using var cts = new CancellationTokenSource(timeout); 27 | await task.WaitAsync(cts.Token); 28 | return true; 29 | } 30 | catch (OperationCanceledException) 31 | { 32 | return false; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("Bedrock.Framework.Tests")] 4 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Middleware/ConnectionLimitMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Connections; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Bedrock.Framework; 8 | 9 | public class ConnectionLimitMiddleware(ConnectionDelegate next, ILogger logger, int limit) 10 | { 11 | private readonly SemaphoreSlim _limiter = new(limit); 12 | 13 | public async Task OnConnectionAsync(ConnectionContext connectionContext) 14 | { 15 | // Wait 10 seconds for a connection 16 | var task = _limiter.WaitAsync(TimeSpan.FromSeconds(10)); 17 | 18 | if (!task.IsCompletedSuccessfully) 19 | { 20 | logger.LogInformation("{ConnectionId} queued", connectionContext.ConnectionId); 21 | 22 | if (!await task.ConfigureAwait(false)) 23 | { 24 | logger.LogInformation("{ConnectionId} timed out in the connection queue", connectionContext.ConnectionId); 25 | return; 26 | } 27 | } 28 | 29 | try 30 | { 31 | await next(connectionContext).ConfigureAwait(false); 32 | } 33 | finally 34 | { 35 | _limiter.Release(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Middleware/LoggingConnectionMiddleware.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.IO.Pipelines; 6 | using System.Threading.Tasks; 7 | using Bedrock.Framework.Infrastructure; 8 | using Microsoft.AspNetCore.Connections; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Bedrock.Framework; 12 | 13 | internal class LoggingConnectionMiddleware(ConnectionDelegate next, ILogger logger, LoggingFormatter loggingFormatter = null) 14 | { 15 | private readonly ConnectionDelegate _next = next ?? throw new ArgumentNullException(nameof(next)); 16 | private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 17 | 18 | public async Task OnConnectionAsync(ConnectionContext context) 19 | { 20 | var oldTransport = context.Transport; 21 | 22 | try 23 | { 24 | await using var loggingDuplexPipe = new LoggingDuplexPipe(context.Transport, _logger, loggingFormatter); 25 | 26 | context.Transport = loggingDuplexPipe; 27 | 28 | await _next(context).ConfigureAwait(false); 29 | } 30 | finally 31 | { 32 | context.Transport = oldTransport; 33 | } 34 | } 35 | 36 | private class LoggingDuplexPipe(IDuplexPipe transport, ILogger logger, LoggingFormatter loggingFormatter) : 37 | DuplexPipeStreamAdapter(transport, stream => new LoggingStream(stream, logger, loggingFormatter)) 38 | { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Middleware/Tls/ITlsApplicationProtocolFeature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Bedrock.Framework.Middleware.Tls; 6 | 7 | public interface ITlsApplicationProtocolFeature 8 | { 9 | ReadOnlyMemory ApplicationProtocol { get; } 10 | } 11 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Middleware/Tls/ITlsConnectionFeature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Cryptography.X509Certificates; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Bedrock.Framework.Middleware.Tls; 9 | 10 | public interface ITlsConnectionFeature 11 | { 12 | /// 13 | /// Synchronously retrieves the remote endpoint's certificate, if any. 14 | /// 15 | X509Certificate2 RemoteCertificate { get; set; } 16 | 17 | /// 18 | /// Asynchronously retrieves the remote endpoint's certificate, if any. 19 | /// 20 | /// 21 | Task GetRemoteCertificateAsync(CancellationToken cancellationToken); 22 | } 23 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Middleware/Tls/ITlsHandshakeFeature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Authentication; 4 | using System.Text; 5 | 6 | namespace Bedrock.Framework.Middleware.Tls; 7 | 8 | public interface ITlsHandshakeFeature 9 | { 10 | SslProtocols Protocol { get; } 11 | 12 | CipherAlgorithmType CipherAlgorithm { get; } 13 | 14 | int CipherStrength { get; } 15 | 16 | HashAlgorithmType HashAlgorithm { get; } 17 | 18 | int HashStrength { get; } 19 | 20 | ExchangeAlgorithmType KeyExchangeAlgorithm { get; } 21 | 22 | int KeyExchangeStrength { get; } 23 | } 24 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Middleware/Tls/RemoteCertificateMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Bedrock.Framework.Middleware.Tls; 6 | 7 | /// 8 | /// Describes the remote certificate requirements for a TLS connection. 9 | /// 10 | public enum RemoteCertificateMode 11 | { 12 | /// 13 | /// A remote certificate is not required and will not be requested from remote endpoints. 14 | /// 15 | NoCertificate, 16 | 17 | /// 18 | /// A remote certificate will be requested; however, authentication will not fail if a certificate is not provided by the remote endpoint. 19 | /// 20 | AllowCertificate, 21 | 22 | /// 23 | /// A remote certificate will be requested, and the remote endpoint must provide a valid certificate for authentication. 24 | /// 25 | RequireCertificate 26 | } 27 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Middleware/Tls/SslDuplexPipe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Pipelines; 4 | using System.Net.Security; 5 | using Bedrock.Framework.Infrastructure; 6 | 7 | namespace Bedrock.Framework.Middleware.Tls; 8 | 9 | internal class SslDuplexPipe : DuplexPipeStreamAdapter 10 | { 11 | public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions) 12 | : this(transport, readerOptions, writerOptions, s => new SslStream(s)) 13 | { 14 | 15 | } 16 | 17 | public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func factory) : 18 | base(transport, readerOptions, writerOptions, factory) 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Middleware/Tls/TlsConnectionFeature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Authentication; 4 | using System.Security.Cryptography.X509Certificates; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Bedrock.Framework.Middleware.Tls; 10 | 11 | internal class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProtocolFeature, ITlsHandshakeFeature 12 | { 13 | public X509Certificate2 LocalCertificate { get; set; } 14 | 15 | public X509Certificate2 RemoteCertificate { get; set; } 16 | 17 | public ReadOnlyMemory ApplicationProtocol { get; set; } 18 | 19 | public SslProtocols Protocol { get; set; } 20 | 21 | public CipherAlgorithmType CipherAlgorithm { get; set; } 22 | 23 | public int CipherStrength { get; set; } 24 | 25 | public HashAlgorithmType HashAlgorithm { get; set; } 26 | 27 | public int HashStrength { get; set; } 28 | 29 | public ExchangeAlgorithmType KeyExchangeAlgorithm { get; set; } 30 | 31 | public int KeyExchangeStrength { get; set; } 32 | 33 | public Task GetRemoteCertificateAsync(CancellationToken cancellationToken) 34 | { 35 | return Task.FromResult(RemoteCertificate); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Protocols/IMessageReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | 4 | namespace Bedrock.Framework.Protocols 5 | { 6 | public interface IMessageReader 7 | { 8 | bool TryParseMessage(in ReadOnlySequence input, ref SequencePosition consumed, ref SequencePosition examined, out TMessage message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Protocols/IMessageWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | namespace Bedrock.Framework.Protocols 4 | { 5 | public interface IMessageWriter 6 | { 7 | void WriteMessage(TMessage message, IBufferWriter output); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Protocols/Protocol.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.IO.Pipelines; 3 | using System.Threading; 4 | using Microsoft.AspNetCore.Connections; 5 | 6 | namespace Bedrock.Framework.Protocols 7 | { 8 | public static class Protocol 9 | { 10 | public static ProtocolWriter CreateWriter(this ConnectionContext connection) 11 | => new ProtocolWriter(connection.Transport.Output); 12 | 13 | public static ProtocolWriter CreateWriter(this ConnectionContext connection, SemaphoreSlim semaphore) 14 | => new ProtocolWriter(connection.Transport.Output, semaphore); 15 | 16 | public static ProtocolReader CreateReader(this ConnectionContext connection) 17 | => new ProtocolReader(connection.Transport.Input); 18 | 19 | public static PipeReader CreatePipeReader(this ConnectionContext connection, IMessageReader> messageReader) 20 | => new MessagePipeReader(connection.Transport.Input, messageReader); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Protocols/ProtocolReadResult.cs: -------------------------------------------------------------------------------- 1 | namespace Bedrock.Framework.Protocols 2 | { 3 | /// 4 | /// Represents a reading result from . 5 | /// 6 | /// The read message. 7 | public readonly struct ProtocolReadResult 8 | { 9 | public ProtocolReadResult(TMessage message, bool isCanceled, bool isCompleted) 10 | { 11 | Message = message; 12 | IsCanceled = isCanceled; 13 | IsCompleted = isCompleted; 14 | } 15 | 16 | /// 17 | /// The read message. 18 | /// 19 | public TMessage Message { get; } 20 | 21 | /// 22 | /// Whether the reading operation was cancelled (true) or not (false). 23 | /// 24 | public bool IsCanceled { get; } 25 | 26 | /// 27 | /// Whether the reading operation was completed (true) or not (false). 28 | /// 29 | public bool IsCompleted { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Protocols/WebSockets/WebSocketFrameException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace Bedrock.Framework.Protocols.WebSockets 7 | { 8 | /// 9 | /// An exception thrown when WebSocket frame data is in error. 10 | /// 11 | [Serializable] 12 | public class WebSocketFrameException : Exception 13 | { 14 | /// 15 | /// Creates an instance of a WebSocketFrameException. 16 | /// 17 | /// The message containing the description of the frame error. 18 | public WebSocketFrameException(string message) : base(message) { } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Protocols/WebSockets/WebSocketOpcode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Bedrock.Framework.Protocols.WebSockets 6 | { 7 | /// 8 | /// The opcode of a WebSocket message frame header. 9 | /// 10 | public enum WebSocketOpcode : byte 11 | { 12 | /// 13 | /// A message containing the continuation of the payload of a previous frame. 14 | /// 15 | Continuation = 0x0, 16 | 17 | /// 18 | /// A message frame containing a text message. 19 | /// 20 | Text = 0x1, 21 | 22 | /// 23 | /// A message frame containing a binary message. 24 | /// 25 | Binary = 0x2, 26 | 27 | /// 28 | /// A control message frame containing a socket close message. 29 | /// 30 | Close = 0x8, 31 | 32 | /// 33 | /// A control message frame containing a ping message. 34 | /// 35 | Ping = 0x9, 36 | 37 | /// 38 | /// A control message frame containing a pong message. 39 | /// 40 | Pong = 0xA 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Protocols/WebSockets/WebSocketReadFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Bedrock.Framework.Protocols.WebSockets 6 | { 7 | /// 8 | /// A WebSocket frame to read as input. 9 | /// 10 | public readonly struct WebSocketReadFrame 11 | { 12 | /// 13 | /// The header of the WebSocket frame. 14 | /// 15 | public WebSocketHeader Header { get; } 16 | 17 | /// 18 | /// A message reader for reading the WebSocket payload. 19 | /// 20 | public WebSocketPayloadReader Payload { get; } 21 | 22 | /// 23 | /// Creates an instance of a WebSocketReadFrame. 24 | /// 25 | /// The header of the WebSocket frame. 26 | /// A message reader for reading the WebSocket payload. 27 | public WebSocketReadFrame(WebSocketHeader header, WebSocketPayloadReader payload) 28 | { 29 | Header = header; 30 | Payload = payload; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Protocols/WebSockets/WebSocketWriteFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Bedrock.Framework.Protocols.WebSockets 7 | { 8 | /// 9 | /// A WebSocket frame to write as output. 10 | /// 11 | public class WebSocketWriteFrame 12 | { 13 | /// 14 | /// The header of the WebSocket frame. 15 | /// 16 | public WebSocketHeader Header { get; } 17 | 18 | /// 19 | /// The payload of the WebSocket frame. 20 | /// 21 | public ReadOnlySequence Payload { get; } 22 | 23 | /// 24 | /// Whether or not the payload sequence has already been masked 25 | /// for delivery, if necessary. 26 | /// 27 | internal bool MaskingComplete { get; set; } 28 | 29 | /// 30 | /// Creates an instance of a WebSocketWriteFrame. 31 | /// 32 | /// The header of the WebSocket frame. 33 | /// The payload of the WebSocket frame. 34 | public WebSocketWriteFrame(WebSocketHeader header, ReadOnlySequence payload) 35 | { 36 | Header = header; 37 | Payload = payload; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Server/EndPointBinding.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Runtime.CompilerServices; 4 | using System.Threading; 5 | using Microsoft.AspNetCore.Connections; 6 | 7 | namespace Bedrock.Framework; 8 | 9 | public class EndPointBinding(EndPoint endPoint, ConnectionDelegate application, IConnectionListenerFactory connectionListenerFactory) : ServerBinding 10 | { 11 | public override ConnectionDelegate Application => application; 12 | 13 | public override async IAsyncEnumerable BindAsync([EnumeratorCancellation]CancellationToken cancellationToken) 14 | { 15 | yield return await connectionListenerFactory.BindAsync(endPoint, cancellationToken); 16 | } 17 | 18 | public override string ToString() 19 | { 20 | return endPoint?.ToString(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Server/LocalHostBinding.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Runtime.CompilerServices; 6 | using System.Threading; 7 | using Microsoft.AspNetCore.Connections; 8 | 9 | namespace Bedrock.Framework; 10 | 11 | public class LocalHostBinding(int port, ConnectionDelegate application, IConnectionListenerFactory connectionListenerFactory) : ServerBinding 12 | { 13 | public override ConnectionDelegate Application => application; 14 | 15 | public override async IAsyncEnumerable BindAsync([EnumeratorCancellation]CancellationToken cancellationToken = default) 16 | { 17 | var exceptions = new List(); 18 | 19 | IConnectionListener ipv6Listener = null; 20 | IConnectionListener ipv4Listener = null; 21 | 22 | try 23 | { 24 | ipv6Listener = await connectionListenerFactory.BindAsync(new IPEndPoint(IPAddress.IPv6Loopback, port), cancellationToken); 25 | } 26 | catch (Exception ex) when (ex is not IOException) 27 | { 28 | exceptions.Add(ex); 29 | } 30 | 31 | if (ipv6Listener != null) 32 | { 33 | yield return ipv6Listener; 34 | } 35 | 36 | try 37 | { 38 | ipv4Listener = await connectionListenerFactory.BindAsync(new IPEndPoint(IPAddress.Loopback, port), cancellationToken); 39 | } 40 | catch (Exception ex) when (ex is not IOException) 41 | { 42 | exceptions.Add(ex); 43 | } 44 | 45 | if (exceptions.Count == 2) 46 | { 47 | throw new IOException($"Failed to bind to {this}", new AggregateException(exceptions)); 48 | } 49 | 50 | if (ipv4Listener != null) 51 | { 52 | yield return ipv4Listener; 53 | } 54 | } 55 | 56 | public override string ToString() 57 | { 58 | return $"localhost:{port}"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Server/ServerBinding.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Connections; 5 | 6 | namespace Bedrock.Framework 7 | { 8 | public abstract class ServerBinding 9 | { 10 | public virtual ConnectionDelegate Application { get; } 11 | 12 | public abstract IAsyncEnumerable BindAsync(CancellationToken cancellationToken = default); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Server/ServerBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Bedrock.Framework.Infrastructure; 4 | 5 | namespace Bedrock.Framework; 6 | 7 | public class ServerBuilder 8 | { 9 | public ServerBuilder() : this(EmptyServiceProvider.Instance) 10 | { 11 | 12 | } 13 | 14 | public ServerBuilder(IServiceProvider serviceProvider) 15 | { 16 | ApplicationServices = serviceProvider; 17 | } 18 | 19 | public IList Bindings { get; } = []; 20 | 21 | public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5); 22 | 23 | public TimeSpan HeartBeatInterval { get; set; } = TimeSpan.FromSeconds(1); 24 | 25 | public IServiceProvider ApplicationServices { get; } 26 | 27 | public Server Build() 28 | { 29 | return new Server(this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Server/ServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Logging.Abstractions; 6 | 7 | namespace Bedrock.Framework 8 | { 9 | internal static class ServiceProviderExtensions 10 | { 11 | internal static ILoggerFactory GetLoggerFactory(this IServiceProvider serviceProvider) 12 | { 13 | return (ILoggerFactory)serviceProvider?.GetService(typeof(ILoggerFactory)) ?? NullLoggerFactory.Instance; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Transports/Memory/MemoryEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace Bedrock.Framework.Transports.Memory; 4 | 5 | public class MemoryEndPoint(string name) : EndPoint 6 | { 7 | public static readonly MemoryEndPoint Default = new MemoryEndPoint("default"); 8 | 9 | public string Name { get; } = name; 10 | 11 | public override string ToString() => Name; 12 | 13 | public override int GetHashCode() => Name.GetHashCode(); 14 | } 15 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Transports/ServerBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Microsoft.AspNetCore.Connections; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Bedrock.Framework; 7 | 8 | public static partial class ServerBuilderExtensions 9 | { 10 | public static ServerBuilder Listen(this ServerBuilder builder, EndPoint endPoint, Action configure) where TTransport : IConnectionListenerFactory 11 | { 12 | return builder.Listen(endPoint, ActivatorUtilities.CreateInstance(builder.ApplicationServices), configure); 13 | } 14 | 15 | public static ServerBuilder Listen(this ServerBuilder builder, EndPoint endPoint, IConnectionListenerFactory connectionListenerFactory, Action configure) 16 | { 17 | var connectionBuilder = new ConnectionBuilder(builder.ApplicationServices); 18 | configure(connectionBuilder); 19 | builder.Bindings.Add(new EndPointBinding(endPoint, connectionBuilder.Build(), connectionListenerFactory)); 20 | return builder; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Transports/Sockets/BufferExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Bedrock.Framework; 5 | 6 | internal static class BufferExtensions 7 | { 8 | public static ArraySegment GetArray(this Memory memory) 9 | { 10 | return ((ReadOnlyMemory)memory).GetArray(); 11 | } 12 | 13 | public static ArraySegment GetArray(this ReadOnlyMemory memory) 14 | { 15 | if (!MemoryMarshal.TryGetArray(memory, out var result)) 16 | { 17 | throw new InvalidOperationException("Buffer backed by array was expected"); 18 | } 19 | return result; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Transports/Sockets/ServerBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Bedrock.Framework; 4 | 5 | public static partial class ServerBuilderExtensions 6 | { 7 | public static ServerBuilder UseSockets(this ServerBuilder serverBuilder, Action configure) 8 | { 9 | var socketsBuilder = new SocketsServerBuilder(); 10 | configure(socketsBuilder); 11 | socketsBuilder.Apply(serverBuilder); 12 | return serverBuilder; 13 | } 14 | 15 | public static ClientBuilder UseSockets(this ClientBuilder clientBuilder) 16 | { 17 | return clientBuilder.UseConnectionFactory(new SocketConnectionFactory()); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Bedrock.Framework/Transports/Sockets/SocketAwaitable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO.Pipelines; 4 | using System.Net.Sockets; 5 | using System.Runtime.CompilerServices; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Bedrock.Framework; 10 | 11 | internal class SocketAwaitable : ICriticalNotifyCompletion 12 | { 13 | private static readonly Action _callbackCompleted = () => { }; 14 | 15 | private readonly PipeScheduler _ioScheduler; 16 | 17 | private Action _callback; 18 | private int _bytesTransferred; 19 | private SocketError _error; 20 | 21 | public SocketAwaitable(PipeScheduler ioScheduler) 22 | { 23 | _ioScheduler = ioScheduler; 24 | } 25 | 26 | public SocketAwaitable GetAwaiter() => this; 27 | public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); 28 | 29 | public int GetResult() 30 | { 31 | Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); 32 | 33 | _callback = null; 34 | 35 | if (_error != SocketError.Success) 36 | { 37 | throw new SocketException((int)_error); 38 | } 39 | 40 | return _bytesTransferred; 41 | } 42 | 43 | public void OnCompleted(Action continuation) 44 | { 45 | if (ReferenceEquals(_callback, _callbackCompleted) || 46 | ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted)) 47 | { 48 | Task.Run(continuation); 49 | } 50 | } 51 | 52 | public void UnsafeOnCompleted(Action continuation) 53 | { 54 | OnCompleted(continuation); 55 | } 56 | 57 | public void Complete(int bytesTransferred, SocketError socketError) 58 | { 59 | _error = socketError; 60 | _bytesTransferred = bytesTransferred; 61 | var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); 62 | 63 | if (continuation != null) 64 | { 65 | _ioScheduler.Schedule(state => ((Action)state)(), continuation); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Transports/Sockets/SocketConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Connections; 5 | 6 | namespace Bedrock.Framework; 7 | 8 | public class SocketConnectionFactory : IConnectionFactory 9 | { 10 | public ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) 11 | { 12 | return new SocketConnection(endpoint).StartAsync(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Transports/Sockets/SocketReceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Pipelines; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework; 8 | 9 | internal class SocketReceiver 10 | { 11 | private readonly Socket _socket; 12 | private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs(); 13 | private readonly SocketAwaitable _awaitable; 14 | 15 | public SocketReceiver(Socket socket, PipeScheduler scheduler) 16 | { 17 | _socket = socket; 18 | _awaitable = new SocketAwaitable(scheduler); 19 | _eventArgs.UserToken = _awaitable; 20 | _eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError); 21 | } 22 | 23 | public SocketAwaitable ReceiveAsync(Memory buffer) 24 | { 25 | #if NETCOREAPP 26 | _eventArgs.SetBuffer(buffer); 27 | #else 28 | var segment = buffer.GetArray(); 29 | 30 | _eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); 31 | #endif 32 | if (!_socket.ReceiveAsync(_eventArgs)) 33 | { 34 | _awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError); 35 | } 36 | 37 | return _awaitable; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Transports/Sockets/SocketSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO.Pipelines; 6 | using System.Net.Sockets; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace Bedrock.Framework; 10 | 11 | internal class SocketSender 12 | { 13 | private readonly Socket _socket; 14 | private readonly SocketAsyncEventArgs _eventArgs = new SocketAsyncEventArgs(); 15 | private readonly SocketAwaitable _awaitable; 16 | 17 | private List> _bufferList; 18 | 19 | public SocketSender(Socket socket, PipeScheduler scheduler) 20 | { 21 | _socket = socket; 22 | _awaitable = new SocketAwaitable(scheduler); 23 | _eventArgs.UserToken = _awaitable; 24 | _eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError); 25 | } 26 | 27 | public SocketAwaitable SendAsync(in ReadOnlySequence buffers) 28 | { 29 | if (buffers.IsSingleSegment) 30 | { 31 | return SendAsync(buffers.First); 32 | } 33 | 34 | #if NETCOREAPP 35 | if (!_eventArgs.MemoryBuffer.Equals(Memory.Empty)) 36 | #else 37 | if (_eventArgs.Buffer != null) 38 | #endif 39 | { 40 | _eventArgs.SetBuffer(null, 0, 0); 41 | } 42 | 43 | _eventArgs.BufferList = GetBufferList(buffers); 44 | 45 | if (!_socket.SendAsync(_eventArgs)) 46 | { 47 | _awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError); 48 | } 49 | 50 | return _awaitable; 51 | } 52 | 53 | private SocketAwaitable SendAsync(ReadOnlyMemory memory) 54 | { 55 | // The BufferList getter is much less expensive then the setter. 56 | if (_eventArgs.BufferList != null) 57 | { 58 | _eventArgs.BufferList = null; 59 | } 60 | 61 | #if NETCOREAPP 62 | _eventArgs.SetBuffer(MemoryMarshal.AsMemory(memory)); 63 | #else 64 | var segment = memory.GetArray(); 65 | 66 | _eventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); 67 | #endif 68 | if (!_socket.SendAsync(_eventArgs)) 69 | { 70 | _awaitable.Complete(_eventArgs.BytesTransferred, _eventArgs.SocketError); 71 | } 72 | 73 | return _awaitable; 74 | } 75 | 76 | private List> GetBufferList(in ReadOnlySequence buffer) 77 | { 78 | Debug.Assert(!buffer.IsEmpty); 79 | Debug.Assert(!buffer.IsSingleSegment); 80 | 81 | if (_bufferList == null) 82 | { 83 | _bufferList = new List>(); 84 | } 85 | else 86 | { 87 | // Buffers are pooled, so it's OK to root them until the next multi-buffer write. 88 | _bufferList.Clear(); 89 | } 90 | 91 | foreach (var b in buffer) 92 | { 93 | _bufferList.Add(b.GetArray()); 94 | } 95 | 96 | return _bufferList; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Bedrock.Framework/Transports/Sockets/SocketsServerBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using Microsoft.AspNetCore.Connections; 6 | using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; 7 | 8 | namespace Bedrock.Framework; 9 | 10 | public class SocketsServerBuilder 11 | { 12 | private List<(EndPoint EndPoint, int Port, Action Application)> _bindings = []; 13 | 14 | public SocketTransportOptions Options { get; } = new SocketTransportOptions(); 15 | 16 | public SocketsServerBuilder Listen(EndPoint endPoint, Action configure) 17 | { 18 | _bindings.Add((endPoint, 0, configure)); 19 | return this; 20 | } 21 | 22 | public SocketsServerBuilder Listen(IPAddress address, int port, Action configure) 23 | { 24 | return Listen(new IPEndPoint(address, port), configure); 25 | } 26 | 27 | public SocketsServerBuilder ListenAnyIP(int port, Action configure) 28 | { 29 | return Listen(IPAddress.Any, port, configure); 30 | } 31 | 32 | public SocketsServerBuilder ListenLocalhost(int port, Action configure) 33 | { 34 | _bindings.Add((null, port, configure)); 35 | return this; 36 | } 37 | 38 | public SocketsServerBuilder ListenUnixSocket(string socketPath, Action configure) 39 | { 40 | return Listen(new UnixDomainSocketEndPoint(socketPath), configure); 41 | } 42 | 43 | internal void Apply(ServerBuilder builder) 44 | { 45 | var socketTransportFactory = new SocketTransportFactory(Microsoft.Extensions.Options.Options.Create(Options), builder.ApplicationServices.GetLoggerFactory()); 46 | 47 | foreach (var binding in _bindings) 48 | { 49 | if (binding.EndPoint == null) 50 | { 51 | var connectionBuilder = new ConnectionBuilder(builder.ApplicationServices); 52 | binding.Application(connectionBuilder); 53 | builder.Bindings.Add(new LocalHostBinding(binding.Port, connectionBuilder.Build(), socketTransportFactory)); 54 | } 55 | else 56 | { 57 | 58 | builder.Listen(binding.EndPoint, socketTransportFactory, binding.Application); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Bedrock.Framework.Benchmarks/Bedrock.Framework.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/Bedrock.Framework.Benchmarks/BenchmarkConfig.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Configs; 2 | using BenchmarkDotNet.Diagnosers; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Bedrock.Framework.Benchmarks 7 | { 8 | public class BenchmarkConfig : ManualConfig 9 | { 10 | public BenchmarkConfig() 11 | { 12 | Add(DefaultConfig.Instance); 13 | Add(MemoryDiagnoser.Default); 14 | 15 | ArtifactsPath = Path.Combine(AppContext.BaseDirectory, "artifacts", DateTime.Now.ToString("yyyy-mm-dd_hh-MM-ss")); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Bedrock.Framework.Benchmarks/HttpHeaderReaderBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using Bedrock.Framework.Protocols.Http.Http1; 2 | using BenchmarkDotNet.Attributes; 3 | using System; 4 | using System.Buffers; 5 | using System.Text; 6 | 7 | namespace Bedrock.Framework.Benchmarks 8 | { 9 | public class HttpHeaderReaderBenchmarks 10 | { 11 | [Params(10, 1000)] 12 | public int HeaderValueLength { get; set; } 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | var data = "/search?rlz=1C1GCEA_enIL874IL874&sxsrf=ACYBGNQAjkw0VCQs1ElFY43_D_Wq9DMwQA%3A1580624707397&ei=Q2s2XtjxF8_RwAK35ocQ&q=request+header+example&oq=request+header+example&gs_l=psy-ab.3..0i7i30l10.9636.9636..9981...0.2..0.245.245.2-1......0....1..gws-wiz.......0i71.JfZoTvof620&ved=0ahUKEwiYn9TxnbLnAhXPKFAKHTfzAQIQ4dUDCAs&uact=5/search?rlz=1C1GCEA_enIL874IL874&sxsrf=ACYBGNQAjkw0VCQs1ElFY43_D_Wq9DMwQA%3A1580624707397&ei=Q2s2XtjxF8_RwAK35ocQ&q=request+header+example&oq=request+header+example&gs_l=psy-ab.3..0i7i30l10.9636.9636..9981...0.2..0.245.245.2-1......0....1..gws-wiz.......0i71.JfZoTvof620&ved=0ahUKEwiYn9TxnbLnAhXPKFAKHTfzAQIQ4dUDCAs&uact=5/search?rlz=1C1GCEA_enIL874IL874&sxsrf=ACYBGNQAjkw0VCQs1ElFY43_D_Wq9DMwQA%3A1580624707397&ei=Q2s2XtjxF8_RwAK35ocQ&q=request+header+example&oq=request+header+example&gs_l=psy-ab.3..0i7i30l10.9636.9636..9981...0.2..0.245.245.2-1......0....1..gws-wiz.......0i71.JfZoTvof620&ved=0ahUKEwiYn9TxnbLnAhXPKFAKHTfzAQIQ4dUDCAs&uact=5/search?rlz=1C1GCEA_enIL874IL874&sxsrf=ACYBGNQAjkw0VCQs1ElFY43_D_Wq9DMwQA%3A1580624707397&ei=Q2s2XtjxF8_RwAK35ocQ&q=request+header+example&oq=request+header+example&gs_l=psy-ab.3..0i7i30l10.9636.9636..9981...0.2..0.245.245.2-1......0....1..gws-wiz.......0i71.JfZoTvof620&ved=0ahUKEwiYn9TxnbLnAhXPKFAKHTfzAQIQ4dUDCAs&uact=5"; 18 | bytes = new ReadOnlySequence(Encoding.ASCII.GetBytes($"Header-Name:{data[..HeaderValueLength]}\r\n")); 19 | } 20 | 21 | private ReadOnlySequence bytes; 22 | private Http1HeaderReader reader = new Http1HeaderReader(); 23 | 24 | [Benchmark] 25 | public bool ReadHeader() 26 | { 27 | var consumed = bytes.Start; 28 | var examined = bytes.Start; 29 | return reader.TryParseMessage(in bytes, ref consumed, ref examined, out var result); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Bedrock.Framework.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using System; 3 | 4 | namespace Bedrock.Framework.Benchmarks 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | new BenchmarkSwitcher(AllBenchmarks).Run(args, new BenchmarkConfig()); 11 | } 12 | 13 | private static readonly Type[] AllBenchmarks = new[] 14 | { 15 | typeof(ProtocolReaderBenchmarks), 16 | typeof(MessagePipeReaderBenchmarks), 17 | typeof(HttpHeaderReaderBenchmarks) 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Bedrock.Framework.Tests/Bedrock.Framework.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0;net9.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/Bedrock.Framework.Tests/Http1Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Net.Http; 3 | using System.Text; 4 | using Bedrock.Framework.Protocols; 5 | using Xunit; 6 | 7 | namespace Bedrock.Framework.Tests 8 | { 9 | public class Http1Tests 10 | { 11 | [Fact] 12 | public void WriteHttp1MessageWithNoHost() 13 | { 14 | // arrange 15 | var messageWriter = new Http1RequestMessageWriter("www.host.com", 8080); 16 | 17 | var request = new HttpRequestMessage(HttpMethod.Get, "/api"); 18 | var output = new ArrayBufferWriter(); 19 | 20 | // act 21 | messageWriter.WriteMessage(request, output); 22 | var actual = Encoding.ASCII.GetString(output.WrittenSpan); 23 | 24 | // assert 25 | var expected = "Host: www.host.com:8080"; 26 | Assert.Contains(expected, actual); 27 | } 28 | 29 | [Fact] 30 | public void WriteHttp1MessageWithHost() 31 | { 32 | // arrange 33 | var messageWriter = new Http1RequestMessageWriter("www.host.com", 80); 34 | 35 | var request = new HttpRequestMessage(HttpMethod.Post, "/api"); 36 | request.Headers.Host = "www.another-host.com"; 37 | var output = new ArrayBufferWriter(); 38 | 39 | // act 40 | messageWriter.WriteMessage(request, output); 41 | var actual = Encoding.ASCII.GetString(output.WrittenSpan); 42 | 43 | // assert 44 | var expected = "Host: www.another-host.com"; 45 | Assert.Contains(expected, actual); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Bedrock.Framework.Tests/Infrastructure/TestSequenceSegment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Bedrock.Framework.Experimental.Tests.Infrastructure 7 | { 8 | /// 9 | /// A utility class for creating sequence segments from byte arrays during unit testing. 10 | /// 11 | public class TestSequenceSegment : ReadOnlySequenceSegment 12 | { 13 | /// 14 | /// Creates an instance of a TestSequenceSegment. 15 | /// 16 | /// The data to be contained in this segment. 17 | public TestSequenceSegment(byte[] data) 18 | { 19 | Memory = new Memory(data); 20 | } 21 | 22 | /// 23 | /// Adds a segment to the list of sequence segments. 24 | /// 25 | /// The data to be contained in the added segment. 26 | /// The new sequence segment. 27 | public TestSequenceSegment AddSegment(byte[] data) 28 | { 29 | var segment = new TestSequenceSegment(data); 30 | segment.RunningIndex = RunningIndex + Memory.Length; 31 | 32 | Next = segment; 33 | return segment; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "0.2-alpha", 4 | "nugetPackageVersion": { 5 | "semVer": 2 6 | } 7 | } --------------------------------------------------------------------------------