public const int RESPONSE_CRC      = -0x0002; // UInt16BE

        // returns the dycrypted response payload only
        protected byte[] Decode(MedtronicCnlSession pumpSession, byte[] payload)
        {
            if (payload.Length < MM_PAYLOAD + NGP_PAYLOAD + NGP55_PAYLOAD + RESPONSE_PAYLOAD ||
                payload[MM_COMMAND] == (byte)CommandType.READ_INFO ||
                payload[MM_COMMAND] == (byte)CommandType.REQUEST_LINK_KEY_RESPONSE)
            {
                throw new EncryptionException("Message received for decryption wrong type/size");
            }

            byte encryptedPayloadSize = payload[MM_PAYLOAD + NGP_PAYLOAD + NGP55_ENCRYPTED_SIZE];

            if (encryptedPayloadSize == 0)
            {
                throw new EncryptionException("Could not decrypt Medtronic Message (encryptedPayloadSize == 0)");
            }

            var encryptedPayload = new MedtronicMessageBuffer(encryptedPayloadSize);

            encryptedPayload.Put(payload, MM_PAYLOAD + NGP_PAYLOAD + NGP55_PAYLOAD, encryptedPayloadSize);
            var decryptedPayload = Decrypt(pumpSession.getKey(), pumpSession.getIV(), encryptedPayload);

            if (decryptedPayload == null)
            {
                throw new EncryptionException("Could not decrypt Medtronic Message (decryptedPayload == null)");
            }

#if DEBUG
            string outputstring = HexDump.DumpHexstring(decryptedPayload);
            Log.d(TAG, "DECRYPTED: " + outputstring);
#endif

            return(decryptedPayload);
        }
        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);
            }
        }
        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));
        }