Example #1
0
 /// <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));
 }
Example #2
0
 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));
 }
Example #3
0
 /// <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));
 }
Example #4
0
        /// <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));
        }
Example #5
0
        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);
        }
Example #6
0
        /// <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);
        }
Example #7
0
        /// <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);
            }
        }
Example #8
0
        /// <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);
            }
        }
Example #9
0
        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));
        }
Example #10
0
        /// <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);
            }
        }