/// <summary> /// Send data and metadata to the server. /// </summary> /// <param name="data">Byte array containing data.</param> /// <param name="metadata">Dictionary containing metadata.</param> /// <returns>Boolean indicating if the message was sent successfully.</returns> public bool Send(byte[] data, Dictionary <object, object> metadata = null) { if (data == null) { data = new byte[0]; } WatsonCommon.BytesToStream(data, out long contentLength, out Stream stream); return(Send(contentLength, stream, metadata)); }
internal static byte[] ReadMessageDataAsync(WatsonMessage msg, int bufferLen) { if (msg == null) { throw new ArgumentNullException(nameof(msg)); } if (msg.ContentLength == 0) { return(new byte[0]); } return(WatsonCommon.ReadFromStreamAsync(msg.DataStream, msg.ContentLength, bufferLen)); }
/// <summary> /// Send data and metadata to the server asynchronously. /// </summary> /// <param name="data">Byte array containing data.</param> /// <param name="metadata">Dictionary containing metadata.</param> /// <param name="token">Cancellation token to cancel the request.</param> /// <returns>Task with Boolean indicating if the message was sent successfully.</returns> public async Task <bool> SendAsync(byte[] data, Dictionary <object, object> metadata = null, CancellationToken token = default) { if (token == default(CancellationToken)) { token = _Token; } if (data == null) { data = new byte[0]; } WatsonCommon.BytesToStream(data, out long contentLength, out Stream stream); return(await SendAsync(contentLength, stream, metadata, token).ConfigureAwait(false)); }
/// <summary> /// Send data and wait for a response for the specified number of milliseconds. A TimeoutException will be thrown if a response is not received. /// </summary> /// <param name="timeoutMs">Number of milliseconds to wait before considering a request to be expired.</param> /// <param name="data">Data to send.</param> /// <param name="metadata">Metadata dictionary to attach to the message.</param> /// <returns>SyncResponse.</returns> public SyncResponse SendAndWait(int timeoutMs, byte[] data, Dictionary <object, object> metadata = null) { if (timeoutMs < 1000) { throw new ArgumentException("Timeout milliseconds must be 1000 or greater."); } if (data == null) { data = new byte[0]; } DateTime expiration = DateTime.Now.AddMilliseconds(timeoutMs); WatsonCommon.BytesToStream(data, out long contentLength, out Stream stream); return(SendAndWait(timeoutMs, contentLength, stream, metadata)); }
internal static async Task <byte[]> ReadMessageDataAsync(WatsonMessage msg, int bufferLen) { if (msg == null) { throw new ArgumentNullException(nameof(msg)); } if (msg.ContentLength == 0) { return(new byte[0]); } byte[] msgData = null; MemoryStream ms = new MemoryStream(); if (msg.Compression == CompressionType.None) { msgData = await WatsonCommon.ReadFromStreamAsync(msg.DataStream, msg.ContentLength, bufferLen); } else if (msg.Compression == CompressionType.Deflate) { using (DeflateStream ds = new DeflateStream(msg.DataStream, CompressionMode.Decompress, true)) { msgData = WatsonCommon.ReadStreamFully(ds); } } else if (msg.Compression == CompressionType.Gzip) { using (GZipStream gs = new GZipStream(msg.DataStream, CompressionMode.Decompress, true)) { msgData = WatsonCommon.ReadStreamFully(gs); } } else { throw new InvalidOperationException("Unknown compression type: " + msg.Compression.ToString()); } return(msgData); }
/// <summary> /// Human-readable string version of the object. /// </summary> /// <returns>String.</returns> public override string ToString() { string ret = "---" + Environment.NewLine; ret += " Preshared key : " + (PresharedKey != null ? WatsonCommon.ByteArrayToHex(PresharedKey) : "null") + Environment.NewLine; ret += " Status : " + Status.ToString() + Environment.NewLine; ret += " SyncRequest : " + SyncRequest.ToString() + Environment.NewLine; ret += " SyncResponse : " + SyncResponse.ToString() + Environment.NewLine; ret += " ExpirationUtc : " + (Expiration != null ? Expiration.Value.ToString(_DateTimeFormat) : "null") + Environment.NewLine; ret += " Conversation : " + ConversationGuid + Environment.NewLine; if (Metadata != null) { ret += " Metadata : " + Metadata.Count + " entries" + Environment.NewLine; } if (DataStream != null) { ret += " DataStream : present, " + ContentLength + " bytes" + Environment.NewLine; } return(ret); }
/// <summary> /// Build the Message object from data that awaits in a NetworkStream or SslStream. /// </summary> /// <returns>True if successful.</returns> internal async Task <bool> BuildFromStream(CancellationToken token) { // {"len":0,"s":"Normal"}\r\n\r\n byte[] headerBytes = new byte[24]; try { #region Read-Headers await _DataStream.ReadAsync(headerBytes, 0, 24, token).ConfigureAwait(false); byte[] headerBuffer = new byte[1]; while (true) { byte[] endCheck = headerBytes.Skip(headerBytes.Length - 4).Take(4).ToArray(); if ((int)endCheck[3] == 0 && (int)endCheck[2] == 0 && (int)endCheck[1] == 0 && (int)endCheck[0] == 0) { _Logger?.Invoke(_Header + "null header data, peer disconnect detected"); return(false); } if ((int)endCheck[3] == 10 && (int)endCheck[2] == 13 && (int)endCheck[1] == 10 && (int)endCheck[0] == 13) { _Logger?.Invoke(_Header + "found header demarcation"); break; } await _DataStream.ReadAsync(headerBuffer, 0, 1, token).ConfigureAwait(false); headerBytes = WatsonCommon.AppendBytes(headerBytes, headerBuffer); } WatsonMessage msg = SerializationHelper.DeserializeJson <WatsonMessage>(Encoding.UTF8.GetString(headerBytes)); ContentLength = msg.ContentLength; PresharedKey = msg.PresharedKey; Status = msg.Status; Metadata = msg.Metadata; SyncRequest = msg.SyncRequest; SyncResponse = msg.SyncResponse; SenderTimestamp = msg.SenderTimestamp; Expiration = msg.Expiration; ConversationGuid = msg.ConversationGuid; _Logger?.Invoke(_Header + "header processing complete" + Environment.NewLine + Encoding.UTF8.GetString(headerBytes).Trim()); #endregion return(true); } catch (TaskCanceledException) { _Logger?.Invoke(_Header + "message read canceled"); return(false); } catch (OperationCanceledException) { _Logger?.Invoke(_Header + "message read canceled"); return(false); } catch (ObjectDisposedException) { _Logger?.Invoke(_Header + "socket disposed"); return(false); } catch (IOException) { _Logger?.Invoke(_Header + "non-graceful termination by peer"); return(false); } catch (Exception e) { _Logger?.Invoke(_Header + "exception encountered: " + Environment.NewLine + "Header bytes: " + BitConverter.ToString(headerBytes).Replace("-", String.Empty) + Environment.NewLine + "Exception: " + SerializationHelper.SerializeJson(e, true) + Environment.NewLine); return(false); } }
/// <summary> /// Build the Message object from data that awaits in a NetworkStream or SslStream. /// </summary> /// <returns>True if successful.</returns> internal async Task <bool> BuildFromStream() { try { #region Read-Headers byte[] buffer = new byte[0]; while (true) { byte[] data = await WatsonCommon.ReadFromStreamAsync(_DataStream, 1, _ReadStreamBuffer); if (data != null && data.Length == 1) { buffer = WatsonCommon.AppendBytes(buffer, data); if (buffer.Length >= 4) { byte[] endCheck = buffer.Skip(buffer.Length - 4).Take(4).ToArray(); if ((int)endCheck[3] == 10 && (int)endCheck[2] == 13 && (int)endCheck[1] == 10 && (int)endCheck[0] == 13) { _Logger?.Invoke(_Header + "ReadHeaders found header demarcation"); break; } } } } WatsonMessage msg = SerializationHelper.DeserializeJson <WatsonMessage>(Encoding.UTF8.GetString(buffer)); ContentLength = msg.ContentLength; PresharedKey = msg.PresharedKey; Status = msg.Status; Metadata = msg.Metadata; SyncRequest = msg.SyncRequest; SyncResponse = msg.SyncResponse; SenderTimestamp = msg.SenderTimestamp; Expiration = msg.Expiration; ConversationGuid = msg.ConversationGuid; Compression = msg.Compression; _Logger?.Invoke(_Header + "BuildFromStream header processing complete" + Environment.NewLine + Encoding.UTF8.GetString(buffer).Trim()); #endregion return(true); } catch (IOException) { _Logger?.Invoke(_Header + "BuildStream IOexception, disconnect assumed"); return(false); } catch (SocketException) { _Logger?.Invoke(_Header + "BuildStream SocketException, disconnect assumed"); return(false); } catch (ObjectDisposedException) { _Logger?.Invoke(_Header + "BuildStream ObjectDisposedException, disconnect assumed"); return(false); } catch (Exception e) { _Logger?.Invoke(_Header + "BuildStream exception: " + Environment.NewLine + SerializationHelper.SerializeJson(e, true) + Environment.NewLine); return(false); } }
private async Task DataReceiver() { DisconnectReason reason = DisconnectReason.Normal; while (true) { bool readLocked = false; try { #region Check-for-Connection if (_Client == null || !_Client.Connected) { _Settings.Logger?.Invoke(_Header + "disconnect detected"); break; } #endregion #region Read-Message while (true) { readLocked = await _ReadLock.WaitAsync(10, _Token).ConfigureAwait(false); if (readLocked) { break; } await Task.Delay(10, _Token).ConfigureAwait(false); } WatsonMessage msg = new WatsonMessage(_DataStream, (_Settings.DebugMessages ? _Settings.Logger : null)); bool buildSuccess = await msg.BuildFromStream(_Token).ConfigureAwait(false); if (!buildSuccess) { _Settings.Logger?.Invoke(_Header + "disconnect detected"); break; } if (msg == null) { await Task.Delay(30, _Token).ConfigureAwait(false); continue; } #endregion #region Process-by-Status if (msg.Status == MessageStatus.Removed) { _Settings.Logger?.Invoke(_Header + "disconnect due to server-side removal"); reason = DisconnectReason.Removed; break; } else if (msg.Status == MessageStatus.Shutdown) { _Settings.Logger?.Invoke(_Header + "disconnect due to server shutdown"); reason = DisconnectReason.Shutdown; break; } else if (msg.Status == MessageStatus.Timeout) { _Settings.Logger?.Invoke(_Header + "disconnect due to timeout"); reason = DisconnectReason.Timeout; break; } else if (msg.Status == MessageStatus.AuthSuccess) { _Settings.Logger?.Invoke(_Header + "authentication successful"); Task unawaited = Task.Run(() => _Events.HandleAuthenticationSucceeded(this, EventArgs.Empty), _Token); continue; } else if (msg.Status == MessageStatus.AuthFailure) { _Settings.Logger?.Invoke(_Header + "authentication failed"); Task unawaited = Task.Run(() => _Events.HandleAuthenticationFailure(this, EventArgs.Empty), _Token); continue; } else if (msg.Status == MessageStatus.AuthRequired) { _Settings.Logger?.Invoke(_Header + "authentication required by server; please authenticate using pre-shared key"); string psk = _Callbacks.HandleAuthenticationRequested(); if (!String.IsNullOrEmpty(psk)) { Authenticate(psk); } continue; } #endregion #region Process-Message if (msg.SyncRequest != null && msg.SyncRequest.Value) { DateTime expiration = WatsonCommon.GetExpirationTimestamp(msg); byte[] msgData = await WatsonCommon.ReadMessageDataAsync(msg, _Settings.StreamBufferSize).ConfigureAwait(false); if (DateTime.Now < expiration) { SyncRequest syncReq = new SyncRequest( _ServerIp + ":" + _ServerPort, msg.ConversationGuid, msg.Expiration.Value, msg.Metadata, msgData); SyncResponse syncResp = _Callbacks.HandleSyncRequestReceived(syncReq); if (syncResp != null) { WatsonCommon.BytesToStream(syncResp.Data, out long contentLength, out Stream stream); WatsonMessage respMsg = new WatsonMessage( syncResp.Metadata, contentLength, stream, false, true, msg.Expiration.Value, msg.ConversationGuid, (_Settings.DebugMessages ? _Settings.Logger : null)); SendInternal(respMsg, contentLength, stream); } } else { _Settings.Logger?.Invoke(_Header + "expired synchronous request received and discarded"); } } else if (msg.SyncResponse != null && msg.SyncResponse.Value) { // No need to amend message expiration; it is copied from the request, which was set by this node // DateTime expiration = WatsonCommon.GetExpirationTimestamp(msg); byte[] msgData = await WatsonCommon.ReadMessageDataAsync(msg, _Settings.StreamBufferSize).ConfigureAwait(false); if (DateTime.Now < msg.Expiration.Value) { lock (_SyncResponseLock) { _SyncResponses.Add(msg.ConversationGuid, new SyncResponse(msg.Expiration.Value, msg.Metadata, msgData)); } } else { _Settings.Logger?.Invoke(_Header + "expired synchronous response received and discarded"); } } else { byte[] msgData = null; if (_Events.IsUsingMessages) { msgData = await WatsonCommon.ReadMessageDataAsync(msg, _Settings.StreamBufferSize).ConfigureAwait(false); MessageReceivedEventArgs args = new MessageReceivedEventArgs((_ServerIp + ":" + _ServerPort), msg.Metadata, msgData); await Task.Run(() => _Events.HandleMessageReceived(this, args)); } else if (_Events.IsUsingStreams) { StreamReceivedEventArgs sr = null; WatsonStream ws = null; if (msg.ContentLength >= _Settings.MaxProxiedStreamSize) { ws = new WatsonStream(msg.ContentLength, msg.DataStream); sr = new StreamReceivedEventArgs((_ServerIp + ":" + _ServerPort), msg.Metadata, msg.ContentLength, ws); // sr = new StreamReceivedFromServerEventArgs(msg.Metadata, msg.ContentLength, msg.DataStream); // must run synchronously, data exists in the underlying stream _Events.HandleStreamReceived(this, sr); } else { MemoryStream ms = WatsonCommon.DataStreamToMemoryStream(msg.ContentLength, msg.DataStream, _Settings.StreamBufferSize); ws = new WatsonStream(msg.ContentLength, ms); sr = new StreamReceivedEventArgs((_ServerIp + ":" + _ServerPort), msg.Metadata, msg.ContentLength, ws); // sr = new StreamReceivedFromServerEventArgs(msg.Metadata, msg.ContentLength, ms); // data has been read, can continue to next message Task unawaited = Task.Run(() => _Events.HandleStreamReceived(this, sr), _Token); } } else { _Settings.Logger?.Invoke(_Header + "event handler not set for either MessageReceived or StreamReceived"); break; } } #endregion _Statistics.IncrementReceivedMessages(); _Statistics.AddReceivedBytes(msg.ContentLength); } catch (ObjectDisposedException) { break; } catch (TaskCanceledException) { break; } catch (OperationCanceledException) { break; } catch (Exception e) { _Settings.Logger?.Invoke( _Header + "data receiver exception for " + _ServerIp + ":" + _ServerPort + ":" + Environment.NewLine + SerializationHelper.SerializeJson(e, true) + Environment.NewLine); _Events.HandleExceptionEncountered(this, new ExceptionEventArgs(e)); break; } finally { if (readLocked && _ReadLock != null) { _ReadLock.Release(); } } } Connected = false; _Settings.Logger?.Invoke(_Header + "data receiver terminated for " + _ServerIp + ":" + _ServerPort); _Events.HandleServerDisconnected(this, new DisconnectionEventArgs((_ServerIp + ":" + _ServerPort), reason)); }
/// <summary> /// Build the Message object from data that awaits in a NetworkStream or SslStream. /// </summary> /// <returns>True if successful.</returns> internal async Task <bool> BuildFromStream() { try { #region Read-Headers // {"len":0,"s":"Normal"}\r\n\r\n byte[] headerBytes = new byte[24]; await _DataStream.ReadAsync(headerBytes, 0, 24); byte[] headerBuffer = new byte[1]; while (true) { byte[] endCheck = headerBytes.Skip(headerBytes.Length - 4).Take(4).ToArray(); if ((int)endCheck[3] == 10 && (int)endCheck[2] == 13 && (int)endCheck[1] == 10 && (int)endCheck[0] == 13) { _Logger?.Invoke(_Header + "found header demarcation"); break; } await _DataStream.ReadAsync(headerBuffer, 0, 1); headerBytes = WatsonCommon.AppendBytes(headerBytes, headerBuffer); } WatsonMessage msg = SerializationHelper.DeserializeJson <WatsonMessage>(Encoding.UTF8.GetString(headerBytes)); ContentLength = msg.ContentLength; PresharedKey = msg.PresharedKey; Status = msg.Status; Metadata = msg.Metadata; SyncRequest = msg.SyncRequest; SyncResponse = msg.SyncResponse; SenderTimestamp = msg.SenderTimestamp; Expiration = msg.Expiration; ConversationGuid = msg.ConversationGuid; _Logger?.Invoke(_Header + "header processing complete" + Environment.NewLine + Encoding.UTF8.GetString(headerBytes).Trim()); #endregion return(true); } catch (IOException) { _Logger?.Invoke(_Header + "IOexception, disconnect detected"); return(false); } catch (SocketException) { _Logger?.Invoke(_Header + "SocketException, disconnect detected"); return(false); } catch (ObjectDisposedException) { _Logger?.Invoke(_Header + "ObjectDisposedException, disconnect detected"); return(false); } catch (Exception e) { _Logger?.Invoke(_Header + "exception: " + Environment.NewLine + SerializationHelper.SerializeJson(e, true) + Environment.NewLine); return(false); } }