** (十) ** ** 短信部分—— VB.NET ** ** 解码 PDU **
早在 2004 年 1 月份我就开始初步的研究 PDU 的编码解码原理,对于 PDU 也有比较深刻的认识。随后按照 3GPP 协议写了一个 PDU Decoder ,后来写成 PDU Decoder 文章发表在 CodeProject 上面,有几个好心的外国网友给我指出了一些 BUG ,现在成了一个比较完善的 Decoder 。具体的源码在 http://blog.csdn.net/hesicong/archive/2004/09/24/115356.aspx 。本文讲解编码器的构成以及我所使用的解码方法及技巧。
** 解码器的构成 **
NameSpace **SMS **
** Decoder **
MustInheritClass ** SMSBase **
Class ** EMS_RECEIVED **
Class **EMS_SUBMIT **
Class **SMS_RECEIVED **
Class **SMS_STATUS_REPORT **
Class **SMS_SUBMIT **
Class **PDUDecoder **
** SMSBase ** ** 部分 **
SMSBase 类是必须继承类,它包含了 PDU 的基本结构以及一些相关辅助函数,是最基本的类,其他的类都是从 SMSBase 继承的。通过 SMSBase 的 Shared 函数 GetSMSType 可以得到 PDU 的类型,从而确定使用的 Class 。
SMSBase 包含了所有短信类型所共有的基本信息部分以及一个指示短信类型的枚举 SMSType ,继承的类扩展其特有的基本信息部分。
Public SCAddressLength As Byte 'Service Center Address length
Public SCAddressType As Byte ' Service Center Type[See GSM 03.40]
Public SCAddressValue As String ' Service Center nuber
Public FirstOctet As Byte 'See GSM 03.40
Public TP_PID As Byte
Public TP_DCS As Byte
Public TP_UDL As Byte
Public TP_UD As String
Public Text As String
Public Type As SMSType
Public UserData As String
Public Enum SMSType
SMS_RECEIVED = 0
SMS_STATUS_REPORT = 2
SMS_SUBMIT = 1
EMS_RECEIVED = 64 'It is "Reserved" on my phone??
EMS_SUBMIT = 65
End Enum
SMSBase 中定义了一个必须重写的过程 GetOrignalData ,其参数为 PDUCode ,目的是为了得到 PDU 的基本信息。不同的短信类型具有不同的解码过程,所以作为一个必须重写的函数。
Public MustOverride Sub GetOrignalData( ByVal PDUCode As String )
SMSBase 中还有一系列的辅助函数,具体实现方法见源代码:
处理 PDU 代码的:
处理 PDU 代码我运用了自称为“按需裁减”的技巧,就是把需要的数据提取出来解码,然后从原 PDUCode 中删除这一部分,在传递给下一个函数处理。这样就不用考略具体的偏移量,简化了操作,增强了适应性。为了能够减少返回处理过的 PDUCode 麻烦,我使用了 ByRef ,执行过程以后 PDUCode 就自动被裁减了。
'Get a byte from PDU string
Shared Function GetByte( ByRef PDUCode As String ) As Byte
'Get a string of certain length
Shared Function GetString( ByRef PDUCode As String , ByVal Length As Integer ) As String
'Get date from SCTS format
Shared Function GetDate( ByRef SCTS As String ) As Date
'Swap two bit
Shared Function Swap( ByRef TwoBitStr As String ) As String
'Get phone address
Shared Function GetAddress( ByRef Address As String ) As String
Shared Function GetSMSType( ByVal PDUCode As String ) As SMSBase.SMSType
TP-UD 解码部分:
TP-UD 的解码的任务主要集中在 Unicode 的解码和 7BitCode 的解码。其中 Unicode 的解码很方便,只需要将两个字节的 PDUCode 通过 Val 函数转换成为数字,在通过 ChrW 函数即可得到。
而 7BitCode 就显得比较难,下面以 T e s t 四个字符简单介绍其基本原理,具体的编码方式请参考相关资料。
Byte1 1 1010100 0xD4
Byte2 11 110010 0xF2
Byte3 100 11100 0x 9C
Byte4 0000 1110 0x0E
注:各字符二进制代码:
T : 1010100 e : 1100101 s : 1110011 t : 1110100
从这个例子可以看出一个 Byte 包含了一个字符的 ASCII 码的二进制部分及后续字符的二进制部分的低位。这样 8 个字符可以压缩成为 7 个 Byte , SMS 中 140Byte 的 TP-UD 长度就可以容纳 160 个英文字母。
通过观察可以看出,只要我们从后到前把所有的二进制代码拼接到一块,就能够方便的处理,上面例子通过拼接后得到:
0000 1110100 1110011 1100101 1010100
我们可以直接通过从后往前的按 7 个一组的原则进行截取在处理就可以得到解码后的代码。为了编程的方便,我设计了一个简单易懂的解码过程,比起通过做乘除法来进行运算的简单,但最终效率不及它。但我想在普通场合应用也绰绰有余了。
1、 Decode7Bit 得到一个 PDU 的 TP-UD 部分
2、 InvertHexString 反转十六进制代码:例如 123456= 〉 563412
3、 Binary 字符串得到反转后的十六进制代码的二进制表示。注意这里依然使用字符串来表示二进制,为了便于“拼接”和“切割”
4、 根据 charCount 所提供的字符数(来自 TP_UDL )按 7 个一组从字符串位往前截取,并用 Chr 函数转换成 ASCII 码。
以下是一些函数的声明部分,具体函数请参见 Blog 内的 PDUDecoder
'Deoce a unicode string
Shared Function DecodeUnicode( ByVal strUnicode As String ) As String
'Decode 7bit to English
Shared Function InvertHexString( ByVal HexString As String ) As String
Shared Function ByteToBinary( ByVal Dec As Byte ) As String
Shared Function BinaryToInt( ByVal Binary As String ) As Integer
Shared Function Decode7Bit( ByVal str7BitCode As String , ByVal charCount As Integer ) As String
** SMS_SUBMIT ** ** 、 SMS_RECEIVED ** ** 、 SMS_STATUS_REPORT **
由于 SMS_RECEIVED 、 SMS_STATUS_REPORT 与 SMS_SUBMIT 比较相似,所以我重点讲讲 SMS_SUBMIT 。
当用 SMSBase 的 GetSMSType 确定一个 PDUCode 为 SMS_SUBMIT 时,就可以声明一个 SMS_SUBMIT 类的实例,通过传递此 PDUCode 作为构造函数的参数。构造函数立即调用 GetOrignalData 函数解码。
参考协议知道 SMS_SUBMIT 比 SMSBase 多出以下部分:
Public TP_MR As Byte
Public DesAddressLength As Byte
Public DesAddressType As Byte
Public DesAddressValue As String
Public TP_VP As Byte
参考协议我们可以很方便的得到 GetOrignalData 函数的实现:
Public Overrides Sub GetOrignalData( ByVal PDUCode As String )
SCAddressLength = GetByte(PDUCode)
SCAddressType = GetByte(PDUCode)
SCAddressValue = GetAddress((GetString(PDUCode, (SCAddressLength - 1) * 2)))
FirstOctet = GetByte(PDUCode)
TP_MR = GetByte(PDUCode)
DesAddressLength = GetByte(PDUCode)
DesAddressType = GetByte(PDUCode)
DesAddressLength += DesAddressLength Mod 2
DesAddressValue = GetAddress((GetString(PDUCode, DesAddressLength)))
TP_PID = GetByte(PDUCode)
TP_DCS = GetByte(PDUCode)
TP_VP = GetByte(PDUCode)
TP_UDL = GetByte(PDUCode)
<P clas