/// <summary>
        /// Track Data is provided in TLV with DFDB05 and DFDB06 TAGS
        /// </summary>
        /// <param name="trackInformation"></param>
        /// <returns></returns>
        public MSRTrackData RetrieveSREDTrackData(byte[] trackInformation)
        {
            byte[] sREDMagStripTrack1 = new byte[] { 0xDF, 0xDB, 0x05 };
            byte[] sREDMagStripTrack2 = new byte[] { 0xDF, 0xDB, 0x06 };

            MSRTrackData trackData = new MSRTrackData()
            {
                PANData           = string.Empty,
                Name              = string.Empty,
                ExpirationDate    = string.Empty,
                DiscretionaryData = string.Empty
            };

            TLV.TLV        tlv  = new TLV.TLV();
            List <TLV.TLV> tags = tlv.Decode(trackInformation, 0, trackInformation.Length, null);

            string decryptedTrack1Data = "";
            string decryptedTrack2Data = "";

            foreach (var tag in tags)
            {
                if (tag.Tag.SequenceEqual(sREDMagStripTrack1))
                {
                    decryptedTrack1Data = ConversionHelper.ByteArrayCodedHextoString(tag.Data);
                }
                else if (tag.Tag.SequenceEqual(sREDMagStripTrack2))
                {
                    decryptedTrack2Data = ConversionHelper.ByteArrayCodedHextoString(tag.Data);
                }
            }

            // clean up track data
            Debug.WriteLine($"DECRYPTED _: {decryptedTrack1Data}");

            // expected format: PAN^NAME^ADDITIONAL-DATA^DISCRETIONARY-DATA
            MatchCollection match = Regex.Matches(decryptedTrack1Data, @"%B([0-9 ]{1,19})\^([^\^]{2,26})\^([0-9]{4}|\^)([0-9]{3}|\^)([^\?]+)\?", RegexOptions.Compiled);

            // DISCRETIONARY DATA is optional
            if (match.Count == 1 && match[0].Groups.Count >= 5)
            {
                // PAN DATA
                trackData.PANData = match[0].Groups[1].Value;

                // NAME
                trackData.Name = match[0].Groups[2].Value;

                // ADDITIONAL DATA
                trackData.ExpirationDate = match[0].Groups[3].Value;
                trackData.ServiceCode    = match[0].Groups[4].Value;

                // DISCRETIONARY DATA
                if (match[0].Groups.Count > 5)
                {
                    trackData.DiscretionaryData = match[0].Groups[5].Value;
                }
            }

            return(trackData);
        }
        private void ReadResponses(byte[] responseBytes)
        {
            var validNADValues = new List <byte> {
                0x01, 0x02, 0x11
            };
            var validPCBValues = new List <byte> {
                0x00, 0x01, 0x02, 0x03, 0x40, 0x41, 0x42, 0x43
            };
            var nestedTagTags = new List <byte[]> {
                new byte[] { 0xEE }, new byte[] { 0xEF }, new byte[] { 0xF0 }, new byte[] { 0xE0 }, new byte[] { 0xE4 }, new byte[] { 0xE7 }, new byte[] { 0xFF, 0x7C }, new byte[] { 0xFF, 0x7F }
            };
            var powerManagement = new List <byte[]> {
                new byte[] { 0xE6 }, new byte[] { 0xC3 }, new byte[] { 0xC4 }, new byte[] { 0x9F, 0x1C }
            };
            var addedResponseComponent = false;

            lock (ReadResponsesBytesLock)
            {
                // Add current bytes to available bytes
                var combinedResponseBytes = new byte[ReadResponsesBytes.Length + responseBytes.Length];

                // TODO ---> @JonBianco BlockCopy should be leveraging here as it is vastly superior to Array.Copy
                // Combine prior bytes with new bytes
                Array.Copy(ReadResponsesBytes, 0, combinedResponseBytes, 0, ReadResponsesBytes.Length);
                Array.Copy(responseBytes, 0, combinedResponseBytes, ReadResponsesBytes.Length, responseBytes.Length);

                // Attempt to parse first message in response buffer
                var consumedResponseBytes = 0;
                var responseCode          = 0;
                var errorFound            = false;

                ReadErrorLevel readErrorLevel = ReadErrorLevel.None;

                // Validate NAD, PCB, and LEN values
                if (combinedResponseBytes.Length < 4)
                {
                    errorFound     = true;
                    readErrorLevel = ReadErrorLevel.Length;
                }
                else if (!validNADValues.Contains(combinedResponseBytes[0]))
                {
                    errorFound     = true;
                    readErrorLevel = ReadErrorLevel.Invalid_NAD;
                }
                else if (!validPCBValues.Contains(combinedResponseBytes[1]))
                {
                    errorFound     = true;
                    readErrorLevel = ReadErrorLevel.Invalid_PCB;
                }
                else if (combinedResponseBytes[2] > (combinedResponseBytes.Length - (3 + 1)))
                {
                    errorFound     = true;
                    readErrorLevel = ReadErrorLevel.Invalid_CombinedBytes;
                }
                else
                {
                    // Validate LRC
                    byte lrc   = 0;
                    var  index = 0;
                    for (index = 0; index < (combinedResponseBytes[2] + 3); index++)
                    {
                        lrc ^= combinedResponseBytes[index];
                    }

                    if (combinedResponseBytes[combinedResponseBytes[2] + 3] != lrc)
                    {
                        errorFound     = true;
                        readErrorLevel = ReadErrorLevel.Missing_LRC;
                    }
                    else if ((combinedResponseBytes[1] & 0x01) == 0x01)
                    {
                        var componentBytes = new byte[combinedResponseBytes[2]];
                        Array.Copy(combinedResponseBytes, 3, componentBytes, 0, combinedResponseBytes[2]);
                        ReadResponseComponentBytes.Add(componentBytes);
                        consumedResponseBytes  = combinedResponseBytes[2] + 3 + 1;
                        errorFound             = true;
                        readErrorLevel         = ReadErrorLevel.CombinedBytes_MisMatch;
                        addedResponseComponent = true;
                    }
                    else
                    {
                        var sw1Offset = combinedResponseBytes[2] + 3 - 2;
                        //if ((combinedResponseBytes[sw1Offset] != 0x90) && (combinedResponseBytes[sw1Offset + 1] != 0x00))
                        //    errorFound = true;
                        responseCode = (combinedResponseBytes[sw1Offset] << 8) + combinedResponseBytes[sw1Offset + 1];
                    }
                }

                if (!errorFound)
                {
                    var totalDecodeSize = combinedResponseBytes[2] - 2;        // Use LEN of final response packet
                    foreach (var component in ReadResponseComponentBytes)
                    {
                        totalDecodeSize += component.Length;
                    }

                    var totalDecodeBytes  = new byte[totalDecodeSize];
                    var totalDecodeOffset = 0;
                    foreach (var component in ReadResponseComponentBytes)
                    {
                        Array.Copy(component, 0, totalDecodeBytes, totalDecodeOffset, component.Length);
                        totalDecodeOffset += component.Length;
                    }
                    Array.Copy(combinedResponseBytes, 3, totalDecodeBytes, totalDecodeOffset, combinedResponseBytes[2] - 2);    // Skip final response header and use LEN of final response (no including the SW1, SW2, and LRC)

                    ReadResponseComponentBytes = new List <byte[]>();

                    if (ResponseTagsHandler != null || ResponseContactlessHandler != null)
                    {
                        TLV.TLV tlv  = new TLV.TLV();
                        var     tags = tlv.Decode(totalDecodeBytes, 0, totalDecodeBytes.Length, nestedTagTags);

                        //PrintTags(tags);
                        if (ResponseTagsHandler != null)
                        {
                            ResponseTagsHandler.Invoke(tags, responseCode);
                        }
                        else if (ResponseContactlessHandler != null)
                        {
                            ResponseContactlessHandler.Invoke(tags, responseCode, combinedResponseBytes[1]);
                        }
                    }
                    else if (ResponseTaglessHandler != null)
                    {
                        ResponseTaglessHandler.Invoke(totalDecodeBytes, responseCode);
                    }

                    consumedResponseBytes = combinedResponseBytes[2] + 3 + 1;  // Consumed NAD, PCB, LEN, [LEN] bytes, and LRC

                    addedResponseComponent = (combinedResponseBytes.Length - consumedResponseBytes) > 0;
                }
                else
                {
                    // allows for debugging of VIPA read issues
                    System.Diagnostics.Debug.WriteLine($"VIPA-READ: ON PORT={commPort} - ERROR LEVEL: '{readErrorLevel}'");
                }

                // Remove consumed bytes and leave remaining bytes for later consumption
                var remainingResponseBytes = new byte[combinedResponseBytes.Length - consumedResponseBytes];
                Array.Copy(combinedResponseBytes, consumedResponseBytes, remainingResponseBytes, 0, combinedResponseBytes.Length - consumedResponseBytes);

                ReadResponsesBytes = remainingResponseBytes;
            }

            if (addedResponseComponent)
            {
                ReadResponses(Array.Empty <byte>());
            }
        }