private async Task <ReceiveMessageResult> receiveMessageAsync(ProtocolType protocol, int timeout, CancellationToken cancellationToken)
        {
            ReceiveMessageResult result = new ReceiveMessageResult
            {
                Bytes   = 0,
                Packets = 0,
                Message = new Memory <byte>(),
            };

            try
            {
                List <byte> receivedData   = new List <byte>();
                DateTime    startTimestamp = DateTime.UtcNow;

                while (DateTime.UtcNow.Subtract(startTimestamp).TotalMilliseconds < timeout && receivedData.Count < TCPHeaderLength)
                {
                    Memory <byte> buffer         = new byte[1500];
                    TimeSpan      receiveTimeout = TimeSpan.FromMilliseconds(timeout).Subtract(DateTime.UtcNow.Subtract(startTimestamp));

                    if (receiveTimeout.TotalMilliseconds >= 50)
                    {
                        int receivedBytes = await _client.ReceiveAsync(buffer, receiveTimeout, cancellationToken);

                        if (receivedBytes > 0)
                        {
                            receivedData.AddRange(buffer.Slice(0, receivedBytes).ToArray());

                            result.Bytes   += receivedBytes;
                            result.Packets += 1;
                        }
                    }
                }

                if (receivedData.Count == 0)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC '" + RemoteHost + ":" + Port + "' - No Data was Received");
                }

                if (receivedData.Count < TCPHeaderLength)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message within the Timeout Period from Unitronics PLC '" + RemoteHost + ":" + Port + "'");
                }

                if (receivedData[3] != 0)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC  '" + RemoteHost + ":" + Port + "' - The TCP Header was Invalid");
                }

                if (BitConverter.ToUInt16(receivedData.GetRange(0, 2).ToArray(), 0) != _requestId)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC '" + RemoteHost + ":" + Port + "' - The TCP Header Transaction ID did not Match");
                }

                if (receivedData[2] != (byte)protocol)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC  '" + RemoteHost + ":" + Port + "' - The TCP Header Protocol Type did not Match");
                }

                int tcpMessageDataLength = BitConverter.ToUInt16(new byte[] { receivedData[4], receivedData[5] });

                if (tcpMessageDataLength <= 0 || tcpMessageDataLength > 1009)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC  '" + RemoteHost + ":" + Port + "' - The TCP Message Length was Invalid");
                }

                receivedData.RemoveRange(0, TCPHeaderLength);

                if (receivedData.Count < tcpMessageDataLength - TCPHeaderLength)
                {
                    startTimestamp = DateTime.UtcNow;

                    while (DateTime.UtcNow.Subtract(startTimestamp).TotalMilliseconds < timeout && receivedData.Count < tcpMessageDataLength)
                    {
                        Memory <byte> buffer         = new byte[1024];
                        TimeSpan      receiveTimeout = TimeSpan.FromMilliseconds(timeout).Subtract(DateTime.UtcNow.Subtract(startTimestamp));

                        if (receiveTimeout.TotalMilliseconds >= 50)
                        {
                            int receivedBytes = await _client.ReceiveAsync(buffer, receiveTimeout, cancellationToken);

                            if (receivedBytes > 0)
                            {
                                receivedData.AddRange(buffer.Slice(0, receivedBytes).ToArray());
                            }

                            result.Bytes   += receivedBytes;
                            result.Packets += 1;
                        }
                    }
                }

                if (receivedData.Count == 0)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC  '" + RemoteHost + ":" + Port + "' - No Data was Received after TCP Header");
                }

                if (receivedData.Count < tcpMessageDataLength - TCPHeaderLength)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message within the Timeout Period from Unitronics PLC  '" + RemoteHost + ":" + Port + "'");
                }

                result.Message = receivedData.ToArray();
            }
            catch (ObjectDisposedException)
            {
                throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC '" + RemoteHost + ":" + Port + "' - The underlying Socket Connection has been Closed");
            }
            catch (TimeoutException)
            {
                throw new UnitronicsException("Failed to Receive " + protocol + " Message within the Timeout Period from Unitronics PLC  '" + RemoteHost + ":" + Port + "'");
            }
            catch (System.Net.Sockets.SocketException e)
            {
                throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC  '" + RemoteHost + ":" + Port + "'", e);
            }

            return(result);
        }
        public async Task <ProcessMessageResult> ProcessMessageAsync(ReadOnlyMemory <byte> requestMessage, ProtocolType protocol, byte unitId, int timeout, int retries, CancellationToken cancellationToken)
        {
            int           attempts        = 0;
            Memory <byte> responseMessage = new Memory <byte>();
            int           bytesSent       = 0;
            int           packetsSent     = 0;
            int           bytesReceived   = 0;
            int           packetsReceived = 0;
            DateTime      startTimestamp  = DateTime.UtcNow;

            while (attempts <= retries)
            {
                try
                {
                    if (!_semaphore.Wait(0))
                    {
                        await _semaphore.WaitAsync(cancellationToken);
                    }

                    if (attempts > 0)
                    {
                        await destroyAndInitializeClient(timeout, cancellationToken);
                    }

                    // Send the Message
                    SendMessageResult sendResult = await sendMessageAsync(requestMessage, protocol, timeout, cancellationToken);

                    bytesSent   += sendResult.Bytes;
                    packetsSent += sendResult.Packets;

                    // Receive a Response
                    ReceiveMessageResult receiveResult = await receiveMessageAsync(protocol, timeout, cancellationToken);

                    bytesReceived   += receiveResult.Bytes;
                    packetsReceived += receiveResult.Packets;
                    responseMessage  = receiveResult.Message;

                    break;
                }
                catch (Exception)
                {
                    if (attempts >= retries)
                    {
                        throw;
                    }
                }
                finally
                {
                    _semaphore.Release();
                }

                // Increment the Attempts
                attempts++;
            }

            return(new ProcessMessageResult
            {
                BytesSent = bytesSent,
                PacketsSent = packetsSent,
                BytesReceived = bytesReceived,
                PacketsReceived = packetsReceived,
                Duration = DateTime.UtcNow.Subtract(startTimestamp).TotalMilliseconds,
                ResponseMessage = responseMessage,
            });
        }
        private async Task <ReceiveMessageResult> receiveMessageAsync(ProtocolType protocol, byte unitId, int timeout, CancellationToken cancellationToken)
        {
            ReceiveMessageResult result = new ReceiveMessageResult
            {
                Bytes   = 0,
                Packets = 0,
                Message = new Memory <byte>(),
            };

            try
            {
                List <byte> receivedData   = new List <byte>();
                DateTime    startTimestamp = DateTime.UtcNow;

                bool receiveCompleted = false;

                while (DateTime.UtcNow.Subtract(startTimestamp).TotalMilliseconds < timeout && receiveCompleted == false)
                {
                    Memory <byte> buffer         = new byte[1500];
                    TimeSpan      receiveTimeout = TimeSpan.FromMilliseconds(timeout).Subtract(DateTime.UtcNow.Subtract(startTimestamp));

                    if (receiveTimeout.TotalMilliseconds >= 50)
                    {
                        int receivedBytes = await _client.ReceiveAsync(buffer, receiveTimeout, cancellationToken);

                        if (receivedBytes > 0)
                        {
                            receivedData.AddRange(buffer.Slice(0, receivedBytes).ToArray());

                            result.Bytes   += receivedBytes;
                            result.Packets += 1;
                        }
                    }

                    receiveCompleted = isReceiveCompleted(protocol, receivedData);
                }

                if (receivedData.Count == 0)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC ID '" + unitId + "' on '" + RemoteHost + ":" + Port + "' - No Data was Received");
                }

                if (receiveCompleted == false)
                {
                    throw new UnitronicsException("Failed to Receive " + protocol + " Message within the Timeout Period from Unitronics PLC ID '" + unitId + "' on '" + RemoteHost + ":" + Port + "'");
                }

                result.Message = trimReceivedData(protocol, receivedData);
            }
            catch (ObjectDisposedException)
            {
                throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC ID '" + unitId + "' on '" + RemoteHost + ":" + Port + "' - The underlying Socket Connection has been Closed");
            }
            catch (TimeoutException)
            {
                throw new UnitronicsException("Failed to Receive " + protocol + " Message within the Timeout Period from Unitronics PLC ID '" + unitId + "' on '" + RemoteHost + ":" + Port + "'");
            }
            catch (System.Net.Sockets.SocketException e)
            {
                throw new UnitronicsException("Failed to Receive " + protocol + " Message from Unitronics PLC ID '" + unitId + "' on '" + RemoteHost + ":" + Port + "'", e);
            }

            return(result);
        }