1.什么是TLV格式?
TLV即Tag-Length-Value,常在IC卡与POS终端设备中通过这样的一个应用通信协议进行数据交换。 金融系统中的TLV是BER-TLV编码的一个特例编码规范,而BER-TLV是ISO定义中的规范。在TLV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)
。这里的长度域的值实际上就是内容域的长度。 其实,在BER编码的方式有两种情况,一种是确定长度的方式,一种是不确定长度的方式,而金融TLV选择了确定长度的方式,这样在设备之间的数据传输量上就可以减少。
2.创建实体类
/// <summary>
/// TLV格式报文实体类
/// </summary>
public class TLVEntity
{
/// <summary>
/// 标记
/// </summary>
public byte[] Tag { get; set; }
/// <summary>
/// 数据长度
/// </summary>
public byte[] Length { get; set; }
/// <summary>
/// 数据
/// </summary>
public byte[] Value { get; set; }
/// <summary>
/// 标记占用字节数
/// </summary>
public int TagSize { get { return this.Tag.Length; } }
/// <summary>
/// 数据长度占用字节数
/// </summary>
public int LengthSize { get { return this.Length.Length; } }
/// <summary>
/// 子嵌套TLV实体列表
/// </summary>
public List<TLVEntity> SubTLVEntity { get; set; }
}
3.报文的打包与解析
/// <summary>
/// TLV格式报文打包解析
/// </summary>
public class PackageTLV
{
/// <summary>
/// 根据tag获取tlv的值
/// </summary>
/// <param name="entities"></param>
/// <param name="tag"></param>
/// <returns></returns>
public static TLVEntity GetValueByTag(List<TLVEntity> entities, string tag)
{
TLVEntity resultEntity = null;
var query = entities.SingleOrDefault(e => e.Tag.ToHexString().ToUpper() == tag);
if (query == null)
{
foreach (var tlv in entities)
{
if (tlv.SubTLVEntity != null)
{
TLVEntity result = GetValueByTag(tlv.SubTLVEntity, tag);
if (result != null && result.Length.Length > 0)
return result;
}
}
}
else
{
resultEntity = query;
}
return resultEntity;
}
/// <summary>
/// 16进制数据转化为TVL实体
/// </summary>
/// <param name="resultData"></param>
/// <returns></returns>
public static List<TLVEntity> ToTLVEntityList(string data)
{
byte[] dataBytes = data.HexStringToByteArray();
var tlvList = Construct(dataBytes);
return tlvList;
}
#region TLV 实体打包
/// <summary>
/// 字节数组转 TLV报文打包
/// </summary>
/// <param name="buffer">字节数据</param>
/// <returns></returns>
public static List<TLVEntity> Construct(byte[] buffer)
{
List<TLVEntity> resultList = new List<TLVEntity>();
int currentIndex = 0;
while (currentIndex < buffer.Length)
{
TLVEntity entity = new TLVEntity();
//1. 根据Tag判断数据是否是嵌套的TLV
bool hasSubEntity = HasSubEntity(buffer, currentIndex);
#region Tag解析
entity.Tag = GetTag(buffer, currentIndex);
currentIndex += entity.Tag.Length;
#endregion
#region Length解析
entity.Length = GetLength(buffer, currentIndex);
currentIndex += entity.Length.Length;
#endregion
#region Value解析
int valueLength = GetValueLengthByLengthByteValue(entity.Length);
entity.Value = buffer.Take(currentIndex + valueLength).Skip(currentIndex).ToArray();
if (hasSubEntity)//判断是否是嵌套结构
{
entity.SubTLVEntity = Construct(entity.Value);//嵌套结构递归解析
}
currentIndex += entity.Value.Length;
#endregion
resultList.Add(entity);
}
return resultList;
}
/// <summary>
/// 是否存在嵌套实体
/// </summary>
/// <returns></returns>
private static bool HasSubEntity(byte[] bytes, int index)
{
if (bytes.Length < index + 1)
throw new ArgumentException("无效的索引值");
return (bytes[index] & 0x20) == 0x20;
}
/// <summary>
/// 获取Tag字节数据
/// </summary>
/// <param name="bytes">长度</param>
/// <param name="index">索引位置</param>
/// <returns></returns>
private static byte[] GetTag(byte[] bytes, int index)
{
if (bytes.Length < index + 1)
{ throw new ArgumentException("无效的索引值"); }
//判断Tag所占字节长度
if ((bytes[index] & 0x1f) == 0x1f)
{
//占2字节
return new byte[] { bytes[index], bytes[index + 1] };
}
else
{
//占1字节
return new byte[] { bytes[index] };
}
}
/// <summary>
/// 获取长度
/// </summary>
/// <param name="bytes">长度</param>
/// <param name="index">索引位置</param>
/// <returns></returns>
private static byte[] GetLength(byte[] bytes, int index)
{
if (bytes.Length < index + 1)
{ throw new ArgumentException("无效的索引值"); }
//判断Length部分所占字节 是1个字节还是多个字节
if ((bytes[index] & 0x80) == 0x80)
{
//占多个字节
int lengthSize = (bytes[index] & 0x7f) + 1;//获取Length所占字节数
return bytes.Take(index + lengthSize).Skip(index).ToArray();
}
else
{
//占单个字节
return new byte[] { bytes[index] };
}
}
/// <summary>
/// 根据Length部分的值获取到value部分的值
/// </summary>
/// <param name="bytes">Length部分的值</param>
/// <returns></returns>
private static int GetValueLengthByLengthByteValue(byte[] bytes)
{
int length = 0;
if (bytes.Length == 1)
{
length = bytes[0];
}
else
{
//从下一个字节开始算Length域
for (int index = 1; index < bytes.Length; index++)
{
length += bytes[index] << ((index - 1) * 8); //计算Length域的长度
}
}
return length;
}
#endregion
#region TLV实体 解析
/// <summary>
/// 解析TLV
/// </summary>
/// <param name="list">
/// <returns></returns>
public static byte[] Parse(List<TLVEntity> list)
{
byte[] buffer = new byte[4096];
int currentIndex = 0;
int currentTLVIndex = 0;
int valueSize = 0;
while (currentTLVIndex < list.Count())
{
valueSize = 0;
TLVEntity entity = list[currentTLVIndex];
Array.Copy(entity.Tag, 0, buffer, currentIndex, entity.TagSize); //解析Tag
currentIndex += entity.TagSize;
for (int index = 0; index < entity.LengthSize; index++)
{
valueSize += entity.Length[index] << (index * 8); //计算Length域的长度
}
if (valueSize > 127)
{
buffer[currentIndex] = Convert.ToByte(0x80 | entity.LengthSize);
currentIndex += 1;
}
Array.Copy(entity.Length, 0, buffer, currentIndex, entity.LengthSize); //解析Length
currentIndex += entity.LengthSize;
//判断是否包含子嵌套TLV
if (entity.SubTLVEntity == null)
{
Array.Copy(entity.Value, 0, buffer, currentIndex, valueSize); //解析Value
currentIndex += valueSize;
}
else
{
byte[] tempBuffer = Parse(entity.SubTLVEntity);
Array.Copy(tempBuffer, 0, buffer, currentIndex, tempBuffer.Length); //解析子嵌套TLV
currentIndex += tempBuffer.Length;
}
currentTLVIndex++;
}
byte[] resultBuffer = new byte[currentIndex];
Array.Copy(buffer, 0, resultBuffer, 0, currentIndex);
return resultBuffer;
}
#endregion
}
4.辅助方法
由于使用上述解析方法解析出来的数据为byte[]
格式,所以在实际应用当中可能需要转换为字符串
public static class MethodExtension
{
/// <summary>
/// 将字节数组转换为十六进制字符字符串。
/// </summary>
/// <param name="bytes">要转为字符串的字节。</param>
/// <returns>作为十六进制字符字符串的字节数组。</returns>
public static string ToHexString(this byte[] bytes)
{
var c = new char[bytes.Length * 2];
for (var i = 0; i < bytes.Length; ++i)
{
var b = ((byte)(bytes[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(bytes[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
/// <summary>
/// 将十六进制字符字符串转换为字节数组。
/// </summary>
/// <param name="hexString">要转换为字节数组的十六进制字符串。</param>
/// <returns>十六进制字符串的字节数组。</returns>
public static byte[] HexStringToByteArray(this string hexString)
{
var hexStringLength = hexString.Length;
if (hexStringLength % 2 == 1) throw new ArgumentException("hexString无效。必须包含偶数个字符,其范围为[0123456789ABCDEF]。");
var b = new byte[hexStringLength / 2];
for (var i = 0; i < hexStringLength; i += 2)
{
var topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
var bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
b[i / 2] = Convert.ToByte(topChar + bottomChar);
}
return b;
}
}
5.应用
static void Main(string[] args)
{
var ValueHex = "01080000000100000000";
var tlvList = PackageTLV.ToTLVEntityList(ValueHex);
var tlvValueList = new List<object>();
foreach (var item in tlvList)
{
tlvValueList.Add(new
{
tag = item?.Tag?.ToHexString(),
type = item?.Tag?.ToHexString(),
len = item?.Length?.ToHexString(),
value = item?.Value?.ToHexString()
});
}
var Value = JsonConvert.SerializeObject(tlvValueList);
Console.WriteLine(Value);
Console.ReadLine();
}