using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.Security; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.Win32.SafeHandles; using WebSocketSharp.Native; using WebSocketSharp.Net; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyCompany("websocket-sharp")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyCopyright("Copyright 2026 JKLeckr")] [assembly: AssemblyDescription("A native C# wrapper for websocket-sharp")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1.0+8c66cd9f6ccbb12b197b4116d31a840b99cfdbba")] [assembly: AssemblyProduct("websocket-sharp")] [assembly: AssemblyTitle("websocket-sharp")] [assembly: AssemblyVersion("0.1.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace WebSocketSharp { public class CloseEventArgs : EventArgs { private readonly bool _clean; private readonly ushort _code; private readonly string _reason; public ushort Code => _code; public string Reason => _reason; public bool WasClean => _clean; internal CloseEventArgs(ushort code, string reason, bool clean) { _code = code; _reason = reason; _clean = clean; } } public class ErrorEventArgs : EventArgs { private readonly Exception _exception; private readonly string _message; public Exception Exception => _exception; public string Message => _message; internal ErrorEventArgs(string message) : this(message, null) { } internal ErrorEventArgs(string message, Exception exception) { _message = message ?? string.Empty; _exception = exception; } } public static class Logging { public enum NativeLogLevel { Off, Error, Warn, Info, Debug, Trace } public delegate void NativeLogHandler(NativeLogLevel level, string message); private const string TraceEnvironmentVariable = "NWS_LOGGING"; private const string TraceFileEnvironmentVariable = "NWS_LOG_FILE"; private const string TraceMarkerFileName = "nativews.log.enable"; private const string DefaultTraceFileName = "native-websocket-sharp.log"; private static readonly object Sync = new object(); private static readonly NativeLogCallback NativeLogBridge = HandleNativeLog; private static NativeLogHandler _nativeLogger; private static NativeLogLevel _nativeLogVerbosity = NativeLogLevel.Off; private static bool _initialized; private static bool _nativeLoggingSupported; private static string _traceFilePath; internal static NativeLogHandler NativeLogger { get { lock (Sync) { return _nativeLogger; } } set { lock (Sync) { EnsureInitializedLocked(); _nativeLogger = value; ApplyNativeLoggingLocked(); } } } internal static NativeLogLevel NativeLogVerbosity { get { lock (Sync) { return _nativeLogVerbosity; } } set { lock (Sync) { EnsureInitializedLocked(); _nativeLogVerbosity = value; ApplyNativeLoggingLocked(); } } } internal static bool NativeLoggingSupported { get { lock (Sync) { return _nativeLoggingSupported; } } } internal static void EnsureInitialized() { lock (Sync) { EnsureInitializedLocked(); } } internal static void Write(int socketId, string message) { Write("managed", socketId, message); } private static void EnsureInitializedLocked() { if (_initialized) { return; } _initialized = true; if (IsTraceEnabled()) { _traceFilePath = ResolveTraceFilePath(); if (_nativeLogger == null) { _nativeLogger = WriteNativeLog; } if (_nativeLogVerbosity == NativeLogLevel.Off) { _nativeLogVerbosity = NativeLogLevel.Trace; } Write("managed", 0, "trace enabled file=" + _traceFilePath); } ApplyNativeLoggingLocked(); } private static void ApplyNativeLoggingLocked() { try { WebSocketInterop.SetLogLevel((int)_nativeLogVerbosity); WebSocketInterop.SetLogHandler((_nativeLogger == null) ? null : NativeLogBridge); _nativeLoggingSupported = true; } catch (Exception ex) { _nativeLoggingSupported = false; Write("managed", 0, "native logging unavailable: " + ex.GetType().Name + ": " + ex.Message); } } private static bool IsTraceEnabled() { string environmentVariable = Environment.GetEnvironmentVariable("NWS_LOGGING"); if (!string.IsNullOrEmpty(environmentVariable) && !string.Equals(environmentVariable, "0", StringComparison.OrdinalIgnoreCase) && !string.Equals(environmentVariable, "false", StringComparison.OrdinalIgnoreCase)) { return true; } return File.Exists(Path.Combine(GetBaseDirectory(), "nativews.log.enable")) || File.Exists(Path.Combine(Environment.CurrentDirectory, "nativews.log.enable")); } private static string ResolveTraceFilePath() { string environmentVariable = Environment.GetEnvironmentVariable("NWS_LOG_FILE"); if (!string.IsNullOrEmpty(environmentVariable)) { return environmentVariable; } return Path.Combine(GetBaseDirectory(), "native-websocket-sharp.log"); } private static string GetBaseDirectory() { string baseDirectory = AppDomain.CurrentDomain.BaseDirectory; return string.IsNullOrEmpty(baseDirectory) ? Environment.CurrentDirectory : baseDirectory; } private static void HandleNativeLog(int level, IntPtr message) { NativeLogHandler nativeLogger; lock (Sync) { nativeLogger = _nativeLogger; } if (nativeLogger == null) { return; } try { string text = ((message == IntPtr.Zero) ? string.Empty : Marshal.PtrToStringAnsi(message)); nativeLogger(ClampLogLevel(level), text ?? string.Empty); } catch { } } private static NativeLogLevel ClampLogLevel(int level) { if (level <= 0) { return NativeLogLevel.Off; } if (level >= 5) { return NativeLogLevel.Trace; } return (NativeLogLevel)level; } private static void WriteNativeLog(NativeLogLevel level, string message) { Write("native/" + level, 0, message); } private static void Write(string source, int socketId, string message) { string traceFilePath; lock (Sync) { if (!_initialized) { EnsureInitializedLocked(); } traceFilePath = _traceFilePath; } if (string.IsNullOrEmpty(traceFilePath)) { return; } string text = ((socketId == 0) ? "-" : socketId.ToString()); string text2 = $"{DateTime.UtcNow:O} [{source}] [ws {text}] [thread {Thread.CurrentThread.ManagedThreadId}] {message ?? string.Empty}"; try { lock (Sync) { File.AppendAllText(traceFilePath, text2 + Environment.NewLine); } } catch { } } } public class MessageEventArgs : EventArgs { private readonly string _data; private readonly Opcode _opcode; private readonly byte[] _rawData; internal Opcode Opcode => _opcode; public string Data => _data; public bool IsBinary => _opcode == Opcode.Binary; public bool IsPing => _opcode == Opcode.Ping; public bool IsText => _opcode == Opcode.Text; public byte[] RawData => _rawData; internal MessageEventArgs(string data) { _data = data; _rawData = null; _opcode = Opcode.Text; } internal MessageEventArgs(Opcode opcode, byte[] rawData) { _opcode = opcode; _rawData = rawData; } } internal enum Opcode : byte { Cont = 0, Text = 1, Binary = 2, Close = 8, Ping = 9, Pong = 10 } public class WebSocket : IDisposable { private static readonly byte[] EmptyBytes = new byte[0]; private static readonly TimeSpan DefaultWaitTime = TimeSpan.FromSeconds(5.0); private static readonly TimeSpan PingCacheWindow = TimeSpan.FromSeconds(1.0); private static readonly TimeSpan PingTimeout = TimeSpan.FromSeconds(5.0); private static int _lastId; private readonly ManualResetEvent _closeCompleted; private readonly ManualResetEvent _connectCompleted; private readonly object _forMessageEventQueue; private readonly object _forPing; private readonly object _forSend; private readonly object _forState; private readonly Queue _messageEventQueue; private readonly ManualResetEvent _openEventCompleted; private readonly ManualResetEvent _pongReceived; private NativeWebSocketHandle _nativeClient; private DateTime _lastPongUtc; private Thread _pollThread; private bool _pollThreadStarted; private volatile WebSocketState _readyState; private readonly bool _secure; private ClientSslConfiguration _sslConfiguration; private readonly Uri _uri; private bool _disposed; private bool _connectSucceeded; private bool _closeReported; private bool _openEventPending; private bool _messageDispatching; private readonly int _id; private TimeSpan _waitTime; public bool IsAlive => ping(EmptyBytes); public bool IsSecure => _secure; public WebSocketState ReadyState => _readyState; public ClientSslConfiguration SslConfiguration { get { if (!_secure) { throw new InvalidOperationException("This instance does not use a secure connection."); } return _sslConfiguration ?? (_sslConfiguration = new ClientSslConfiguration(_uri.DnsSafeHost)); } } public Uri Url => _uri; public static Logging.NativeLogHandler NativeLogger { get { return Logging.NativeLogger; } set { Logging.NativeLogger = value; } } public static Logging.NativeLogLevel NativeLogVerbosity { get { return Logging.NativeLogVerbosity; } set { Logging.NativeLogVerbosity = value; } } public static bool NativeLoggingSupported => Logging.NativeLoggingSupported; public TimeSpan WaitTime { get { return _waitTime; } set { if (value <= TimeSpan.Zero) { throw new ArgumentOutOfRangeException("value", "Zero or less."); } lock (_forState) { if (_readyState == WebSocketState.Closed) { _waitTime = value; } } } } public event EventHandler OnClose; public event EventHandler OnError; public event EventHandler OnMessage; public event EventHandler OnOpen; public WebSocket(string url, params string[] protocols) { _id = Interlocked.Increment(ref _lastId); Logging.EnsureInitialized(); log_trace("constructor begin url=" + (url ?? "")); if (url == null) { throw new ArgumentNullException("url"); } if (url.Length == 0) { throw new ArgumentException("An empty string.", "url"); } if (!TryCreateWebSocketUri(url, out _uri, out var message)) { throw new ArgumentException(message, "url"); } if (protocols != null && protocols.Length != 0 && !CheckProtocols(protocols, out var message2)) { throw new ArgumentException(message2, "protocols"); } _secure = string.Equals(_uri.Scheme, "wss", StringComparison.OrdinalIgnoreCase); _readyState = WebSocketState.Closed; _closeCompleted = new ManualResetEvent(initialState: true); _connectCompleted = new ManualResetEvent(initialState: false); _messageEventQueue = new Queue(); _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot; _openEventCompleted = new ManualResetEvent(initialState: true); _forPing = new object(); _forSend = new object(); _forState = new object(); _pongReceived = new ManualResetEvent(initialState: false); _lastPongUtc = DateTime.MinValue; _waitTime = DefaultWaitTime; NativeResult nativeResult = WebSocketInterop.Create(_uri.ToString(), out _nativeClient); if (nativeResult != NativeResult.Ok || _nativeClient == null || _nativeClient.IsInvalid) { log_trace("constructor native create failed result=" + nativeResult); throw new InvalidOperationException("The native websocket client could not be created."); } log_trace("constructor complete secure=" + _secure + " state=" + _readyState); } public void Close() { close(1005, string.Empty); } public void Close(ushort code) { ValidateCloseCode(code); close(code, string.Empty); } public void Close(ushort code, string reason) { ValidateCloseCode(code); ValidateCloseReason(code, reason); close(code, reason ?? string.Empty); } public void CloseAsync() { closeAsync(1005, string.Empty); } public void CloseAsync(ushort code) { ValidateCloseCode(code); closeAsync(code, string.Empty); } public void CloseAsync(ushort code, string reason) { ValidateCloseCode(code); ValidateCloseReason(code, reason); closeAsync(code, reason ?? string.Empty); } public void Connect() { ThrowIfDisposed(); if (!connect()) { } } public void ConnectAsync() { ThrowIfDisposed(); ValidateConnectStart(); QueueBackground(delegate { try { Connect(); } catch { } }); } public bool Ping() { return ping(EmptyBytes); } public bool Ping(string message) { if (string.IsNullOrEmpty(message)) { return ping(EmptyBytes); } byte[] bytes; try { bytes = Encoding.UTF8.GetBytes(message); } catch (Exception) { throw new ArgumentException("It could not be UTF-8-encoded.", "message"); } if (bytes.Length > 125) { throw new ArgumentOutOfRangeException("message", "Its size is greater than 125 bytes."); } return ping(bytes); } public void Send(string data) { if (data == null) { throw new ArgumentNullException("data"); } byte[] bytes; try { bytes = Encoding.UTF8.GetBytes(data); } catch (Exception) { throw new ArgumentException("It could not be UTF-8-encoded.", "data"); } send(bytes, isBinary: false); } public void SendAsync(string data, Action completed) { if (data == null) { throw new ArgumentNullException("data"); } byte[] bytes; try { bytes = Encoding.UTF8.GetBytes(data); } catch (Exception) { throw new ArgumentException("It could not be UTF-8-encoded.", "data"); } ValidateSendState(); QueueBackground(delegate { bool flag = false; try { send(bytes, isBinary: false); flag = true; } catch { flag = false; } completed?.Invoke(flag); }); } public void Dispose() { log_trace("Dispose begin state=" + _readyState.ToString() + " disposed=" + _disposed); Dispose(disposing: true); GC.SuppressFinalize(this); log_trace("Dispose end state=" + _readyState.ToString() + " disposed=" + _disposed); } protected virtual void Dispose(bool disposing) { if (_disposed) { log_trace("Dispose(" + disposing + ") ignored; already disposed"); return; } if (!disposing) { _disposed = true; return; } try { if (_readyState == WebSocketState.Open || _readyState == WebSocketState.Connecting) { log_trace("Dispose closing active socket state=" + _readyState); close(1001, string.Empty); } else if (_readyState == WebSocketState.Closing && !_closeCompleted.WaitOne(_waitTime)) { log_trace("Dispose force closing timed out closing socket"); forceClose(1001, string.Empty); } } catch (Exception ex) { log_trace("Dispose swallowed close exception " + ex.GetType().Name + ": " + ex.Message); } _disposed = true; Thread thread = null; lock (_forState) { thread = _pollThread; } if (thread != null && thread.IsAlive && thread != Thread.CurrentThread) { log_trace("Dispose joining poll thread"); thread.Join(1000); log_trace("Dispose poll thread join complete alive=" + thread.IsAlive); } destroyNativeClient(); } protected virtual void RaiseOnOpen() { this.OnOpen?.Invoke(this, EventArgs.Empty); } protected virtual void RaiseOnClose(CloseEventArgs e) { this.OnClose?.Invoke(this, e); } protected virtual void RaiseOnError(ErrorEventArgs e) { this.OnError?.Invoke(this, e); } protected virtual void RaiseOnMessage(MessageEventArgs e) { this.OnMessage?.Invoke(this, e); } private void clearMessageEventQueue() { lock (_forMessageEventQueue) { _messageEventQueue.Clear(); _messageDispatching = false; } } private void dispatchMessageEvents() { while (true) { MessageEventArgs e; lock (_forMessageEventQueue) { if (_openEventPending || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { if (_readyState != WebSocketState.Open) { _messageEventQueue.Clear(); } _messageDispatching = false; break; } e = _messageEventQueue.Dequeue(); } log_trace("raising OnMessage"); try { RaiseOnMessage(e); } catch (Exception ex) { log_trace("OnMessage threw " + ex.GetType().Name + ": " + ex.Message); raiseOnErrorSafely(new ErrorEventArgs("An error has occurred during an OnMessage event.", ex)); } log_trace("OnMessage returned"); } } private void dispatchOpenEvent() { try { log_trace("raising OnOpen"); try { RaiseOnOpen(); } catch (Exception ex) { log_trace("OnOpen threw " + ex.GetType().Name + ": " + ex.Message); raiseOnErrorSafely(new ErrorEventArgs("An error has occurred during the OnOpen event.", ex)); } log_trace("OnOpen returned"); bool flag = false; lock (_forMessageEventQueue) { _openEventPending = false; if (!_messageDispatching && _messageEventQueue.Count != 0 && _readyState == WebSocketState.Open) { _messageDispatching = true; flag = true; } } if (flag) { log_trace("starting OnMessage dispatcher"); QueueBackground(dispatchMessageEvents); } } finally { _openEventCompleted.Set(); } } private void enqueueMessageEvent(MessageEventArgs e) { bool flag = false; lock (_forMessageEventQueue) { _messageEventQueue.Enqueue(e); if (!_openEventPending && !_messageDispatching && _readyState == WebSocketState.Open) { _messageDispatching = true; flag = true; } } if (flag) { log_trace("starting OnMessage dispatcher"); QueueBackground(dispatchMessageEvents); } } private void raiseOnCloseSafely(CloseEventArgs e) { try { RaiseOnClose(e); } catch (Exception ex) { log_trace("OnClose threw " + ex.GetType().Name + ": " + ex.Message); } } private void raiseOnErrorSafely(ErrorEventArgs e) { try { RaiseOnError(e); } catch (Exception ex) { log_trace("OnError threw " + ex.GetType().Name + ": " + ex.Message); } } private static bool CheckProtocols(string[] protocols, out string message) { message = null; for (int i = 0; i < protocols.Length; i++) { string text = protocols[i]; if (string.IsNullOrEmpty(text) || !IsToken(text)) { message = "It contains a value that is not a token."; return false; } for (int j = i + 1; j < protocols.Length; j++) { if (protocols[j] == text) { message = "It contains a value twice."; return false; } } } return true; } private static bool IsCloseStatusCode(ushort code) { return code > 999 && code < 5000; } private static bool IsToken(string value) { foreach (char c in value) { if (c < ' ' || c > '~') { return false; } switch (c) { case '\t': case ' ': case '"': case '(': case ')': case ',': case '/': case ':': case ';': case '<': case '=': case '>': case '?': case '@': case '[': case '\\': case ']': case '{': case '}': return false; } } return true; } private static bool TryCreateWebSocketUri(string uriString, out Uri result, out string message) { result = null; message = null; Uri.TryCreate(uriString, UriKind.Absolute, out Uri result2); if (result2 == null) { message = "An invalid URI string."; return false; } if (!result2.IsAbsoluteUri) { message = "A relative URI."; return false; } string scheme = result2.Scheme; if (scheme != "ws" && scheme != "wss") { message = "The scheme part is not 'ws' or 'wss'."; return false; } if (result2.Port == 0) { message = "The port part is zero."; return false; } if (result2.Fragment.Length > 0) { message = "It includes the fragment component."; return false; } if (result2.Port != -1) { result = result2; return true; } result = new Uri(string.Format("{0}://{1}:{2}{3}", scheme, result2.Host, (scheme == "ws") ? 80 : 443, result2.PathAndQuery)); return true; } private static void QueueBackground(ThreadStart action) { ThreadPool.QueueUserWorkItem(delegate { action(); }); } private void log_trace(string message) { Logging.Write(_id, message); } private void close(ushort code, string reason) { ThrowIfDisposed(); log_trace("close begin code=" + code + " reasonLength=" + (reason?.Length ?? 0) + " state=" + _readyState); if (_readyState == WebSocketState.Closed) { log_trace("close ignored; already closed"); return; } if (_readyState == WebSocketState.Closing) { log_trace("close ignored; already closing"); return; } NativeResult nativeResult; lock (_forState) { if (_readyState == WebSocketState.Closed || _readyState == WebSocketState.Closing) { return; } _closeCompleted.Reset(); _readyState = WebSocketState.Closing; startPollingLoop(); NativeWebSocketHandle nativeClient = getNativeClient(); nativeResult = ((nativeClient == null) ? NativeResult.Disposed : WebSocketInterop.Close(nativeClient, reason, code)); log_trace("native close result=" + nativeResult); } switch (nativeResult) { case NativeResult.Ok: if (!_closeCompleted.WaitOne(_waitTime)) { log_trace("close timed out after " + _waitTime.TotalMilliseconds + "ms; forcing close"); forceClose(code, reason ?? string.Empty); } log_trace("close complete state=" + _readyState); break; default: if (nativeResult != NativeResult.Disposed) { log_trace("close throwing result=" + nativeResult); throw CreateCommandException(nativeResult, "An error has occurred while attempting to close."); } goto case NativeResult.InvalidState; case NativeResult.InvalidState: log_trace("close finalizing after native result=" + nativeResult); finalizeClosedState(new CloseEventArgs(code, reason ?? string.Empty, clean: false), raiseEvent: false); break; } } private void closeAsync(ushort code, string reason) { ThrowIfDisposed(); QueueBackground(delegate { try { close(code, reason); } catch { } }); } private bool connect() { if (_readyState == WebSocketState.Open) { log_trace("connect ignored; already open"); return true; } ValidateConnectStart(); log_trace("connect begin state=" + _readyState.ToString() + " uri=" + _uri); NativeResult nativeResult; lock (_forState) { _connectSucceeded = false; _connectCompleted.Reset(); _closeCompleted.Reset(); _closeReported = false; _readyState = WebSocketState.Connecting; startPollingLoop(); NativeWebSocketHandle nativeClient = getNativeClient(); nativeResult = ((nativeClient == null) ? NativeResult.Disposed : WebSocketInterop.Connect(nativeClient)); log_trace("native connect result=" + nativeResult); } if (nativeResult != NativeResult.Ok) { _readyState = WebSocketState.Closed; log_trace("connect throwing native result=" + nativeResult); throw CreateCommandException(nativeResult, "An error has occurred while attempting to connect."); } log_trace("connect waiting for native open/error"); _connectCompleted.WaitOne(); if (_connectSucceeded) { _openEventCompleted.WaitOne(); } log_trace("connect wait complete succeeded=" + _connectSucceeded + " state=" + _readyState); return _connectSucceeded; } private void destroyNativeClient() { NativeWebSocketHandle nativeWebSocketHandle = null; lock (_forState) { if (_nativeClient == null) { log_trace("destroyNativeClient ignored; no native handle"); return; } nativeWebSocketHandle = _nativeClient; _nativeClient = null; } log_trace("destroyNativeClient disposing native handle"); nativeWebSocketHandle.Dispose(); } private void finalizeClosedState(CloseEventArgs closeEvent, bool raiseEvent) { bool flag = false; lock (_forState) { _readyState = WebSocketState.Closed; _connectSucceeded = false; _connectCompleted.Set(); _closeCompleted.Set(); _lastPongUtc = DateTime.MinValue; if (raiseEvent && !_closeReported) { _closeReported = true; flag = true; } } clearMessageEventQueue(); log_trace("finalizeClosedState code=" + closeEvent.Code + " wasClean=" + closeEvent.WasClean + " raise=" + flag); if (flag) { log_trace("raising OnClose"); raiseOnCloseSafely(closeEvent); log_trace("OnClose returned"); } } private void forceClose(ushort code, string reason) { log_trace("forceClose code=" + code + " reasonLength=" + (reason?.Length ?? 0)); abortNativeClient(code, reason); finalizeClosedState(new CloseEventArgs(code, reason ?? string.Empty, clean: false), raiseEvent: true); } private void abortNativeClient(ushort code, string reason) { NativeWebSocketHandle nativeClient; lock (_forState) { nativeClient = getNativeClient(); } if (nativeClient != null) { log_trace("native abort result=" + WebSocketInterop.Abort(nativeClient, reason, code)); } else { log_trace("native abort skipped; no handle"); } } private NativeWebSocketHandle getNativeClient() { NativeWebSocketHandle nativeClient = _nativeClient; return (nativeClient == null || nativeClient.IsInvalid || nativeClient.IsClosed) ? null : nativeClient; } private bool hasNativeClient() { return getNativeClient() != null; } private Exception CreateCommandException(NativeResult result, string defaultMessage) { if (1 == 0) { } Exception result2 = result switch { NativeResult.InvalidState => new InvalidOperationException(defaultMessage), NativeResult.NotOpen => new InvalidOperationException("The current state of the connection is not Open."), NativeResult.Disposed => new ObjectDisposedException(GetType().FullName), NativeResult.InvalidArgument => new ArgumentException(defaultMessage), NativeResult.Timeout => new TimeoutException(defaultMessage), _ => new InvalidOperationException(defaultMessage), }; if (1 == 0) { } return result2; } private Exception CreateErrorException(NativeErrorKind kind, string message) { if (1 == 0) { } Exception result; switch (kind) { case NativeErrorKind.Timeout: result = new TimeoutException(message); break; case NativeErrorKind.TlsFailed: result = new AuthenticationException(message); break; case NativeErrorKind.ConnectFailed: case NativeErrorKind.Io: result = new IOException(message); break; default: result = new InvalidOperationException(message); break; } if (1 == 0) { } return result; } private void handleNativeClose(NativeEvent nativeEvent) { string text = decodeString(nativeEvent.Data); log_trace("native event close code=" + nativeEvent.CloseCode + " wasClean=" + nativeEvent.CloseWasClean + " reasonLength=" + text.Length); finalizeClosedState(new CloseEventArgs(nativeEvent.CloseCode, text, nativeEvent.CloseWasClean), raiseEvent: true); } private void handleNativeError(NativeEvent nativeEvent) { string text = decodeString(nativeEvent.Data); Exception exception = CreateErrorException(nativeEvent.ErrorKind, text); log_trace("native event error kind=" + nativeEvent.ErrorKind.ToString() + " state=" + _readyState.ToString() + " message=" + text); lock (_forState) { if (_readyState == WebSocketState.Connecting) { _readyState = WebSocketState.Closed; _connectSucceeded = false; _connectCompleted.Set(); _closeCompleted.Set(); } } log_trace("raising OnError"); raiseOnErrorSafely(new ErrorEventArgs(text, exception)); log_trace("OnError returned"); } private void handleNativeEvent(NativeEvent nativeEvent) { log_trace("handleNativeEvent kind=" + nativeEvent.Kind.ToString() + " state=" + _readyState); switch (nativeEvent.Kind) { case NativeEventKind.Open: _openEventCompleted.Reset(); lock (_forMessageEventQueue) { _openEventPending = true; } lock (_forState) { _readyState = WebSocketState.Open; _connectSucceeded = true; _connectCompleted.Set(); } log_trace("native event open; starting OnOpen dispatcher"); QueueBackground(dispatchOpenEvent); break; case NativeEventKind.Close: handleNativeClose(nativeEvent); break; case NativeEventKind.Message: if (nativeEvent.MessageKind == NativeMessageKind.Text) { log_trace("native event text message bytes=" + ((nativeEvent.Data != null) ? nativeEvent.Data.Length : 0)); enqueueMessageEvent(new MessageEventArgs(decodeString(nativeEvent.Data))); } else { log_trace("native event binary message bytes=" + ((nativeEvent.Data != null) ? nativeEvent.Data.Length : 0)); enqueueMessageEvent(new MessageEventArgs(Opcode.Binary, nativeEvent.Data ?? new byte[0])); } break; case NativeEventKind.Error: handleNativeError(nativeEvent); break; case NativeEventKind.Pong: log_trace("native event pong bytes=" + ((nativeEvent.Data != null) ? nativeEvent.Data.Length : 0)); _lastPongUtc = DateTime.UtcNow; _pongReceived.Set(); break; } } private bool hasRecentPong() { DateTime lastPongUtc = _lastPongUtc; return lastPongUtc != DateTime.MinValue && DateTime.UtcNow - lastPongUtc <= PingCacheWindow; } private bool ping(byte[] payload) { ThrowIfDisposed(); log_trace("ping begin bytes=" + ((payload != null) ? payload.Length : 0) + " state=" + _readyState); if (_readyState != WebSocketState.Open) { log_trace("ping false; state=" + _readyState); return false; } if (hasRecentPong()) { log_trace("ping true; recent pong"); return true; } lock (_forPing) { if (_readyState != WebSocketState.Open) { return false; } if (hasRecentPong()) { return true; } _pongReceived.Reset(); NativeWebSocketHandle nativeClient = getNativeClient(); if (nativeClient == null) { log_trace("ping false; no native handle"); return false; } NativeResult nativeResult = WebSocketInterop.Ping(nativeClient, payload); if (nativeResult != NativeResult.Ok) { log_trace("native ping result=" + nativeResult); return false; } bool result = _pongReceived.WaitOne(PingTimeout); log_trace("ping wait complete pong=" + result); return result; } } private void pollLoop() { log_trace("pollLoop start"); try { do { NativeWebSocketHandle nativeClient = getNativeClient(); if (nativeClient == null) { log_trace("pollLoop exit; no native handle"); return; } NativeEvent nativeEvent; NativeResult nativeResult = WebSocketInterop.PollEvent(nativeClient, 50, out nativeEvent); switch (nativeResult) { case NativeResult.Ok: log_trace("native poll event kind=" + nativeEvent.Kind); handleNativeEvent(nativeEvent); break; default: log_trace("native poll failure result=" + nativeResult); handlePollFailure(nativeResult); return; case NativeResult.Timeout: break; } } while (_readyState != WebSocketState.Closed); log_trace("pollLoop exit; ready state closed"); } finally { log_trace("pollLoop finally"); lock (_forState) { _pollThreadStarted = false; _pollThread = null; } if (_readyState == WebSocketState.Closed) { destroyNativeClient(); } } } private void handlePollFailure(NativeResult result) { Exception ex = CreateCommandException(result, "The native websocket poller failed."); string message = ex.Message; log_trace("handlePollFailure result=" + result.ToString() + " message=" + message); raiseOnErrorSafely(new ErrorEventArgs(message, ex)); finalizeClosedState(new CloseEventArgs(1006, string.Empty, clean: false), raiseEvent: true); } private void send(byte[] data, bool isBinary) { ThrowIfDisposed(); ValidateSendState(); log_trace("send begin kind=" + (isBinary ? "binary" : "text") + " bytes=" + ((data != null) ? data.Length : 0) + " state=" + _readyState); lock (_forSend) { ValidateSendState(); NativeWebSocketHandle client = getNativeClient() ?? throw new ObjectDisposedException(GetType().FullName); NativeResult nativeResult = (isBinary ? WebSocketInterop.SendBinary(client, data) : WebSocketInterop.SendText(client, data)); log_trace("native send result=" + nativeResult); if (nativeResult != NativeResult.Ok) { throw CreateCommandException(nativeResult, "The message could not be sent."); } } } private string decodeString(byte[] data) { if (data == null || data.Length == 0) { return string.Empty; } return Encoding.UTF8.GetString(data); } private void startPollingLoop() { lock (_forState) { if (_pollThreadStarted || !hasNativeClient()) { log_trace("startPollingLoop skipped started=" + _pollThreadStarted + " hasClient=" + hasNativeClient()); return; } Thread thread = (_pollThread = new Thread(pollLoop) { IsBackground = true, Name = "websocket-sharp-native-poll" }); _pollThreadStarted = true; thread.Start(); log_trace("startPollingLoop started thread"); } } private void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().FullName); } } private void ValidateCloseCode(ushort code) { if (!IsCloseStatusCode(code)) { throw new ArgumentOutOfRangeException("code", "Less than 1000 or greater than 4999."); } if (code == 1011) { throw new ArgumentException("1011 cannot be used.", "code"); } } private void ValidateCloseReason(ushort code, string reason) { if (!string.IsNullOrEmpty(reason)) { if (code == 1005) { throw new ArgumentException("1005 cannot be used.", "code"); } byte[] bytes; try { bytes = Encoding.UTF8.GetBytes(reason); } catch (Exception) { throw new ArgumentException("It could not be UTF-8-encoded.", "reason"); } if (bytes.Length > 123) { throw new ArgumentOutOfRangeException("reason", "Its size is greater than 123 bytes."); } } } private void ValidateConnectStart() { if (_readyState == WebSocketState.Closing) { throw new InvalidOperationException("The close process is in progress."); } if (_readyState == WebSocketState.Connecting) { throw new InvalidOperationException("The connection is already in progress."); } } private void ValidateSendState() { if (_readyState != WebSocketState.Open) { throw new InvalidOperationException("The current state of the connection is not Open."); } if (!hasNativeClient()) { throw new ObjectDisposedException(GetType().FullName); } } } public enum WebSocketState : ushort { Connecting, Open, Closing, Closed } } namespace WebSocketSharp.Net { public class ClientSslConfiguration { private bool _checkCertRevocation; private LocalCertificateSelectionCallback _clientCertSelectionCallback; private X509CertificateCollection _clientCerts; private SslProtocols _enabledSslProtocols; private RemoteCertificateValidationCallback _serverCertValidationCallback; private string _targetHost; public bool CheckCertificateRevocation { get { return _checkCertRevocation; } set { _checkCertRevocation = value; } } public X509CertificateCollection ClientCertificates { get { return _clientCerts; } set { _clientCerts = value; } } public LocalCertificateSelectionCallback ClientCertificateSelectionCallback { get { if (_clientCertSelectionCallback == null) { _clientCertSelectionCallback = defaultSelectClientCertificate; } return _clientCertSelectionCallback; } set { _clientCertSelectionCallback = value; } } public SslProtocols EnabledSslProtocols { get { return _enabledSslProtocols; } set { _enabledSslProtocols = value; } } public RemoteCertificateValidationCallback ServerCertificateValidationCallback { get { if (_serverCertValidationCallback == null) { _serverCertValidationCallback = defaultValidateServerCertificate; } return _serverCertValidationCallback; } set { _serverCertValidationCallback = value; } } public string TargetHost { get { return _targetHost; } set { if (value == null) { throw new ArgumentNullException("value"); } if (value.Length == 0) { throw new ArgumentException("An empty string.", "value"); } _targetHost = value; } } public ClientSslConfiguration(string targetHost) { if (targetHost == null) { throw new ArgumentNullException("targetHost"); } if (targetHost.Length == 0) { throw new ArgumentException("An empty string.", "targetHost"); } _targetHost = targetHost; _enabledSslProtocols = SslProtocols.None; } public ClientSslConfiguration(ClientSslConfiguration configuration) { if (configuration == null) { throw new ArgumentNullException("configuration"); } _checkCertRevocation = configuration._checkCertRevocation; _clientCertSelectionCallback = configuration._clientCertSelectionCallback; _clientCerts = configuration._clientCerts; _enabledSslProtocols = configuration._enabledSslProtocols; _serverCertValidationCallback = configuration._serverCertValidationCallback; _targetHost = configuration._targetHost; } private static X509Certificate defaultSelectClientCertificate(object sender, string targetHost, X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string[] acceptableIssuers) { return null; } private static bool defaultValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; } } } namespace WebSocketSharp.Native { internal enum RuntimePlatform { Windows, Linux, Mac } internal enum RuntimeArchitecture { X86, X64, Arm64 } internal class NativeHelpers { internal static RuntimePlatform GetRuntimePlatform() { switch (Environment.OSVersion.Platform) { case PlatformID.Win32S: case PlatformID.Win32Windows: case PlatformID.Win32NT: case PlatformID.WinCE: return RuntimePlatform.Windows; case PlatformID.MacOSX: return RuntimePlatform.Mac; case PlatformID.Unix: return (!File.Exists("/System/Library/CoreServices/SystemVersion.plist")) ? RuntimePlatform.Linux : RuntimePlatform.Mac; default: return RuntimePlatform.Windows; } } internal static RuntimeArchitecture GetRuntimeArchitecture() { if (IntPtr.Size == 4) { return RuntimeArchitecture.X86; } string text = (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432") ?? Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") ?? string.Empty).ToUpperInvariant(); if (text.Contains("ARM64") || text.Contains("AARCH64")) { return RuntimeArchitecture.Arm64; } return RuntimeArchitecture.X64; } } internal static class NativeLibLoader { [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate NativeResult nws_client_create_delegate(byte[] urlPtr, ulong urlLen, out IntPtr client); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void nws_client_destroy_delegate(IntPtr client); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate NativeResult nws_client_abort_delegate(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate NativeResult nws_client_connect_delegate(NativeWebSocketHandle client); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate NativeResult nws_client_close_delegate(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate NativeResult nws_client_send_text_delegate(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate NativeResult nws_client_send_binary_delegate(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate NativeResult nws_client_ping_delegate(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate NativeResult nws_client_poll_event_delegate(NativeWebSocketHandle client, int timeoutMs, out NativeEventRaw nativeEvent); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void nws_event_clear_delegate(ref NativeEventRaw nativeEvent); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void nws_set_log_handler_delegate(NativeLogCallback handler); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void nws_set_log_level_delegate(int level); private sealed class NativeFunctionTable { public IntPtr ModuleHandle; public string LibraryPath; public nws_client_create_delegate Create; public nws_client_destroy_delegate Destroy; public nws_client_abort_delegate Abort; public nws_client_connect_delegate Connect; public nws_client_close_delegate Close; public nws_client_send_text_delegate SendText; public nws_client_send_binary_delegate SendBinary; public nws_client_ping_delegate Ping; public nws_client_poll_event_delegate PollEvent; public nws_event_clear_delegate ClearEvent; public nws_set_log_handler_delegate SetLogHandler; public nws_set_log_level_delegate SetLogLevel; } private static class Linux64NLib { [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_create(byte[] urlPtr, ulong urlLen, out IntPtr client); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_client_destroy(IntPtr client); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_abort(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_connect(NativeWebSocketHandle client); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_close(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_send_text(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_send_binary(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_ping(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_poll_event(NativeWebSocketHandle client, int timeoutMs, out NativeEventRaw nativeEvent); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_event_clear(ref NativeEventRaw nativeEvent); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_set_log_handler(NativeLogCallback handler); [DllImport("nativews-linux-amd64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_set_log_level(int level); } private static class LinuxArm64NLib { [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_create(byte[] urlPtr, ulong urlLen, out IntPtr client); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_client_destroy(IntPtr client); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_abort(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_connect(NativeWebSocketHandle client); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_close(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_send_text(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_send_binary(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_ping(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_poll_event(NativeWebSocketHandle client, int timeoutMs, out NativeEventRaw nativeEvent); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_event_clear(ref NativeEventRaw nativeEvent); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_set_log_handler(NativeLogCallback handler); [DllImport("nativews-linux-arm64.so", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_set_log_level(int level); } private static class MacNLib { [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_create(byte[] urlPtr, ulong urlLen, out IntPtr client); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_client_destroy(IntPtr client); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_abort(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_connect(NativeWebSocketHandle client); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_close(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_send_text(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_send_binary(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_ping(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern NativeResult nws_client_poll_event(NativeWebSocketHandle client, int timeoutMs, out NativeEventRaw nativeEvent); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_event_clear(ref NativeEventRaw nativeEvent); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_set_log_handler(NativeLogCallback handler); [DllImport("nativews-macos-universal.dylib", CallingConvention = CallingConvention.Cdecl)] internal static extern void nws_set_log_level(int level); } private const string Windows32Library = "nativews-win32.dll"; private const string Windows64Library = "nativews-win64.dll"; private const string WindowsArm64Library = "nativews-winarm64.dll"; private const string Linux64Library = "nativews-linux-amd64.so"; private const string LinuxArm64Library = "nativews-linux-arm64.so"; private const string MacLibrary = "nativews-macos-universal.dylib"; private static readonly object Sync = new object(); private static NativeFunctionTable _functions; internal static NativeResult Create(byte[] urlPtr, ulong urlLen, out IntPtr client) { return GetFunctions().Create(urlPtr, urlLen, out client); } internal static void Destroy(IntPtr client) { GetFunctions().Destroy(client); } internal static NativeResult Abort(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen) { return GetFunctions().Abort(client, code, reasonPtr, reasonLen); } internal static NativeResult Connect(NativeWebSocketHandle client) { return GetFunctions().Connect(client); } internal static NativeResult Close(NativeWebSocketHandle client, ushort code, byte[] reasonPtr, ulong reasonLen) { return GetFunctions().Close(client, code, reasonPtr, reasonLen); } internal static NativeResult SendText(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen) { return GetFunctions().SendText(client, dataPtr, dataLen); } internal static NativeResult SendBinary(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen) { return GetFunctions().SendBinary(client, dataPtr, dataLen); } internal static NativeResult Ping(NativeWebSocketHandle client, byte[] dataPtr, ulong dataLen) { return GetFunctions().Ping(client, dataPtr, dataLen); } internal static NativeResult PollEvent(NativeWebSocketHandle client, int timeoutMs, out NativeEventRaw nativeEvent) { return GetFunctions().PollEvent(client, timeoutMs, out nativeEvent); } internal static void ClearEvent(ref NativeEventRaw nativeEvent) { GetFunctions().ClearEvent(ref nativeEvent); } internal static void SetLogHandler(NativeLogCallback handler) { NativeFunctionTable functions = GetFunctions(); if (functions.SetLogHandler == null) { throw CreateMissingExportException(functions.LibraryPath, "nws_set_log_handler"); } functions.SetLogHandler(handler); } internal static void SetLogLevel(int level) { NativeFunctionTable functions = GetFunctions(); if (functions.SetLogLevel == null) { throw CreateMissingExportException(functions.LibraryPath, "nws_set_log_level"); } functions.SetLogLevel(level); } private static NativeFunctionTable GetFunctions() { NativeFunctionTable functions = _functions; if (functions != null) { return functions; } lock (Sync) { if (_functions == null) { _functions = LoadFunctions(); } return _functions; } } private static NativeFunctionTable LoadFunctions() { RuntimePlatform runtimePlatform = NativeHelpers.GetRuntimePlatform(); if (1 == 0) { } NativeFunctionTable result = runtimePlatform switch { RuntimePlatform.Windows => LoadWindowsFunctions(), RuntimePlatform.Mac => LoadMacFunctions(), _ => (NativeHelpers.GetRuntimeArchitecture() == RuntimeArchitecture.Arm64) ? LoadLinuxArm64Functions() : LoadLinux64Functions(), }; if (1 == 0) { } return result; } private static NativeFunctionTable LoadWindowsFunctions() { string text = Path.Combine(GetNativeLibraryDirectory(), GetWindowsLibraryName()); if (!File.Exists(text)) { throw new DllNotFoundException("The native websocket library could not be found at '" + text + "'."); } IntPtr intPtr = LoadLibrary(text); if (intPtr == IntPtr.Zero) { int lastWin32Error = Marshal.GetLastWin32Error(); throw new DllNotFoundException("The native websocket library could not be loaded from '" + text + "' (LoadLibrary error " + lastWin32Error + ")."); } return new NativeFunctionTable { ModuleHandle = intPtr, LibraryPath = text, Create = GetDelegate(intPtr, "nws_client_create", text), Destroy = GetDelegate(intPtr, "nws_client_destroy", text), Abort = GetDelegate(intPtr, "nws_client_abort", text), Connect = GetDelegate(intPtr, "nws_client_connect", text), Close = GetDelegate(intPtr, "nws_client_close", text), SendText = GetDelegate(intPtr, "nws_client_send_text", text), SendBinary = GetDelegate(intPtr, "nws_client_send_binary", text), Ping = GetDelegate(intPtr, "nws_client_ping", text), PollEvent = GetDelegate(intPtr, "nws_client_poll_event", text), ClearEvent = GetDelegate(intPtr, "nws_event_clear", text), SetLogHandler = GetOptionalDelegate(intPtr, "nws_set_log_handler"), SetLogLevel = GetOptionalDelegate(intPtr, "nws_set_log_level") }; } private static NativeFunctionTable LoadLinux64Functions() { NativeFunctionTable nativeFunctionTable = new NativeFunctionTable(); nativeFunctionTable.LibraryPath = "nativews-linux-amd64.so"; nativeFunctionTable.Create = Linux64NLib.nws_client_create; nativeFunctionTable.Destroy = Linux64NLib.nws_client_destroy; nativeFunctionTable.Abort = Linux64NLib.nws_client_abort; nativeFunctionTable.Connect = Linux64NLib.nws_client_connect; nativeFunctionTable.Close = Linux64NLib.nws_client_close; nativeFunctionTable.SendText = Linux64NLib.nws_client_send_text; nativeFunctionTable.SendBinary = Linux64NLib.nws_client_send_binary; nativeFunctionTable.Ping = Linux64NLib.nws_client_ping; nativeFunctionTable.PollEvent = Linux64NLib.nws_client_poll_event; nativeFunctionTable.ClearEvent = Linux64NLib.nws_event_clear; nativeFunctionTable.SetLogHandler = Linux64NLib.nws_set_log_handler; nativeFunctionTable.SetLogLevel = Linux64NLib.nws_set_log_level; return nativeFunctionTable; } private static NativeFunctionTable LoadLinuxArm64Functions() { NativeFunctionTable nativeFunctionTable = new NativeFunctionTable(); nativeFunctionTable.LibraryPath = "nativews-linux-arm64.so"; nativeFunctionTable.Create = LinuxArm64NLib.nws_client_create; nativeFunctionTable.Destroy = LinuxArm64NLib.nws_client_destroy; nativeFunctionTable.Abort = LinuxArm64NLib.nws_client_abort; nativeFunctionTable.Connect = LinuxArm64NLib.nws_client_connect; nativeFunctionTable.Close = LinuxArm64NLib.nws_client_close; nativeFunctionTable.SendText = LinuxArm64NLib.nws_client_send_text; nativeFunctionTable.SendBinary = LinuxArm64NLib.nws_client_send_binary; nativeFunctionTable.Ping = LinuxArm64NLib.nws_client_ping; nativeFunctionTable.PollEvent = LinuxArm64NLib.nws_client_poll_event; nativeFunctionTable.ClearEvent = LinuxArm64NLib.nws_event_clear; nativeFunctionTable.SetLogHandler = LinuxArm64NLib.nws_set_log_handler; nativeFunctionTable.SetLogLevel = LinuxArm64NLib.nws_set_log_level; return nativeFunctionTable; } private static NativeFunctionTable LoadMacFunctions() { NativeFunctionTable nativeFunctionTable = new NativeFunctionTable(); nativeFunctionTable.LibraryPath = "nativews-macos-universal.dylib"; nativeFunctionTable.Create = MacNLib.nws_client_create; nativeFunctionTable.Destroy = MacNLib.nws_client_destroy; nativeFunctionTable.Abort = MacNLib.nws_client_abort; nativeFunctionTable.Connect = MacNLib.nws_client_connect; nativeFunctionTable.Close = MacNLib.nws_client_close; nativeFunctionTable.SendText = MacNLib.nws_client_send_text; nativeFunctionTable.SendBinary = MacNLib.nws_client_send_binary; nativeFunctionTable.Ping = MacNLib.nws_client_ping; nativeFunctionTable.PollEvent = MacNLib.nws_client_poll_event; nativeFunctionTable.ClearEvent = MacNLib.nws_event_clear; nativeFunctionTable.SetLogHandler = MacNLib.nws_set_log_handler; nativeFunctionTable.SetLogLevel = MacNLib.nws_set_log_level; return nativeFunctionTable; } private static string GetNativeLibraryDirectory() { string location = typeof(NativeLibLoader).Assembly.Location; if (!string.IsNullOrEmpty(location)) { string directoryName = Path.GetDirectoryName(location); if (!string.IsNullOrEmpty(directoryName)) { return directoryName; } } return AppDomain.CurrentDomain.BaseDirectory ?? string.Empty; } private static string GetWindowsLibraryName() { RuntimeArchitecture runtimeArchitecture = NativeHelpers.GetRuntimeArchitecture(); if (1 == 0) { } string result = runtimeArchitecture switch { RuntimeArchitecture.X86 => "nativews-win32.dll", RuntimeArchitecture.Arm64 => "nativews-winarm64.dll", _ => "nativews-win64.dll", }; if (1 == 0) { } return result; } private static T GetDelegate(IntPtr moduleHandle, string exportName, string libraryPath) where T : class { IntPtr procAddress = GetProcAddress(moduleHandle, exportName); if (procAddress == IntPtr.Zero) { throw new EntryPointNotFoundException("The native websocket library '" + libraryPath + "' does not export '" + exportName + "'."); } return (T)(object)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(T)); } private static T GetOptionalDelegate(IntPtr moduleHandle, string exportName) where T : class { IntPtr procAddress = GetProcAddress(moduleHandle, exportName); return (procAddress == IntPtr.Zero) ? null : ((T)(object)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(T))); } private static EntryPointNotFoundException CreateMissingExportException(string libraryPath, string exportName) { return new EntryPointNotFoundException("The native websocket library '" + libraryPath + "' does not export '" + exportName + "'."); } [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); } internal enum NativeResult { Ok = 0, Timeout = 1, InvalidState = 2, InvalidArgument = 3, NotOpen = 4, Disposed = 5, InternalError = 6, Unknown = -1 } internal enum NativeErrorKind { ConnectFailed = 1, TlsFailed = 2, Io = 3, Protocol = 4, Timeout = 5, Internal = 6, Unknown = -1 } internal enum NativeEventKind { Open = 1, Close, Message, Error, Pong } internal enum NativeMessageKind { Text = 1, Binary } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void NativeLogCallback(int level, IntPtr message); internal struct NativeEvent { public NativeEventKind Kind; public NativeMessageKind MessageKind; public NativeErrorKind ErrorKind; public ushort CloseCode; public bool CloseWasClean; public byte[] Data; } internal struct NativeEventRaw { public int kind; public int message_kind; public int error_kind; public ushort close_code; public byte close_was_clean; public IntPtr data_ptr; public ulong data_len; } internal sealed class NativeWebSocketHandle : SafeHandleZeroOrMinusOneIsInvalid { public NativeWebSocketHandle() : base(ownsHandle: true) { } public NativeWebSocketHandle(IntPtr handle) : base(ownsHandle: true) { SetHandle(handle); } protected override bool ReleaseHandle() { WebSocketInterop.Destroy(handle); handle = IntPtr.Zero; return true; } } internal static class WebSocketInterop { private static NativeResult Create(byte[] url, out NativeWebSocketHandle client) { IntPtr client2; NativeResult nativeResult = NativeLibLoader.Create(url, (ulong)url.Length, out client2); client = ((nativeResult == NativeResult.Ok && client2 != IntPtr.Zero) ? new NativeWebSocketHandle(client2) : null); return nativeResult; } public static NativeResult Create(string url, out NativeWebSocketHandle client) { return Create(Encoding.UTF8.GetBytes(url), out client); } public static NativeResult Connect(NativeWebSocketHandle client) { return NativeLibLoader.Connect(client); } public static NativeResult Abort(NativeWebSocketHandle client, string reason, ushort code) { byte[] array = EncodeNullable(reason); return NativeLibLoader.Abort(client, code, array, (ulong)array.Length); } public static NativeResult Close(NativeWebSocketHandle client, string reason, ushort code) { byte[] array = EncodeNullable(reason); return NativeLibLoader.Close(client, code, array, (ulong)array.Length); } public static void Destroy(IntPtr client) { if (!(client == IntPtr.Zero)) { NativeLibLoader.Destroy(client); } } public static NativeResult SendText(NativeWebSocketHandle client, byte[] data) { return NativeLibLoader.SendText(client, data, (ulong)data.Length); } public static NativeResult SendBinary(NativeWebSocketHandle client, byte[] data) { return NativeLibLoader.SendBinary(client, data, (ulong)data.Length); } public static NativeResult Ping(NativeWebSocketHandle client, byte[] data) { return NativeLibLoader.Ping(client, data, (ulong)data.Length); } public static NativeResult PollEvent(NativeWebSocketHandle client, int timeoutMs, out NativeEvent nativeEvent) { NativeEventRaw nativeEvent2; NativeResult nativeResult = NativeLibLoader.PollEvent(client, timeoutMs, out nativeEvent2); if (nativeResult != NativeResult.Ok) { nativeEvent = default(NativeEvent); return nativeResult; } try { nativeEvent = new NativeEvent { Kind = (NativeEventKind)nativeEvent2.kind, MessageKind = (NativeMessageKind)nativeEvent2.message_kind, ErrorKind = (NativeErrorKind)nativeEvent2.error_kind, CloseCode = nativeEvent2.close_code, CloseWasClean = (nativeEvent2.close_was_clean != 0), Data = CopyBytes(nativeEvent2.data_ptr, nativeEvent2.data_len) }; } finally { ClearEvent(ref nativeEvent2); } return nativeResult; } private static void ClearEvent(ref NativeEventRaw nativeEvent) { NativeLibLoader.ClearEvent(ref nativeEvent); } public static void SetLogHandler(NativeLogCallback handler) { NativeLibLoader.SetLogHandler(handler); } public static void SetLogLevel(int level) { NativeLibLoader.SetLogLevel(level); } private static byte[] CopyBytes(IntPtr dataPtr, ulong dataLen) { if (dataPtr == IntPtr.Zero || dataLen == 0) { return new byte[0]; } if (dataLen > int.MaxValue) { throw new InvalidOperationException("Native payload is too large for managed allocation."); } byte[] array = new byte[(uint)dataLen]; Marshal.Copy(dataPtr, array, 0, array.Length); return array; } private static byte[] EncodeNullable(string text) { return string.IsNullOrEmpty(text) ? new byte[0] : Encoding.UTF8.GetBytes(text); } } }