protected int ClearMessage(UsbHidDriver mDevice, int timeout)
        {
            int  count   = 0;
            bool cleared = false;

            do
            {
                try
                {
                    ReadMessage(mDevice, timeout);
                    count++;
                    MedtronicCnlService.cnlClear++;
                }
                catch (TimeoutException e)
                {
                    cleared = true;
                }
            } while (!cleared);

            if (count > 0)
            {
                Log.d(TAG, "clearMessage: message stream cleared " + count + " messages.");
            }

            return(count);
        }
        // safety check to make sure an expected 0x81 response is received before next expected 0x80 response
        // very infrequent as clearMessage catches most issues but very important to save a CNL error situation
        protected async Task <byte[]> ReadMessage_0x81(UsbHidDriver mDevice, int timeout = READ_TIMEOUT_MS)
        {
            byte[] responseBytes;
            bool   doRetry;
            var    expected = (byte)0x81;

            do
            {
                responseBytes = await ReadMessage(mDevice, timeout);

                var actual = responseBytes[18];
                if (actual != expected)
                {
                    doRetry = true;
                    Log.d(TAG, "readMessage0x81: did not get 0x81 response, got " + HexDump.ToHexstring(actual));
                    MedtronicCnlService.cnl0x81++;
                }
                else
                {
                    doRetry = false;
                    break;
                }
            } while (doRetry);

            return(responseBytes);
        }
        protected async Task <byte[]> SendToPump(UsbHidDriver mDevice, MedtronicCnlSession pumpSession, string tag)
        {
            tag = " (" + tag + ")";
            byte[] payload;
            byte   medtronicSequenceNumber = pumpSession.getMedtronicSequenceNumber();

            // extra safety check and delay, CNL is not happy when we miss incoming messages
            // the short delay may also help with state readiness
            ClearMessage(mDevice, PRESEND_CLEAR_TIMEOUT_MS);

            SendMessage(mDevice);

            try
            {
                payload = await ReadMessage_0x81(mDevice);
            }
            catch (TimeoutException e)
            {
                // ugh... there should always be a CNL 0x81 response and if we don't get one it usually ends with a E86 / E81 error on the CNL needing a unplug/plug cycle
                Log.e(TAG, "Timeout waiting for 0x81 response." + tag);
                ClearMessage(mDevice, ERROR_CLEAR_TIMEOUT_MS);
                throw new TimeoutException("Timeout waiting for 0x81 response" + tag);
            }

            Log.d(TAG, "0x81 response: payload.Length=" + payload.Length + (payload.Length >= 0x30 ? " payload[0x21]=" + payload[0x21] + " payload[0x2C]=" + payload[0x2C] + " medtronicSequenceNumber=" + medtronicSequenceNumber + " payload[0x2D]=" + payload[0x2D] : "") + tag);

            // following errors usually have no further response from the CNL but occasionally they do
            // and these need to be read and cleared asap or yep E86 me baby and a unresponsive CNL
            // the extra delay from the clearMessage timeout may be helping here too by holding back any further downstream sends etc - investigate
            if (payload.Length <= 0x21)
            {
                ClearMessage(mDevice, ERROR_CLEAR_TIMEOUT_MS);
                throw new UnexpectedMessageException("0x81 response was empty" + tag);  // *bad* CNL death soon after this, may want to end comms immediately
            }
            else if (payload.Length != 0x30 && payload[0x21] != 0x55)
            {
                ClearMessage(mDevice, ERROR_CLEAR_TIMEOUT_MS);
                throw new UnexpectedMessageException("0x81 response was not a 0x55 message" + tag);
            }
            else if (payload[0x2C] != medtronicSequenceNumber)
            {
                ClearMessage(mDevice, ERROR_CLEAR_TIMEOUT_MS);
                throw new UnexpectedMessageException("0x81 sequence number does not match" + tag);
            }
            else if (payload[0x2D] == 0x04)
            {
                ClearMessage(mDevice, ERROR_CLEAR_TIMEOUT_MS);
                throw new UnexpectedMessageException("0x81 connection busy" + tag);
            }
            else if (payload[0x2D] != 0x02)
            {
                ClearMessage(mDevice, ERROR_CLEAR_TIMEOUT_MS);
                throw new UnexpectedMessageException("0x81 connection lost" + tag);
            }

            return(payload);
        }
        protected async Task <byte[]> ReadMessage(UsbHidDriver mDevice, int timeout = READ_TIMEOUT_MS)
        {
            ByteArrayOutputStream responseMessage = new ByteArrayOutputStream();

            int messageSize = 0;
            int bytesRead;

            do
            {
                var rawBytes = await mDevice.Read(timeout);

                bytesRead = rawBytes?.Length ?? -1;

                if (bytesRead == -1)
                {
                    throw new TimeoutException("Timeout waiting for response from pump");
                }
                else if (bytesRead > 0)
                {
                    // Validate the header
                    var responseBuffer = new MedtronicMessageBuffer(bytesRead);
                    ValidateHeader(responseBuffer);
                    messageSize = responseBuffer.MessageLength;
                    responseMessage.write(responseBuffer, 4, messageSize);
                }
                else
                {
                    Log.w(TAG, "readMessage: got a zero-sized response.");
                }
            } while (bytesRead > 0 && messageSize == USB_MAX_MESSAGE_SIZE);

            string responsestring = HexDump.DumpHexstring(responseMessage.toByteArray());

            Log.d(TAG, "READ: " + responsestring);

            return(responseMessage.toByteArray());
        }
        protected void SendMessage(UsbHidDriver mDevice)
        {
            int pos     = 0;
            var message = Encode();

            // chop into pieces and send each individual piece
            while (message.Length > pos)
            {
                var outputBuffer = new MedtronicMessageBuffer(USB_BLOCKSIZE);
                int sendLength   = (pos + USB_MAX_MESSAGE_SIZE > message.Length)
                    ? message.Length - pos
                    : USB_MAX_MESSAGE_SIZE;
                outputBuffer.Put(HeaderBytes);
                outputBuffer.Put((byte)sendLength);
                outputBuffer.Put(message, pos, sendLength);

                // separate the send concern
                mDevice.Write(outputBuffer, WRITE_TIMEOUT_MS);
                pos += sendLength;

                string outputstring = HexDump.DumpHexstring(outputBuffer);
                Log.d(TAG, "WRITE: " + outputstring);
            }
        }
 internal void send(UsbHidDriver mDevice)
 {
     throw new NotImplementedException();
 }
        protected async Task <byte[]> ReadFromPump(UsbHidDriver mDevice, MedtronicCnlSession pumpSession, string tag)
        {
            tag = " (" + tag + ")";

            MultipacketSession multipacketSession = null;

            byte[] tupple;
            byte[] payload   = null;
            byte[] decrypted = null;

            bool fetchMoreData    = true;
            int  retry            = 0;
            int  expectedSegments = 0;

            int cmd;

            while (fetchMoreData)
            {
                if (multipacketSession != null)
                {
                    do
                    {
                        if (expectedSegments < 1)
                        {
                            tupple = multipacketSession.MissingSegments();
                            new MultipacketResendPacketsMessage(pumpSession, tupple).send(mDevice);
                            expectedSegments = read16BEtoUInt(tupple, 0x02);
                        }
                        try
                        {
                            payload = await ReadMessage(mDevice, MULTIPACKET_TIMEOUT_MS);

                            break;
                        }
                        catch (TimeoutException e)
                        {
                            if (++retry >= SEGMENT_RETRY)
                            {
                                throw new TimeoutException("Timeout waiting for response from pump (multipacket)" + tag);
                            }
                            Log.d(TAG, "*** Multisession timeout, expecting:" + expectedSegments + " retry: " + retry);
                            expectedSegments = 0;
                        }
                    } while (retry > 0);
                }
                else
                {
                    try
                    {
                        payload = await ReadMessage(mDevice, READ_TIMEOUT_MS);
                    }
                    catch (TimeoutException e)
                    {
                        throw new TimeoutException("Timeout waiting for response from pump" + tag);
                    }
                }

                if (payload.Length < 0x0039)
                {
                    Log.d(TAG, "*** bad response" + HexDump.DumpHexstring(payload, 0x12, payload.Length - 0x14));
                    fetchMoreData = true;
                }
                else
                {
                    decrypted = Decode(pumpSession, payload);

                    cmd = read16BEtoUInt(decrypted, RESPONSE_COMMAND);
                    Log.d(TAG, "CMD: " + HexDump.ToHexstring(cmd));

                    if (MedtronicSendMessageRequestMessage.MessageType.EHSM_SESSION.response(cmd))
                    {
                        // EHSM_SESSION(0)
                        Log.d(TAG, "*** EHSM response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = true;
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.NAK_COMMAND.response(cmd))
                    {
                        Log.d(TAG, "*** NAK response" + HexDump.DumpHexstring(decrypted));
                        ClearMessage(mDevice, ERROR_CLEAR_TIMEOUT_MS);
                        // if multipacket was in progress we may need to clear 2 EHSM_SESSION(1) messages from pump
                        short nakcmd  = read16BEtoShort(decrypted, 3);
                        byte  nakcode = decrypted[5];
                        throw new UnexpectedMessageException("Pump sent a NAK(" + string.Format("%02X", nakcmd) + ":" + string.Format("%02X", nakcode) + ") response" + tag);
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.INITIATE_MULTIPACKET_TRANSFER.response(cmd))
                    {
                        multipacketSession = new MultipacketSession(decrypted);
                        new AckMessage(pumpSession, MedtronicSendMessageRequestMessage.MessageType.INITIATE_MULTIPACKET_TRANSFER.response()).send(mDevice);
                        expectedSegments = multipacketSession.PacketsToFetch;
                        fetchMoreData    = true;
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.MULTIPACKET_SEGMENT_TRANSMISSION.response(cmd))
                    {
                        if (multipacketSession == null)
                        {
                            throw new UnexpectedMessageException("multipacketSession not initiated before segment received" + tag);
                        }
                        multipacketSession.AddSegment(decrypted);
                        expectedSegments--;

                        if (multipacketSession.PayloadComplete)
                        {
                            Log.d(TAG, "*** Multisession Complete");
                            new AckMessage(pumpSession, MedtronicSendMessageRequestMessage.MessageType.MULTIPACKET_SEGMENT_TRANSMISSION.response()).send(mDevice);

                            // read 0412 = EHSM_SESSION(1)
                            payload = await ReadMessage(mDevice, READ_TIMEOUT_MS);

                            decrypted = Decode(pumpSession, payload);
                            Log.d(TAG, "*** response" + HexDump.DumpHexstring(decrypted));

                            return(multipacketSession.Response);
                        }
                        else
                        {
                            fetchMoreData = true;
                        }
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.END_HISTORY_TRANSMISSION.response(cmd))
                    {
                        Log.d(TAG, "*** END_HISTORY_TRANSMISSION response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = false;
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.READ_PUMP_TIME.response(cmd))
                    {
                        Log.d(TAG, "*** READ_PUMP_TIME response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = false;
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.READ_PUMP_STATUS.response(cmd))
                    {
                        Log.d(TAG, "*** READ_PUMP_STATUS response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = false;
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.READ_HISTORY_INFO.response(cmd))
                    {
                        Log.d(TAG, "*** READ_HISTORY_INFO response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = false;
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.READ_BASAL_PATTERN.response(cmd))
                    {
                        Log.d(TAG, "*** READ_BASAL_PATTERN response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = false;
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.READ_BOLUS_WIZARD_CARB_RATIOS.response(cmd))
                    {
                        Log.d(TAG, "*** READ_BOLUS_WIZARD_CARB_RATIOS response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = false;
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.READ_BOLUS_WIZARD_SENSITIVITY_FACTORS.response(cmd))
                    {
                        Log.d(TAG, "*** READ_BOLUS_WIZARD_SENSITIVITY_FACTORS response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = false;
                    }
                    else if (MedtronicSendMessageRequestMessage.MessageType.READ_BOLUS_WIZARD_BG_TARGETS.response(cmd))
                    {
                        Log.d(TAG, "*** READ_BOLUS_WIZARD_BG_TARGETS response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = false;
                    }
                    else
                    {
                        Log.d(TAG, "*** ??? response" + HexDump.DumpHexstring(decrypted));
                        fetchMoreData = true;
                    }
                }
            }

            // when returning non-multipacket decrypted data, we need to trim the 2 byte checksum
            if (decrypted == null)
            {
                return(payload);
            }
            return(decrypted.Partial(0, decrypted.Length - 2));
        }
        // intercept unexpected messages from the CNL
        // these usually come from pump requests as it can occasionally resend message responses several times (possibly due to a missed CNL ACK during CNL-PUMP comms?)
        // mostly noted on the higher radio channels, channel 26 shows this the most
        // if these messages are not cleared the CNL will likely error needing to be unplugged to reset as it expects them to be read before any further commands are sent

        protected int ClearMessage(UsbHidDriver mDevice)
        {
            return(ClearMessage(mDevice, CLEAR_TIMEOUT_MS));
        }