private async Task StartReadAsync()
        {
            while (!_cancellationToken.IsCancellationRequested && (_pipe?.IsConnected == true))
            {
                try
                {
                    byte[] header = new byte[HEADER_SIZE];
                    await _pipe.ReadAsync(header.AsMemory(0, header.Length), _cancellationToken.Token);

                    RpcPacketType opCode     = (RpcPacketType)BitConverter.ToInt32(header.AsSpan());
                    int           dataLength = BitConverter.ToInt32(header.AsSpan(4));

                    if (dataLength == 0)//if this is zero it means the pipe closed
                    {
                        break;
                    }

                    byte[] dataBuffer = new byte[dataLength];
                    await _pipe.ReadAsync(dataBuffer.AsMemory(0, dataBuffer.Length), _cancellationToken.Token);
                    await ProcessPipeMessageAsync(opCode, Encoding.UTF8.GetString(dataBuffer));
                }
                catch (Exception exc)
                {
                    _logger.Error("Discord Pipe read error", exc);
                    throw;
                }
            }
            _logger.Information("Stopped reading from discord pipe");
        }
 private DhtRpcPacket(int transactionID, NodeContact sourceNode, RpcPacketType type, RpcQueryType queryType)
 {
     _transactionID = transactionID;
     _sourceNode    = sourceNode;
     _type          = type;
     _queryType     = queryType;
 }
        private async Task ProcessPipeMessageAsync(RpcPacketType opCode, string data)
        {
            if (opCode == RpcPacketType.PING)
            {
                SendPacket(data, RpcPacketType.PONG);
                return;
            }
            if (opCode == RpcPacketType.HANDSHAKE)
            {
                if (string.IsNullOrEmpty(data))
                {
                    //probably close?
                }
                else
                {
                    //probably restart?
                }
                //SendPacket(new { v = RPC_VERSION, client_id = clientId.Value }, RpcPacketType.HANDSHAKE);
                //happens when closing discord and artemis is open?
                //TODO: investigate
            }
            if (opCode == RpcPacketType.CLOSE)
            {
                _logger.Error("Discord pipe connection closed: {data}", data);
                return;
            }

            IDiscordMessage discordMessage;

            try
            {
                discordMessage = JsonConvert.DeserializeObject <IDiscordMessage>(data, _jsonSerializerSettings);
            }
            catch (Exception exc)
            {
                _logger.Error(exc, "Error deserializing discord message: {data}", data);
                return;
            }

            _logger.Verbose("Received discord message: {data}", data);

            if (discordMessage is DiscordResponse discordResponse)
            {
                await ProcessDiscordResponseAsync(discordResponse);
            }
            else if (discordMessage is DiscordEvent discordEvent)
            {
                await ProcessDiscordEventAsync(discordEvent);
            }
            else
            {
                _logger.Error("Received unexpected discord message: {data}", data);
            }
        }
        private void SendPacket(object obj, RpcPacketType opcode = RpcPacketType.FRAME)
        {
            string stringData = JsonConvert.SerializeObject(obj, _jsonSerializerSettings);

            byte[] data       = Encoding.UTF8.GetBytes(stringData);
            int    dataLength = data.Length;

            byte[]       sendBuff = new byte[dataLength + HEADER_SIZE];
            BinaryWriter writer   = new BinaryWriter(new MemoryStream(sendBuff));

            writer.Write((int)opcode);
            writer.Write(dataLength);
            writer.Write(data);
            _pipe.Write(sendBuff);

            _logger.Verbose("Sent discord message: {stringData}", stringData);
        }
        public DhtRpcPacket(Stream s, IPAddress nodeIP)
        {
            int version = s.ReadByte();

            switch (version)
            {
            case 1:
                byte[] buffer = new byte[20];

                OffsetStream.StreamRead(s, buffer, 0, 4);
                _transactionID = BitConverter.ToInt32(buffer, 0);

                OffsetStream.StreamRead(s, buffer, 0, 20);
                BinaryID nodeID = BinaryID.Clone(buffer, 0, 20);

                OffsetStream.StreamRead(s, buffer, 0, 2);
                _sourceNode = new NodeContact(nodeID, new IPEndPoint(nodeIP, BitConverter.ToUInt16(buffer, 0)));

                _type      = (RpcPacketType)s.ReadByte();
                _queryType = (RpcQueryType)s.ReadByte();

                switch (_queryType)
                {
                case RpcQueryType.FIND_NODE:
                    OffsetStream.StreamRead(s, buffer, 0, 20);
                    _networkID = BinaryID.Clone(buffer, 0, 20);

                    if (_type == RpcPacketType.Response)
                    {
                        int count = s.ReadByte();
                        _contacts = new NodeContact[count];

                        for (int i = 0; i < count; i++)
                        {
                            _contacts[i] = new NodeContact(s);
                        }
                    }
                    break;

                case RpcQueryType.FIND_PEERS:
                    OffsetStream.StreamRead(s, buffer, 0, 20);
                    _networkID = BinaryID.Clone(buffer, 0, 20);

                    if (_type == RpcPacketType.Response)
                    {
                        int count = s.ReadByte();
                        _contacts = new NodeContact[count];

                        for (int i = 0; i < count; i++)
                        {
                            _contacts[i] = new NodeContact(s);
                        }

                        count  = s.ReadByte();
                        _peers = new PeerEndPoint[count];

                        for (int i = 0; i < count; i++)
                        {
                            _peers[i] = new PeerEndPoint(s);
                        }

                        OffsetStream.StreamRead(s, buffer, 0, 20);
                        _token = new BinaryID(buffer);
                    }
                    break;

                case RpcQueryType.ANNOUNCE_PEER:
                    OffsetStream.StreamRead(s, buffer, 0, 20);
                    _networkID = BinaryID.Clone(buffer, 0, 20);

                    if (_type == RpcPacketType.Query)
                    {
                        OffsetStream.StreamRead(s, buffer, 0, 2);
                        _servicePort = BitConverter.ToUInt16(buffer, 0);

                        OffsetStream.StreamRead(s, buffer, 0, 20);
                        _token = new BinaryID(buffer);
                    }
                    break;
                }

                break;

            default:
                throw new IOException("DHT-RPC packet version not supported: " + version);
            }
        }