/*
* 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);
}
}
}