Created
January 12, 2016 19:07
-
-
Save dasjestyr/2d707d9e4f685e3b8be7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// <summary> | |
| /// List of "approved" emv tags | |
| /// </summary> | |
| public enum EmvTag | |
| { | |
| /// <summary> | |
| /// Terminal verification results (TVR) | |
| /// </summary> | |
| TerminalVerificationResults = 0x95, | |
| /// <summary> | |
| /// Issuer application data (IAD) | |
| /// </summary> | |
| IssuerApplicationData = 0x9F10, | |
| /// <summary> | |
| /// Transaction status information (TSI) | |
| /// </summary> | |
| TransactionStatusInformation = 0x9B, | |
| /// <summary> | |
| /// Authorization response code (ARC) | |
| /// </summary> | |
| AuthorizationResponseCode = 0x8A, | |
| /// <summary> | |
| /// Issuer identification number (IIN) | |
| /// </summary> | |
| IssuerIdentificationNumber = 0x42, | |
| /// <summary> | |
| /// Application identifier (ADF Name) | |
| /// </summary> | |
| ApplicationIdentifierAdfName = 0x4F, | |
| /// <summary> | |
| /// The application label | |
| /// </summary> | |
| ApplicationLabel = 0x50, | |
| /// <summary> | |
| /// The track2 equivalent data | |
| /// </summary> | |
| Track2EquivalentData = 0x57, | |
| /// <summary> | |
| /// Application Primary Account (PAN) | |
| /// </summary> | |
| PrimaryAccountNumber = 0x5A, | |
| /// <summary> | |
| /// The cardholder name | |
| /// </summary> | |
| CardholderName = 0x5F20, | |
| /// <summary> | |
| /// The application expiration date | |
| /// </summary> | |
| ApplicationExpirationDate = 0x5F24, | |
| /// <summary> | |
| /// The application effective date | |
| /// </summary> | |
| ApplicationEffectiveDate = 0x5F25, | |
| /// <summary> | |
| /// The issuer country code | |
| /// </summary> | |
| IssuerCountryCode = 0x5F28, | |
| /// <summary> | |
| /// The service code | |
| /// </summary> | |
| ServiceCode = 0x5F30, | |
| /// <summary> | |
| /// The pan sequence number | |
| /// </summary> | |
| PANSequenceNumber = 0x5F34, | |
| /// <summary> | |
| /// The application interchange profile | |
| /// </summary> | |
| ApplicationInterchangeProfile = 0x82, | |
| /// <summary> | |
| /// The dedicated file | |
| /// </summary> | |
| DedicatedFile = 0x84, | |
| /// <summary> | |
| /// The transaction type | |
| /// </summary> | |
| TransactionType = 0x9C, | |
| /// <summary> | |
| /// The authorized amount | |
| /// </summary> | |
| AuthorizedAmount = 0x9F02, | |
| /// <summary> | |
| /// The other amount numeric | |
| /// </summary> | |
| OtherAmountNumeric = 0x9F03, | |
| /// <summary> | |
| /// Application ID (AID: terminal) | |
| /// </summary> | |
| ApplicationIDTerminal = 0x9F06, | |
| /// <summary> | |
| /// The application version number | |
| /// </summary> | |
| ApplicationVersionNumber = 0x9F09, | |
| /// <summary> | |
| /// The issuer code table index | |
| /// </summary> | |
| IssuerCodeTableIndex = 0x9F11, | |
| /// <summary> | |
| /// The application preferred name | |
| /// </summary> | |
| ApplicationPreferredName = 0x9F12, | |
| /// <summary> | |
| /// The terminal country code | |
| /// </summary> | |
| TerminalCountryCode = 0x9F1A, | |
| /// <summary> | |
| /// The terminal identification | |
| /// </summary> | |
| TerminalIdentification = 0x9F1C, | |
| /// <summary> | |
| /// The interface device serial number (IFD) | |
| /// </summary> | |
| InterfaceDeviceSerialNumber = 0x9F1E, | |
| /// <summary> | |
| /// Application cryptogram (AC) | |
| /// </summary> | |
| ApplicationCryptogram = 0x9F26, | |
| /// <summary> | |
| /// Cryptogram information data (CID) | |
| /// </summary> | |
| CryptogramInformationData = 0x9F27, | |
| /// <summary> | |
| /// The terminal capabilities | |
| /// </summary> | |
| TerminalCapabilities = 0x9F33, | |
| /// <summary> | |
| /// Cardholder verification method results | |
| /// </summary> | |
| CVMResults = 0x9F34, | |
| /// <summary> | |
| /// The terminal type | |
| /// </summary> | |
| TerminalType = 0x9F35, | |
| /// <summary> | |
| /// Application transaction counter (ATC) | |
| /// </summary> | |
| ApplicationTransactionCounter = 0x9F36, | |
| /// <summary> | |
| /// Unpredictable number (UN) | |
| /// </summary> | |
| UnpredictableNumber = 0x9F37, | |
| /// <summary> | |
| /// The transaction sequence counter | |
| /// </summary> | |
| TransactionSequenceCounter = 0x9F41, | |
| /// <summary> | |
| /// Transaction category code | |
| /// </summary> | |
| TransactionCategoryCode = 0x9F53, | |
| /// <summary> | |
| /// Issuer script result | |
| /// </summary> | |
| IssuerScriptResult = 0x9F5B | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// <summary> | |
| /// Helper class wraps a tlv tag dictionary and permits easy access for EMV-specific tags. | |
| /// </summary> | |
| public class EmvTlvTagValues | |
| { | |
| private readonly ReadOnlyDictionary<string, string> _tlvDictionary; | |
| public string this[EmvTag tag] | |
| { | |
| get | |
| { | |
| var tagString = ((int)tag).ToString("X2"); | |
| return _tlvDictionary.ContainsKey(tagString) ? _tlvDictionary[tagString] : null; | |
| } | |
| } | |
| public string this[string tag] => _tlvDictionary.ContainsKey(tag) ? _tlvDictionary[tag] : null; | |
| /// <summary> | |
| /// Initializes a new instance of the <see cref="EmvTlvTagValues" /> class. | |
| /// </summary> | |
| /// <param name="tlvString">The TLV string.</param> | |
| public EmvTlvTagValues(string tlvString) | |
| { | |
| _tlvDictionary = TlvParser.ReadTlv(tlvString); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public class TlvParser | |
| { | |
| private static readonly ILogger Logger = new DefaultLogWriter(); | |
| private const string LogTitle = "TLV_PARSER"; | |
| private const int ExtendedTagMask = 0x1F; | |
| private const int ContinueExtendedtagMask = 0x80; | |
| private const int ExtendedLengthMask = 0x80; | |
| /// <summary> | |
| /// Reads the TLV. | |
| /// </summary> | |
| /// <param name="tlv">The TLV.</param> | |
| /// <returns></returns> | |
| /// <exception cref="System.ArgumentException">Could not create dictionary for tlv tag-values. Are you sure this tlv string doesn't contain duplicate tags?</exception> | |
| public static ReadOnlyDictionary<string, string>ReadTlv(string tlv) | |
| { | |
| var values = ReadTlvWithDupeTags(tlv); | |
| IDictionary<string, string> result; | |
| try | |
| { | |
| result = values.ToDictionary(value => value.Key, value => value.Value); | |
| } | |
| catch (ArgumentException ex) | |
| { | |
| throw new ArgumentException("Could not create dictionary for tlv tag-values. Are you sure this tlv string doesn't contain duplicate tags?", ex); | |
| } | |
| return new ReadOnlyDictionary<string, string>(result); | |
| } | |
| /// <summary> | |
| /// Reads tlv string and returns collection of tags and values. Use when you know there will be duplicate tags for each entry (e.g. CAPK response from XMLPOSServer) | |
| /// </summary> | |
| /// <param name="tlv">The TLV.</param> | |
| /// <returns></returns> | |
| /// <exception cref="System.ArgumentException">tlv</exception> | |
| public static ReadOnlyCollection<KeyValuePair<string, string>> ReadTlvWithDupeTags(string tlv) | |
| { | |
| var result = new List<KeyValuePair<string, string>>(); | |
| // validate | |
| if (string.IsNullOrEmpty(tlv)) | |
| { | |
| Logger.Debug("TLV was null or empty. Nothing to parse!", LogTitle); | |
| return new ReadOnlyCollection<KeyValuePair<string, string>>(result); | |
| } | |
| if ((tlv.Length & 0x01) != 0x00) | |
| { | |
| const string error = "Invalid TLV size. String must be an even number of bytes!"; | |
| Logger.Error(error, LogTitle); | |
| throw new ArgumentException(error, nameof(tlv)); | |
| } | |
| // process via state machine | |
| var currentState = TlvParseState.ReadingTag; | |
| var valueLength = 0; | |
| var extendedLengthCount = 0; | |
| var tag = string.Empty; | |
| var value = string.Empty; | |
| for (var i = 0; i < tlv.Length; i += 2) | |
| { | |
| var currentByteHex = tlv.Substring(i, 2); | |
| int currentByte; | |
| // ensure the current byte is a valid hex value | |
| try | |
| { | |
| currentByte = int.Parse(currentByteHex, NumberStyles.HexNumber); | |
| } | |
| catch | |
| { | |
| var error = | |
| $"Invalid TLV. All characters must be hex, but we encountered a non hex value ({currentByteHex})"; | |
| Logger.Error(error, LogTitle); | |
| throw new ArgumentException(error, nameof(tlv)); | |
| } | |
| // process | |
| switch (currentState) | |
| { | |
| case TlvParseState.ReadingTag: | |
| tag = currentByteHex; | |
| value = string.Empty; | |
| currentState = (currentByte & ExtendedTagMask) == ExtendedTagMask ? TlvParseState.ReadingExtendedTag : TlvParseState.ReadingValueLength; | |
| break; | |
| case TlvParseState.ReadingExtendedTag: | |
| tag = tag + currentByteHex; | |
| if ((currentByte & ContinueExtendedtagMask) == 0x00) | |
| currentState = TlvParseState.ReadingValueLength; | |
| break; | |
| case TlvParseState.ReadingValueLength: | |
| if ((currentByte & ExtendedLengthMask) == ExtendedLengthMask) | |
| { | |
| extendedLengthCount = currentByte ^ ExtendedLengthMask; | |
| currentState = TlvParseState.ReadingExtendedValueLength; | |
| if (extendedLengthCount < 1 || extendedLengthCount > 4) | |
| { | |
| var error = | |
| $"Invalid TLV extended length size (allowable range 1-4 bytes): {extendedLengthCount}"; | |
| Logger.Error(error, LogTitle); | |
| throw new ArgumentException(error, nameof(tlv)); | |
| } | |
| } | |
| else | |
| { | |
| valueLength = currentByte; | |
| if (valueLength > 0) | |
| { | |
| currentState = TlvParseState.ReadingValue; | |
| } | |
| else | |
| { | |
| result.Add(new KeyValuePair<string, string>(tag.ToUpper(), value)); | |
| currentState = TlvParseState.ReadingTag; | |
| } | |
| } | |
| break; | |
| case TlvParseState.ReadingExtendedValueLength: | |
| valueLength = (valueLength << 8) | currentByte; | |
| extendedLengthCount--; | |
| if (extendedLengthCount == 0) | |
| { | |
| currentState = TlvParseState.ReadingValue; | |
| } | |
| break; | |
| case TlvParseState.ReadingValue: | |
| value = value + currentByteHex; | |
| valueLength--; | |
| if (valueLength == 0) | |
| { | |
| result.Add(new KeyValuePair<string, string>(tag.ToUpper(), value)); | |
| currentState = TlvParseState.ReadingTag; | |
| } | |
| break; | |
| } | |
| } | |
| return new ReadOnlyCollection<KeyValuePair<string, string>>(result); | |
| } | |
| /// <summary> | |
| /// Writes a TLV string. | |
| /// </summary> | |
| /// <param name="values">The value dictionary where the value is a hex string.</param> | |
| /// <param name="blacklistTags">Return tlv string without certain tags.</param> | |
| /// <returns></returns> | |
| public static string WriteTlv(IDictionary<string, string> values, IEnumerable<string> blacklistTags = null) | |
| { | |
| var tlv = new StringBuilder(); | |
| var bl = blacklistTags?.ToList(); | |
| foreach (var item in values) | |
| { | |
| var tag = item.Key; | |
| var value = item.Value; | |
| // if the current tag is in the black list, skip it | |
| if (blacklistTags != null && bl.Contains(tag)) continue; | |
| /* TLV = [Tag]-[Length]-[Value] | |
| * Tag is normally 2 bytes, but could be 3 (e.g. muira) | |
| * Length is variable in length (i.e. could be 1 byte, 2 bytes, 3 bytes, etc) | |
| * Value is variable in length. The byte length of the value is defined in the [Length] block of the TLV | |
| * | |
| * NOTE: '8X' is a flag in the [Length] block. If this is the first bit in this block, it denotes that the byte length | |
| * of the [Length] block has been extended beyond the normal 1 byte. Where 8 is the flag denoting the extension, X is | |
| * the byte length of the extended [Length] block. For example, if the first byte of the [Length] block is | |
| * '82', this means that the [Length] block is extended and the new size of the [Length] block is 2 bytes. | |
| */ | |
| // figure out the length of the [length] block | |
| string extendedLength; | |
| var byteLength = value.Length/2; | |
| if (byteLength <= 0x7F) | |
| { | |
| extendedLength = string.Empty; | |
| } | |
| else if (byteLength <= 0xFF) | |
| { | |
| // extended and is 1 byte | |
| extendedLength = "81"; | |
| } | |
| else if (byteLength <= 0xFFFF) | |
| { | |
| // extended and is 2 bytes | |
| extendedLength = "82"; | |
| } | |
| else if (byteLength <= 0xFFFFFF) | |
| { | |
| // extended and is 3 bytes | |
| extendedLength = "83"; | |
| } | |
| else | |
| { | |
| // extended and is 4 bytes (huge) | |
| extendedLength = "84"; | |
| } | |
| // pad the hex value if wrong length (2) | |
| var lengthHexValue = byteLength.ToString("X2"); | |
| if (lengthHexValue.Length%2 != 0) | |
| { | |
| lengthHexValue = "0" + lengthHexValue; | |
| } | |
| tlv.AppendFormat("{0}{1}{2}{3}", tag, extendedLength, lengthHexValue, value); | |
| } | |
| return tlv.ToString(); | |
| } | |
| private enum TlvParseState | |
| { | |
| ReadingTag, | |
| ReadingExtendedTag, | |
| ReadingValueLength, | |
| ReadingExtendedValueLength, | |
| ReadingValue | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment