/* * Copyright (c) 2022 Vaughn Nugent * * Library: VNLib * Package: VNLib.Net.Messaging.FBM * File: ManagedClientWebSocket.cs * * ManagedClientWebSocket.cs is part of VNLib.Net.Messaging.FBM which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Net.Messaging.FBM is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * VNLib.Net.Messaging.FBM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ using System; using System.Net; using System.Threading; using System.Net.Security; using System.Net.WebSockets; using System.Threading.Tasks; using System.Security.Cryptography.X509Certificates; using VNLib.Utils.Memory; #nullable enable namespace VNLib.Net.Messaging.FBM.Client { /// /// A wrapper container to manage client websockets /// public class ManagedClientWebSocket : WebSocket { private readonly int TxBufferSize; private readonly int RxBufferSize; private readonly TimeSpan KeepAliveInterval; private readonly VnTempBuffer _dataBuffer; private readonly string? _subProtocol; /// /// A collection of headers to add to the client /// public WebHeaderCollection Headers { get; } public X509CertificateCollection Certificates { get; } public IWebProxy? Proxy { get; set; } public CookieContainer? Cookies { get; set; } public RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; set; } private ClientWebSocket? _socket; /// /// Initiaizes a new that accepts an optional sub-protocol for connections /// /// The size (in bytes) of the send buffer size /// The size (in bytes) of the receive buffer size to use /// The WS keepalive interval /// The optional sub-protocol to use public ManagedClientWebSocket(int txBufferSize, int rxBufferSize, TimeSpan keepAlive, string? subProtocol) { //Init header collection Headers = new(); Certificates = new(); //Alloc buffer _dataBuffer = new(rxBufferSize); TxBufferSize = txBufferSize; RxBufferSize = rxBufferSize; KeepAliveInterval = keepAlive; _subProtocol = subProtocol; } /// /// Asyncrhonously prepares a new client web-socket and connects to the remote endpoint /// /// The endpoint to connect to /// A token to cancel the connect operation /// A task that compeltes when the client has connected public async Task ConnectAsync(Uri serverUri, CancellationToken token) { //Init new socket _socket = new(); try { //Set buffer _socket.Options.SetBuffer(RxBufferSize, TxBufferSize, _dataBuffer); //Set remaining stored options _socket.Options.ClientCertificates = Certificates; _socket.Options.KeepAliveInterval = KeepAliveInterval; _socket.Options.Cookies = Cookies; _socket.Options.Proxy = Proxy; _socket.Options.RemoteCertificateValidationCallback = RemoteCertificateValidationCallback; //Specify sub-protocol if (!string.IsNullOrEmpty(_subProtocol)) { _socket.Options.AddSubProtocol(_subProtocol); } //Set headers for (int i = 0; i < Headers.Count; i++) { string name = Headers.GetKey(i); string? value = Headers.Get(i); //Set header _socket.Options.SetRequestHeader(name, value); } //Connect to server await _socket.ConnectAsync(serverUri, token); } catch { //Cleanup the socket Cleanup(); throw; } } /// /// Cleans up internal resources to prepare for another connection /// public void Cleanup() { //Dispose old socket if set _socket?.Dispose(); _socket = null; } /// public override WebSocketCloseStatus? CloseStatus => _socket?.CloseStatus; /// public override string CloseStatusDescription => _socket?.CloseStatusDescription ?? string.Empty; /// public override WebSocketState State => _socket?.State ?? WebSocketState.Closed; /// public override string SubProtocol => _subProtocol ?? string.Empty; /// public override void Abort() { _socket?.Abort(); } /// public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken) { return _socket?.CloseAsync(closeStatus, statusDescription, cancellationToken) ?? Task.CompletedTask; } /// public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken) { if (_socket?.State == WebSocketState.Open || _socket?.State == WebSocketState.CloseSent) { return _socket.CloseOutputAsync(closeStatus, statusDescription, cancellationToken); } return Task.CompletedTask; } /// public override ValueTask ReceiveAsync(Memory buffer, CancellationToken cancellationToken) { _ = _socket ?? throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, "The connected socket has been disconnected"); return _socket.ReceiveAsync(buffer, cancellationToken); } /// public override Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken) { _ = _socket ?? throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, "The connected socket has been disconnected"); return _socket.ReceiveAsync(buffer, cancellationToken); } /// public override ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) { _ = _socket ?? throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, "The connected socket has been disconnected"); return _socket.SendAsync(buffer, messageType, endOfMessage, cancellationToken); } /// public override Task SendAsync(ArraySegment buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) { _ = _socket ?? throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, "The connected socket has been disconnected"); return _socket.SendAsync(buffer, messageType, endOfMessage, cancellationToken); } /// public override void Dispose() { //Free buffer _dataBuffer.Dispose(); _socket?.Dispose(); GC.SuppressFinalize(this); } } }