Android短信源码分析 --PDU解析过程
6.14 PDU解析过程
SmsMessage.Java,通过createFromPdu开始解析PDU数据。
public static SmsMessage createFromPdu(byte[] pdu) {
try {
SmsMessage msg = new SmsMessage();
msg.parsePdu(pdu);
return msg;
} catch (RuntimeException ex) {
Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
}
依据协议TS 23.040 9.2.3.9,parsePdu 先解析出SCA,再根据mti消息类型决定使用哪个解析函数,对于接收的sms,使用parseSmsDeliver。
parseSmsDeliver 解析出OA(2字节+号码个数并补偶),PID(1B),dcs(1B),scts(7B),并根据第一个字节判断是否有UDH,最后调用parseUserData。
private void parseSmsDeliver(PduParser p, int firstByte) {
replyPathPresent = (firstByte & 0x80) == 0x80;
originatingAddress = p.getAddress();
if (originatingAddress != null) {
if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
+ originatingAddress.address);
}
protocolIdentifier = p.getByte();
// TP-Data-Coding-Scheme
// see TS 23.038
dataCodingScheme = p.getByte();
scTimeMillis = p.getSCTimestampMillis();
if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
parseUserData(p, hasUserDataHeader);
}
parseUserData,先根据协议3GPP TS 23.038 V7.0.0,将DCS分类,解析消息属性。再使用constructUserData创建一个用户实例,分解出用户数据userData和用户数据头userDataHeader,
int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
if (hasUserDataHeader) {
System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
userDataHeader = SmsHeader.fromByteArray(udh);
}
…
userData = new byte[bufferLen];
System.arraycopy(pdu, offset, userData, 0, userData.length);
}
对于用户数据头userDataHeader,
SmsHeader fromByteArray会根据协议TS 23.040 9.2.3.24来解析用户数据头信息UDH,先是头信息总长度UDHL,再是具体的数据,每一段的数据格式为:类型ID(1B)+该类型后续数据长度(1B)+数据(x B,决定于长度),有多少个类型就由多少倍上面的数据长度,示例数据:04 01020101,UDHL 4字节,有一条ID=01的记录,其数据长2字节,数据是0101。
如果文件头带R8编码,SmsHeader fromByteArray里会进行相应解析,并将IEI数据信息存放在languageShiftTable和languageTable里面。
public static SmsHeader fromByteArray(byte[] data) {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
SmsHeader smsHeader = new SmsHeader();
while (inStream.available() > 0) {
int id = inStream.read(); \\读出IEI类型
int length = inStream.read();\\读出IEI长度
switch (id) {
case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT: \\使用转换表
smsHeader.languageShiftTable = inStream.read();\\读出IEI数据
break;
case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
smsHeader.languageTable = inStream.read();\\读出IEI数据
break;
}
return smsHeader;
}
对于用户数据userData,
以ENCODING_7BIT编码方式来分析,
case ENCODING_7BIT:
messageBody = p.getUserDataGSM7Bit(count, //字节个数
hasUserDataHeader ? userDataHeader.languageTable : 0, //是否使用编码表
hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
break;
gsm7BitPackedToString会完成将PDU里面的数据解压成字符串的过程,存放在PDU里的数据是经过7bit压缩过的转义字符串,不是我们正常阅读的字符串。解压的时候会参考languageShiftTable和languageTable来选择相应的字符表。
public static String gsm7BitPackedToString(byte[] pdu, int offset,
int lengthSeptets, int numPaddingBits, int languageTable, int shiftTable) {
StringBuilder ret = new StringBuilder(lengthSeptets);
try {
boolean prevCharWasEscape = false;
String languageTableToChar = sLanguageTables[languageTable];//选择基本表
String shiftTableToChar = sLanguageShiftTables[shiftTable];//选择扩展表
for (int i = 0 ; i < lengthSeptets ; i++) {
int bitOffset = (7 * i) + numPaddingBits;
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
int gsmVal;
gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
// if it crosses a byte boundary
if (shift > 1) {
// set msb bits to 0
gsmVal &= 0x7f >> (shift - 1);
gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
}//对gsmVal的处理是依次解析出进行过7BIT压缩的每个字符,这些字符目前还是转义过的字符,不是我们通过字符串能正常看到的字符
if (prevCharWasEscape) {//前一个字符是转义字符
if (gsmVal == GSM_EXTENDED_ESCAPE) {
ret.append(' '); // display ' ' for reserved double escape sequence 连续2个转义字符表示一个空格
} else {
char c = shiftTableToChar.charAt(gsmVal);
if (c == ' ') { // 转义字符后字符应该在shift表里查找,如果shift表里在存放的是空格(见表定义,shift表里没有定义的字符都是空格),则使用基本语言表的字符
ret.append(languageTableToChar.charAt(gsmVal));
} else {
ret.append(c); // 转义字符后字符应该在shift表里查找
}
}
prevCharWasEscape = false;
} else if (gsmVal == GSM_EXTENDED_ESCAPE) {
prevCharWasEscape = true; //当前字符是转义字符,存起来给下一个字符使用
} else {
ret.append(languageTableToChar.charAt(gsmVal));//正常时,找到语言表里的ascii字符
} } } }
return ret.toString();
}
至此,PDU数据解析完成。
对接收PDU数据解码总结如下:
分段 含义 说明
SC地址信息的长度n, n个八位字节(包括91) (长度占1字节)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),SMSC地址要补‘F’凑成偶数个 (n字节)
基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址,回复地址数字个数m, 共m个十进制数(不包括91和‘F’) (x字节)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),回复地址(TP-RA),补‘F’凑成偶数个 ((m+1)/2 字节)
协议标识(TP-PID), 00是普通GSM类型,点到点方式 (1字节)
用户信息编码方式(TP-DCS) ,08是UCS2编码 (1字节)
时间戳(TP-SCTS) 80是 +8时区 (7字节)
用户信息长度(TP-UDL) UCS2编码是字节数,7BIT编码 是字符个数 (长度占1字节)
用户信息(TP-UD) (y字节)
举例:
分段 含义 说明
08 地址信息的长度 个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址
0D 回复地址数字个数 共13个十进制数(不包括91和‘F’)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 回复地址(TP-RA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
08 用户信息编码方式(TP-DCS) UCS2编码
30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8时区
06 用户信息长度(TP-UDL) 实际长度6个字节
4F 60 59 7D 00 21 用户信息(TP-UD) “你好!”
6.14 PDU解析过程
SmsMessage.java,通过createFromPdu开始解析PDU数据。
public static SmsMessage createFromPdu(byte[] pdu) {
try {
SmsMessage msg = new SmsMessage();
msg.parsePdu(pdu);
return msg;
} catch (RuntimeException ex) {
Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
}
依据协议TS 23.040 9.2.3.9,parsePdu 先解析出SCA,再根据mti消息类型决定使用哪个解析函数,对于接收的sms,使用parseSmsDeliver。
parseSmsDeliver 解析出OA(2字节+号码个数并补偶),PID(1B),dcs(1B),scts(7B),并根据第一个字节判断是否有UDH,最后调用parseUserData。
private void parseSmsDeliver(PduParser p, int firstByte) {
replyPathPresent = (firstByte & 0x80) == 0x80;
originatingAddress = p.getAddress();
if (originatingAddress != null) {
if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
+ originatingAddress.address);
}
protocolIdentifier = p.getByte();
// TP-Data-Coding-Scheme
// see TS 23.038
dataCodingScheme = p.getByte();
scTimeMillis = p.getSCTimestampMillis();
if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
parseUserData(p, hasUserDataHeader);
}
parseUserData,先根据协议3GPP TS 23.038 V7.0.0,将DCS分类,解析消息属性。再使用constructUserData创建一个用户实例,分解出用户数据userData和用户数据头userDataHeader,
int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
if (hasUserDataHeader) {
System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
userDataHeader = SmsHeader.fromByteArray(udh);
}
…
userData = new byte[bufferLen];
System.arraycopy(pdu, offset, userData, 0, userData.length);
}
对于用户数据头userDataHeader,
SmsHeader fromByteArray会根据协议TS 23.040 9.2.3.24来解析用户数据头信息UDH,先是头信息总长度UDHL,再是具体的数据,每一段的数据格式为:类型ID(1B)+该类型后续数据长度(1B)+数据(x B,决定于长度),有多少个类型就由多少倍上面的数据长度,示例数据:04 01020101,UDHL 4字节,有一条ID=01的记录,其数据长2字节,数据是0101。
如果文件头带R8编码,SmsHeader fromByteArray里会进行相应解析,并将IEI数据信息存放在languageShiftTable和languageTable里面。
public static SmsHeader fromByteArray(byte[] data) {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
SmsHeader smsHeader = new SmsHeader();
while (inStream.available() > 0) {
int id = inStream.read(); \\读出IEI类型
int length = inStream.read();\\读出IEI长度
switch (id) {
case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT: \\使用转换表
smsHeader.languageShiftTable = inStream.read();\\读出IEI数据
break;
case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
smsHeader.languageTable = inStream.read();\\读出IEI数据
break;
}
return smsHeader;
}
对于用户数据userData,
以ENCODING_7BIT编码方式来分析,
case ENCODING_7BIT:
messageBody = p.getUserDataGSM7Bit(count, //字节个数
hasUserDataHeader ? userDataHeader.languageTable : 0, //是否使用编码表
hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
break;
gsm7BitPackedToString会完成将PDU里面的数据解压成字符串的过程,存放在PDU里的数据是经过7bit压缩过的转义字符串,不是我们正常阅读的字符串。解压的时候会参考languageShiftTable和languageTable来选择相应的字符表。
public static String gsm7BitPackedToString(byte[] pdu, int offset,
int lengthSeptets, int numPaddingBits, int languageTable, int shiftTable) {
StringBuilder ret = new StringBuilder(lengthSeptets);
try {
boolean prevCharWasEscape = false;
String languageTableToChar = sLanguageTables[languageTable];//选择基本表
String shiftTableToChar = sLanguageShiftTables[shiftTable];//选择扩展表
for (int i = 0 ; i < lengthSeptets ; i++) {
int bitOffset = (7 * i) + numPaddingBits;
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
int gsmVal;
gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
// if it crosses a byte boundary
if (shift > 1) {
// set msb bits to 0
gsmVal &= 0x7f >> (shift - 1);
gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
}//对gsmVal的处理是依次解析出进行过7BIT压缩的每个字符,这些字符目前还是转义过的字符,不是我们通过字符串能正常看到的字符
if (prevCharWasEscape) {//前一个字符是转义字符
if (gsmVal == GSM_EXTENDED_ESCAPE) {
ret.append(' '); // display ' ' for reserved double escape sequence 连续2个转义字符表示一个空格
} else {
char c = shiftTableToChar.charAt(gsmVal);
if (c == ' ') { // 转义字符后字符应该在shift表里查找,如果shift表里在存放的是空格(见表定义,shift表里没有定义的字符都是空格),则使用基本语言表的字符
ret.append(languageTableToChar.charAt(gsmVal));
} else {
ret.append(c); // 转义字符后字符应该在shift表里查找
}
}
prevCharWasEscape = false;
} else if (gsmVal == GSM_EXTENDED_ESCAPE) {
prevCharWasEscape = true; //当前字符是转义字符,存起来给下一个字符使用
} else {
ret.append(languageTableToChar.charAt(gsmVal));//正常时,找到语言表里的ascii字符
} } } }
return ret.toString();
}
至此,PDU数据解析完成。
对接收PDU数据解码总结如下:
分段 含义 说明
SC地址信息的长度n, n个八位字节(包括91) (长度占1字节)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),SMSC地址要补‘F’凑成偶数个 (n字节)
基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址,回复地址数字个数m, 共m个十进制数(不包括91和‘F’) (x字节)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),回复地址(TP-RA),补‘F’凑成偶数个 ((m+1)/2 字节)
协议标识(TP-PID), 00是普通GSM类型,点到点方式 (1字节)
用户信息编码方式(TP-DCS) ,08是UCS2编码 (1字节)
时间戳(TP-SCTS) 80是 +8时区 (7字节)
用户信息长度(TP-UDL) UCS2编码是字节数,7BIT编码 是字符个数 (长度占1字节)
用户信息(TP-UD) (y字节)
举例:
分段 含义 说明
08 地址信息的长度 个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址
0D 回复地址数字个数 共13个十进制数(不包括91和‘F’)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 回复地址(TP-RA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
08 用户信息编码方式(TP-DCS) UCS2编码
30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8时区
06 用户信息长度(TP-UDL) 实际长度6个字节
4F 60 59 7D 00 21 用户信息(TP-UD) “你好!”
6.14 PDU解析过程
SmsMessage.java,通过createFromPdu开始解析PDU数据。
public static SmsMessage createFromPdu(byte[] pdu) {
try {
SmsMessage msg = new SmsMessage();
msg.parsePdu(pdu);
return msg;
} catch (RuntimeException ex) {
Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
}
依据协议TS 23.040 9.2.3.9,parsePdu 先解析出SCA,再根据mti消息类型决定使用哪个解析函数,对于接收的sms,使用parseSmsDeliver。
parseSmsDeliver 解析出OA(2字节+号码个数并补偶),PID(1B),dcs(1B),scts(7B),并根据第一个字节判断是否有UDH,最后调用parseUserData。
private void parseSmsDeliver(PduParser p, int firstByte) {
replyPathPresent = (firstByte & 0x80) == 0x80;
originatingAddress = p.getAddress();
if (originatingAddress != null) {
if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
+ originatingAddress.address);
}
protocolIdentifier = p.getByte();
// TP-Data-Coding-Scheme
// see TS 23.038
dataCodingScheme = p.getByte();
scTimeMillis = p.getSCTimestampMillis();
if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
parseUserData(p, hasUserDataHeader);
}
parseUserData,先根据协议3GPP TS 23.038 V7.0.0,将DCS分类,解析消息属性。再使用constructUserData创建一个用户实例,分解出用户数据userData和用户数据头userDataHeader,
int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
if (hasUserDataHeader) {
System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
userDataHeader = SmsHeader.fromByteArray(udh);
}
…
userData = new byte[bufferLen];
System.arraycopy(pdu, offset, userData, 0, userData.length);
}
对于用户数据头userDataHeader,
SmsHeader fromByteArray会根据协议TS 23.040 9.2.3.24来解析用户数据头信息UDH,先是头信息总长度UDHL,再是具体的数据,每一段的数据格式为:类型ID(1B)+该类型后续数据长度(1B)+数据(x B,决定于长度),有多少个类型就由多少倍上面的数据长度,示例数据:04 01020101,UDHL 4字节,有一条ID=01的记录,其数据长2字节,数据是0101。
如果文件头带R8编码,SmsHeader fromByteArray里会进行相应解析,并将IEI数据信息存放在languageShiftTable和languageTable里面。
public static SmsHeader fromByteArray(byte[] data) {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
SmsHeader smsHeader = new SmsHeader();
while (inStream.available() > 0) {
int id = inStream.read(); \\读出IEI类型
int length = inStream.read();\\读出IEI长度
switch (id) {
case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT: \\使用转换表
smsHeader.languageShiftTable = inStream.read();\\读出IEI数据
break;
case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
smsHeader.languageTable = inStream.read();\\读出IEI数据
break;
}
return smsHeader;
}
对于用户数据userData,
以ENCODING_7BIT编码方式来分析,
case ENCODING_7BIT:
messageBody = p.getUserDataGSM7Bit(count, //字节个数
hasUserDataHeader ? userDataHeader.languageTable : 0, //是否使用编码表
hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
break;
gsm7BitPackedToString会完成将PDU里面的数据解压成字符串的过程,存放在PDU里的数据是经过7bit压缩过的转义字符串,不是我们正常阅读的字符串。解压的时候会参考languageShiftTable和languageTable来选择相应的字符表。
public static String gsm7BitPackedToString(byte[] pdu, int offset,
int lengthSeptets, int numPaddingBits, int languageTable, int shiftTable) {
StringBuilder ret = new StringBuilder(lengthSeptets);
try {
boolean prevCharWasEscape = false;
String languageTableToChar = sLanguageTables[languageTable];//选择基本表
String shiftTableToChar = sLanguageShiftTables[shiftTable];//选择扩展表
for (int i = 0 ; i < lengthSeptets ; i++) {
int bitOffset = (7 * i) + numPaddingBits;
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
int gsmVal;
gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
// if it crosses a byte boundary
if (shift > 1) {
// set msb bits to 0
gsmVal &= 0x7f >> (shift - 1);
gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
}//对gsmVal的处理是依次解析出进行过7BIT压缩的每个字符,这些字符目前还是转义过的字符,不是我们通过字符串能正常看到的字符
if (prevCharWasEscape) {//前一个字符是转义字符
if (gsmVal == GSM_EXTENDED_ESCAPE) {
ret.append(' '); // display ' ' for reserved double escape sequence 连续2个转义字符表示一个空格
} else {
char c = shiftTableToChar.charAt(gsmVal);
if (c == ' ') { // 转义字符后字符应该在shift表里查找,如果shift表里在存放的是空格(见表定义,shift表里没有定义的字符都是空格),则使用基本语言表的字符
ret.append(languageTableToChar.charAt(gsmVal));
} else {
ret.append(c); // 转义字符后字符应该在shift表里查找
}
}
prevCharWasEscape = false;
} else if (gsmVal == GSM_EXTENDED_ESCAPE) {
prevCharWasEscape = true; //当前字符是转义字符,存起来给下一个字符使用
} else {
ret.append(languageTableToChar.charAt(gsmVal));//正常时,找到语言表里的ascii字符
} } } }
return ret.toString();
}
至此,PDU数据解析完成。
对接收PDU数据解码总结如下:
分段 含义 说明
SC地址信息的长度n, n个八位字节(包括91) (长度占1字节)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),SMSC地址要补‘F’凑成偶数个 (n字节)
基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址,回复地址数字个数m, 共m个十进制数(不包括91和‘F’) (x字节)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),回复地址(TP-RA),补‘F’凑成偶数个 ((m+1)/2 字节)
协议标识(TP-PID), 00是普通GSM类型,点到点方式 (1字节)
用户信息编码方式(TP-DCS) ,08是UCS2编码 (1字节)
时间戳(TP-SCTS) 80是 +8时区 (7字节)
用户信息长度(TP-UDL) UCS2编码是字节数,7BIT编码 是字符个数 (长度占1字节)
用户信息(TP-UD) (y字节)
举例:
分段 含义 说明
08 地址信息的长度 个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址
0D 回复地址数字个数 共13个十进制数(不包括91和‘F’)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 回复地址(TP-RA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
08 用户信息编码方式(TP-DCS) UCS2编码
30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8时区
06 用户信息长度(TP-UDL) 实际长度6个字节
4F 60 59 7D 00 21 用户信息(TP-UD) “你好!”
6.14 PDU解析过程
SmsMessage.java,通过createFromPdu开始解析PDU数据。
public static SmsMessage createFromPdu(byte[] pdu) {
try {
SmsMessage msg = new SmsMessage();
msg.parsePdu(pdu);
return msg;
} catch (RuntimeException ex) {
Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
}
依据协议TS 23.040 9.2.3.9,parsePdu 先解析出SCA,再根据mti消息类型决定使用哪个解析函数,对于接收的sms,使用parseSmsDeliver。
parseSmsDeliver 解析出OA(2字节+号码个数并补偶),PID(1B),dcs(1B),scts(7B),并根据第一个字节判断是否有UDH,最后调用parseUserData。
private void parseSmsDeliver(PduParser p, int firstByte) {
replyPathPresent = (firstByte & 0x80) == 0x80;
originatingAddress = p.getAddress();
if (originatingAddress != null) {
if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
+ originatingAddress.address);
}
protocolIdentifier = p.getByte();
// TP-Data-Coding-Scheme
// see TS 23.038
dataCodingScheme = p.getByte();
scTimeMillis = p.getSCTimestampMillis();
if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
parseUserData(p, hasUserDataHeader);
}
parseUserData,先根据协议3GPP TS 23.038 V7.0.0,将DCS分类,解析消息属性。再使用constructUserData创建一个用户实例,分解出用户数据userData和用户数据头userDataHeader,
int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
if (hasUserDataHeader) {
System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
userDataHeader = SmsHeader.fromByteArray(udh);
}
…
userData = new byte[bufferLen];
System.arraycopy(pdu, offset, userData, 0, userData.length);
}
对于用户数据头userDataHeader,
SmsHeader fromByteArray会根据协议TS 23.040 9.2.3.24来解析用户数据头信息UDH,先是头信息总长度UDHL,再是具体的数据,每一段的数据格式为:类型ID(1B)+该类型后续数据长度(1B)+数据(x B,决定于长度),有多少个类型就由多少倍上面的数据长度,示例数据:04 01020101,UDHL 4字节,有一条ID=01的记录,其数据长2字节,数据是0101。
如果文件头带R8编码,SmsHeader fromByteArray里会进行相应解析,并将IEI数据信息存放在languageShiftTable和languageTable里面。
public static SmsHeader fromByteArray(byte[] data) {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
SmsHeader smsHeader = new SmsHeader();
while (inStream.available() > 0) {
int id = inStream.read(); \\读出IEI类型
int length = inStream.read();\\读出IEI长度
switch (id) {
case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT: \\使用转换表
smsHeader.languageShiftTable = inStream.read();\\读出IEI数据
break;
case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
smsHeader.languageTable = inStream.read();\\读出IEI数据
break;
}
return smsHeader;
}
对于用户数据userData,
以ENCODING_7BIT编码方式来分析,
case ENCODING_7BIT:
messageBody = p.getUserDataGSM7Bit(count, //字节个数
hasUserDataHeader ? userDataHeader.languageTable : 0, //是否使用编码表
hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
break;
gsm7BitPackedToString会完成将PDU里面的数据解压成字符串的过程,存放在PDU里的数据是经过7bit压缩过的转义字符串,不是我们正常阅读的字符串。解压的时候会参考languageShiftTable和languageTable来选择相应的字符表。
public static String gsm7BitPackedToString(byte[] pdu, int offset,
int lengthSeptets, int numPaddingBits, int languageTable, int shiftTable) {
StringBuilder ret = new StringBuilder(lengthSeptets);
try {
boolean prevCharWasEscape = false;
String languageTableToChar = sLanguageTables[languageTable];//选择基本表
String shiftTableToChar = sLanguageShiftTables[shiftTable];//选择扩展表
for (int i = 0 ; i < lengthSeptets ; i++) {
int bitOffset = (7 * i) + numPaddingBits;
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
int gsmVal;
gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
// if it crosses a byte boundary
if (shift > 1) {
// set msb bits to 0
gsmVal &= 0x7f >> (shift - 1);
gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
}//对gsmVal的处理是依次解析出进行过7BIT压缩的每个字符,这些字符目前还是转义过的字符,不是我们通过字符串能正常看到的字符
if (prevCharWasEscape) {//前一个字符是转义字符
if (gsmVal == GSM_EXTENDED_ESCAPE) {
ret.append(' '); // display ' ' for reserved double escape sequence 连续2个转义字符表示一个空格
} else {
char c = shiftTableToChar.charAt(gsmVal);
if (c == ' ') { // 转义字符后字符应该在shift表里查找,如果shift表里在存放的是空格(见表定义,shift表里没有定义的字符都是空格),则使用基本语言表的字符
ret.append(languageTableToChar.charAt(gsmVal));
} else {
ret.append(c); // 转义字符后字符应该在shift表里查找
}
}
prevCharWasEscape = false;
} else if (gsmVal == GSM_EXTENDED_ESCAPE) {
prevCharWasEscape = true; //当前字符是转义字符,存起来给下一个字符使用
} else {
ret.append(languageTableToChar.charAt(gsmVal));//正常时,找到语言表里的ascii字符
} } } }
return ret.toString();
}
至此,PDU数据解析完成。
对接收PDU数据解码总结如下:
分段 含义 说明
SC地址信息的长度n, n个八位字节(包括91) (长度占1字节)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),SMSC地址要补‘F’凑成偶数个 (n字节)
基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址,回复地址数字个数m, 共m个十进制数(不包括91和‘F’) (x字节)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),回复地址(TP-RA),补‘F’凑成偶数个 ((m+1)/2 字节)
协议标识(TP-PID), 00是普通GSM类型,点到点方式 (1字节)
用户信息编码方式(TP-DCS) ,08是UCS2编码 (1字节)
时间戳(TP-SCTS) 80是 +8时区 (7字节)
用户信息长度(TP-UDL) UCS2编码是字节数,7BIT编码 是字符个数 (长度占1字节)
用户信息(TP-UD) (y字节)
举例:
分段 含义 说明
08 地址信息的长度 个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址
0D 回复地址数字个数 共13个十进制数(不包括91和‘F’)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 回复地址(TP-RA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
08 用户信息编码方式(TP-DCS) UCS2编码
30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8时区
06 用户信息长度(TP-UDL) 实际长度6个字节
4F 60 59 7D 00 21 用户信息(TP-UD) “你好!”
6.14 PDU解析过程
SmsMessage.java,通过createFromPdu开始解析PDU数据。
public static SmsMessage createFromPdu(byte[] pdu) {
try {
SmsMessage msg = new SmsMessage();
msg.parsePdu(pdu);
return msg;
} catch (RuntimeException ex) {
Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
}
依据协议TS 23.040 9.2.3.9,parsePdu 先解析出SCA,再根据mti消息类型决定使用哪个解析函数,对于接收的sms,使用parseSmsDeliver。
parseSmsDeliver 解析出OA(2字节+号码个数并补偶),PID(1B),dcs(1B),scts(7B),并根据第一个字节判断是否有UDH,最后调用parseUserData。
private void parseSmsDeliver(PduParser p, int firstByte) {
replyPathPresent = (firstByte & 0x80) == 0x80;
originatingAddress = p.getAddress();
if (originatingAddress != null) {
if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
+ originatingAddress.address);
}
protocolIdentifier = p.getByte();
// TP-Data-Coding-Scheme
// see TS 23.038
dataCodingScheme = p.getByte();
scTimeMillis = p.getSCTimestampMillis();
if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
parseUserData(p, hasUserDataHeader);
}
parseUserData,先根据协议3GPP TS 23.038 V7.0.0,将DCS分类,解析消息属性。再使用constructUserData创建一个用户实例,分解出用户数据userData和用户数据头userDataHeader,
int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
if (hasUserDataHeader) {
System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
userDataHeader = SmsHeader.fromByteArray(udh);
}
…
userData = new byte[bufferLen];
System.arraycopy(pdu, offset, userData, 0, userData.length);
}
对于用户数据头userDataHeader,
SmsHeader fromByteArray会根据协议TS 23.040 9.2.3.24来解析用户数据头信息UDH,先是头信息总长度UDHL,再是具体的数据,每一段的数据格式为:类型ID(1B)+该类型后续数据长度(1B)+数据(x B,决定于长度),有多少个类型就由多少倍上面的数据长度,示例数据:04 01020101,UDHL 4字节,有一条ID=01的记录,其数据长2字节,数据是0101。
如果文件头带R8编码,SmsHeader fromByteArray里会进行相应解析,并将IEI数据信息存放在languageShiftTable和languageTable里面。
public static SmsHeader fromByteArray(byte[] data) {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
SmsHeader smsHeader = new SmsHeader();
while (inStream.available() > 0) {
int id = inStream.read(); \\读出IEI类型
int length = inStream.read();\\读出IEI长度
switch (id) {
case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT: \\使用转换表
smsHeader.languageShiftTable = inStream.read();\\读出IEI数据
break;
case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
smsHeader.languageTable = inStream.read();\\读出IEI数据
break;
}
return smsHeader;
}
对于用户数据userData,
以ENCODING_7BIT编码方式来分析,
case ENCODING_7BIT:
messageBody = p.getUserDataGSM7Bit(count, //字节个数
hasUserDataHeader ? userDataHeader.languageTable : 0, //是否使用编码表
hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
break;
gsm7BitPackedToString会完成将PDU里面的数据解压成字符串的过程,存放在PDU里的数据是经过7bit压缩过的转义字符串,不是我们正常阅读的字符串。解压的时候会参考languageShiftTable和languageTable来选择相应的字符表。
public static String gsm7BitPackedToString(byte[] pdu, int offset,
int lengthSeptets, int numPaddingBits, int languageTable, int shiftTable) {
StringBuilder ret = new StringBuilder(lengthSeptets);
try {
boolean prevCharWasEscape = false;
String languageTableToChar = sLanguageTables[languageTable];//选择基本表
String shiftTableToChar = sLanguageShiftTables[shiftTable];//选择扩展表
for (int i = 0 ; i < lengthSeptets ; i++) {
int bitOffset = (7 * i) + numPaddingBits;
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
int gsmVal;
gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
// if it crosses a byte boundary
if (shift > 1) {
// set msb bits to 0
gsmVal &= 0x7f >> (shift - 1);
gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
}//对gsmVal的处理是依次解析出进行过7BIT压缩的每个字符,这些字符目前还是转义过的字符,不是我们通过字符串能正常看到的字符
if (prevCharWasEscape) {//前一个字符是转义字符
if (gsmVal == GSM_EXTENDED_ESCAPE) {
ret.append(' '); // display ' ' for reserved double escape sequence 连续2个转义字符表示一个空格
} else {
char c = shiftTableToChar.charAt(gsmVal);
if (c == ' ') { // 转义字符后字符应该在shift表里查找,如果shift表里在存放的是空格(见表定义,shift表里没有定义的字符都是空格),则使用基本语言表的字符
ret.append(languageTableToChar.charAt(gsmVal));
} else {
ret.append(c); // 转义字符后字符应该在shift表里查找
}
}
prevCharWasEscape = false;
} else if (gsmVal == GSM_EXTENDED_ESCAPE) {
prevCharWasEscape = true; //当前字符是转义字符,存起来给下一个字符使用
} else {
ret.append(languageTableToChar.charAt(gsmVal));//正常时,找到语言表里的ascii字符
} } } }
return ret.toString();
}
至此,PDU数据解析完成。
对接收PDU数据解码总结如下:
分段 含义 说明
SC地址信息的长度n, n个八位字节(包括91) (长度占1字节)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),SMSC地址要补‘F’凑成偶数个 (n字节)
基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址,回复地址数字个数m, 共m个十进制数(不包括91和‘F’) (x字节)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),回复地址(TP-RA),补‘F’凑成偶数个 ((m+1)/2 字节)
协议标识(TP-PID), 00是普通GSM类型,点到点方式 (1字节)
用户信息编码方式(TP-DCS) ,08是UCS2编码 (1字节)
时间戳(TP-SCTS) 80是 +8时区 (7字节)
用户信息长度(TP-UDL) UCS2编码是字节数,7BIT编码 是字符个数 (长度占1字节)
用户信息(TP-UD) (y字节)
举例:
分段 含义 说明
08 地址信息的长度 个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址
0D 回复地址数字个数 共13个十进制数(不包括91和‘F’)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 回复地址(TP-RA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
08 用户信息编码方式(TP-DCS) UCS2编码
30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8时区
06 用户信息长度(TP-UDL) 实际长度6个字节
4F 60 59 7D 00 21 用户信息(TP-UD) “你好!”
6.14 PDU解析过程
SmsMessage.java,通过createFromPdu开始解析PDU数据。
public static SmsMessage createFromPdu(byte[] pdu) {
try {
SmsMessage msg = new SmsMessage();
msg.parsePdu(pdu);
return msg;
} catch (RuntimeException ex) {
Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
}
依据协议TS 23.040 9.2.3.9,parsePdu 先解析出SCA,再根据mti消息类型决定使用哪个解析函数,对于接收的sms,使用parseSmsDeliver。
parseSmsDeliver 解析出OA(2字节+号码个数并补偶),PID(1B),dcs(1B),scts(7B),并根据第一个字节判断是否有UDH,最后调用parseUserData。
private void parseSmsDeliver(PduParser p, int firstByte) {
replyPathPresent = (firstByte & 0x80) == 0x80;
originatingAddress = p.getAddress();
if (originatingAddress != null) {
if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
+ originatingAddress.address);
}
protocolIdentifier = p.getByte();
// TP-Data-Coding-Scheme
// see TS 23.038
dataCodingScheme = p.getByte();
scTimeMillis = p.getSCTimestampMillis();
if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
parseUserData(p, hasUserDataHeader);
}
parseUserData,先根据协议3GPP TS 23.038 V7.0.0,将DCS分类,解析消息属性。再使用constructUserData创建一个用户实例,分解出用户数据userData和用户数据头userDataHeader,
int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
if (hasUserDataHeader) {
System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
userDataHeader = SmsHeader.fromByteArray(udh);
}
…
userData = new byte[bufferLen];
System.arraycopy(pdu, offset, userData, 0, userData.length);
}
对于用户数据头userDataHeader,
SmsHeader fromByteArray会根据协议TS 23.040 9.2.3.24来解析用户数据头信息UDH,先是头信息总长度UDHL,再是具体的数据,每一段的数据格式为:类型ID(1B)+该类型后续数据长度(1B)+数据(x B,决定于长度),有多少个类型就由多少倍上面的数据长度,示例数据:04 01020101,UDHL 4字节,有一条ID=01的记录,其数据长2字节,数据是0101。
如果文件头带R8编码,SmsHeader fromByteArray里会进行相应解析,并将IEI数据信息存放在languageShiftTable和languageTable里面。
public static SmsHeader fromByteArray(byte[] data) {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
SmsHeader smsHeader = new SmsHeader();
while (inStream.available() > 0) {
int id = inStream.read(); \\读出IEI类型
int length = inStream.read();\\读出IEI长度
switch (id) {
case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT: \\使用转换表
smsHeader.languageShiftTable = inStream.read();\\读出IEI数据
break;
case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
smsHeader.languageTable = inStream.read();\\读出IEI数据
break;
}
return smsHeader;
}
对于用户数据userData,
以ENCODING_7BIT编码方式来分析,
case ENCODING_7BIT:
messageBody = p.getUserDataGSM7Bit(count, //字节个数
hasUserDataHeader ? userDataHeader.languageTable : 0, //是否使用编码表
hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
break;
gsm7BitPackedToString会完成将PDU里面的数据解压成字符串的过程,存放在PDU里的数据是经过7bit压缩过的转义字符串,不是我们正常阅读的字符串。解压的时候会参考languageShiftTable和languageTable来选择相应的字符表。
public static String gsm7BitPackedToString(byte[] pdu, int offset,
int lengthSeptets, int numPaddingBits, int languageTable, int shiftTable) {
StringBuilder ret = new StringBuilder(lengthSeptets);
try {
boolean prevCharWasEscape = false;
String languageTableToChar = sLanguageTables[languageTable];//选择基本表
String shiftTableToChar = sLanguageShiftTables[shiftTable];//选择扩展表
for (int i = 0 ; i < lengthSeptets ; i++) {
int bitOffset = (7 * i) + numPaddingBits;
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
int gsmVal;
gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
// if it crosses a byte boundary
if (shift > 1) {
// set msb bits to 0
gsmVal &= 0x7f >> (shift - 1);
gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
}//对gsmVal的处理是依次解析出进行过7BIT压缩的每个字符,这些字符目前还是转义过的字符,不是我们通过字符串能正常看到的字符
if (prevCharWasEscape) {//前一个字符是转义字符
if (gsmVal == GSM_EXTENDED_ESCAPE) {
ret.append(' '); // display ' ' for reserved double escape sequence 连续2个转义字符表示一个空格
} else {
char c = shiftTableToChar.charAt(gsmVal);
if (c == ' ') { // 转义字符后字符应该在shift表里查找,如果shift表里在存放的是空格(见表定义,shift表里没有定义的字符都是空格),则使用基本语言表的字符
ret.append(languageTableToChar.charAt(gsmVal));
} else {
ret.append(c); // 转义字符后字符应该在shift表里查找
}
}
prevCharWasEscape = false;
} else if (gsmVal == GSM_EXTENDED_ESCAPE) {
prevCharWasEscape = true; //当前字符是转义字符,存起来给下一个字符使用
} else {
ret.append(languageTableToChar.charAt(gsmVal));//正常时,找到语言表里的ascii字符
} } } }
return ret.toString();
}
至此,PDU数据解析完成。
对接收PDU数据解码总结如下:
分段 含义 说明
SC地址信息的长度n, n个八位字节(包括91) (长度占1字节)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),SMSC地址要补‘F’凑成偶数个 (n字节)
基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址,回复地址数字个数m, 共m个十进制数(不包括91和‘F’) (x字节)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’),回复地址(TP-RA),补‘F’凑成偶数个 ((m+1)/2 字节)
协议标识(TP-PID), 00是普通GSM类型,点到点方式 (1字节)
用户信息编码方式(TP-DCS) ,08是UCS2编码 (1字节)
时间戳(TP-SCTS) 80是 +8时区 (7字节)
用户信息长度(TP-UDL) UCS2编码是字节数,7BIT编码 是字符个数 (长度占1字节)
用户信息(TP-UD) (y字节)
举例:
分段 含义 说明
08 地址信息的长度 个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址
0D 回复地址数字个数 共13个十进制数(不包括91和‘F’)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 回复地址(TP-RA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
08 用户信息编码方式(TP-DCS) UCS2编码
30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8时区
06 用户信息长度(TP-UDL) 实际长度6个字节
4F 60 59 7D 00 21 用户信息(TP-UD) “你好!”