Android短彩信源码解析-短信发送流程(三)

时间:2021-12-08 08:55:06

3、短信pdu的压缩与封装

相关文章:

-------------------------------------------------------------

1、短信发送上层逻辑

2、短信发送framework层逻辑

-------------------------------------------------------------

一直很逃避对这一部分做一个总结。因为这部分有些超出我能力范围之外,关于段彩信的3GPP协议,也看过一些,但总归觉得自己并非专业。对协议的学习仅仅停留在使用层面。刚开始了解这部分的时候,下过许多功夫,也走了不少弯路,为了截取pdu数据包,给10086/10010发了无数的短信(截取pdu数据log包需要发短信,给他们发短信测试不花钱,虽然通信费公司报销一部分,为公司省钱的娃),最后对pdu使用豁然开朗还是在看了网上一位大神写的帖子之后。(在文章之后会贴出来)

ps:强烈建议大家做几遍pdu的压缩与解析,对学习pdu的使用有很大帮助。大家可以参照文章最后贴出来的大神的解析过程,结合代码进行分析。

 

以下是从代码实现的角度来总结一下,为那些“挣扎”在pdu问题的娃抛个砖,引个路。如有错误,敬请批评指正,不胜感激。

ps:每条短信可容纳多少字符啊?

答:看代码。

SmsSingleReceipientSender.java

if ((MmsConfig.getEmailGateway() != null) &&
(Mms.isEmailAddress(mDest) || MessageUtils.isAlias(mDest))) {
String msgText;
msgText = mDest + " " + mMessageText;
mDest = MmsConfig.getEmailGateway();
messages = smsManager.divideMessage(msgText);
} else {
messages = smsManager.divideMessage(mMessageText);
// remove spaces from destination number (e.g. "801 555 1212" -> "8015551212")
mDest = mDest.replaceAll(" ", "");
mDest = Conversation.verifySingleRecipient(mContext, mThreadId, mDest);
}

最终调用:

SmsMessage.java

会执行:fragmentText()

TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ?
com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false) :
com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false);

往下追一下代码就可以找到答案。这里附上几个参考值:

 /** The maximum number of payload bytes per message */
public static final int MAX_USER_DATA_BYTES = 140;

/**
* The maximum number of payload bytes per message if a user data header
* is present. This assumes the header only contains the
* CONCATENATED_8_BIT_REFERENCE element.
*/
public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134;

/** The maximum number of payload septets per message */
public static final int MAX_USER_DATA_SEPTETS = 160;

/**
* The maximum number of payload septets per message if a user data header
* is present. This assumes the header only contains the
* CONCATENATED_8_BIT_REFERENCE element.
*/
public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;


 

下面进入正题:

1、在我们调用SmsDispatcher.java进行发送短信,会执行sendText方法,具体的方法实现,在子类中:

GsmSmsDispatcher.java

protected void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, text, (deliveryIntent != null));
if (pdu != null) {
sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
} else {
Log.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
}
}

CdmaSmsDispatcher.java

protected void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, text, (deliveryIntent != null), null);
sendSubmitPdu(pdu, sentIntent, deliveryIntent);
}


Gsm与Cdma大体的流程是类似的,都为:构建pdu对象、进入信息头部标识、编码收件人地址(短信中心号码等)、编码信息内容、编码信息时间内容等。

具体的差别存在于编码信息内容,Gsm采用7bit,CMDA可以说是采用4bit。

我们以CDMA为例:

2、com.android.internal.telephony.cdma.SmsMessage.java
getSubmitPdu()方法:

public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
boolean statusReportRequested, SmsHeader smsHeader) {

/**
* TODO(cleanup): Do we really want silent failure like this?
* Would it not be much more reasonable to make sure we don't
* call this function if we really want nothing done?
*/
if (message == null || destAddr == null) {
return null;
}

UserData uData = new UserData();
uData.payloadStr = message;
uData.userDataHeader = smsHeader;
return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
}

在这个方法中,new了一个UserData对象,其中信息内容message放在了payloadStr变量中,smsHeader放在了userDataHeader中。这里smsHeader可忽略。

我们继续看一下privateGetSubmitPdu方法:

3、privateGetSubmitPdu:

private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
UserData userData) {

/**
* TODO(cleanup): give this function a more meaningful name.
*/

/**
* TODO(cleanup): Make returning null from the getSubmitPdu
* variations meaningful -- clean up the error feedback
* mechanism, and avoid null pointer exceptions.
*/

/**
* North America Plus Code :
* Convert + code to 011 and dial out for international SMS
*/
CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
PhoneNumberUtils.cdmaCheckAndProcessPlusCode(destAddrStr));
if (destAddr == null) return null;

BearerData bearerData = new BearerData();
bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;

bearerData.messageId = getNextMessageId();

bearerData.deliveryAckReq = statusReportRequested;
bearerData.userAckReq = false;
bearerData.readAckReq = false;
bearerData.reportReq = false;

bearerData.userData = userData;

byte[] encodedBearerData = BearerData.encode(bearerData);
if (Log.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
Log.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
Log.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
}
if (encodedBearerData == null) return null;

int teleservice = bearerData.hasUserDataHeader ?
SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;

SmsEnvelope envelope = new SmsEnvelope();
envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
envelope.teleService = teleservice;
envelope.destAddress = destAddr;
envelope.bearerReply = RETURN_ACK;
envelope.bearerData = encodedBearerData;

/**
* TODO(cleanup): envelope looks to be a pointless class, get
* rid of it. Also -- most of the envelope fields set here
* are ignored, why?
*/

try {
/**
* TODO(cleanup): reference a spec and get rid of the ugly comments
*/
ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(envelope.teleService);
dos.writeInt(0); //servicePresent
dos.writeInt(0); //serviceCategory
dos.write(destAddr.digitMode);
dos.write(destAddr.numberMode);
dos.write(destAddr.ton); // number_type
dos.write(destAddr.numberPlan);
dos.write(destAddr.numberOfDigits);
dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
// Subaddress is not supported.
dos.write(0); //subaddressType
dos.write(0); //subaddr_odd
dos.write(0); //subaddr_nbr_of_digits
dos.write(encodedBearerData.length);
dos.write(encodedBearerData, 0, encodedBearerData.length);
dos.close();

SubmitPdu pdu = new SubmitPdu();
pdu.encodedMessage = baos.toByteArray();
pdu.encodedScAddress = null;
return pdu;
} catch(IOException ex) {
Log.e(LOG_TAG, "creating SubmitPdu failed: " + ex);
}
return null;
}

3.1、在这个方法中,首先进行了信息Address的编码

public static CdmaSmsAddress parse(String address) {
CdmaSmsAddress addr = new CdmaSmsAddress();
addr.address = address;
addr.ton = CdmaSmsAddress.TON_UNKNOWN;
byte[] origBytes = null;
String filteredAddr = filterNumericSugar(address);
if (filteredAddr != null) {
origBytes = parseToDtmf(filteredAddr);
}
if (origBytes != null) {
addr.digitMode = DIGIT_MODE_4BIT_DTMF;
addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
if (address.indexOf('+') != -1) {
addr.ton = TON_INTERNATIONAL_OR_IP;
}
} else {
filteredAddr = filterWhitespace(address);
origBytes = UserData.stringToAscii(filteredAddr);
if (origBytes == null) {
return null;
}
addr.digitMode = DIGIT_MODE_8BIT_CHAR;
addr.numberMode = NUMBER_MODE_DATA_NETWORK;
if (address.indexOf('@') != -1) {
addr.ton = TON_NATIONAL_OR_EMAIL;
}
}
addr.origBytes = origBytes;
addr.numberOfDigits = origBytes.length;
return addr;
}

在这个方法内会依据号码格式做一下调整,比如头部含有“+”号怎么处理,然后调用4bit或者8bit压缩电话号码,具体流程以及压缩过程感兴趣的可以打log进行追一下。

3.2、接下来初始化一个BearerData对象,并执行encode进行编码,此处主要是对信息内容进行编码。

BearerData bearerData = new BearerData();
bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;

bearerData.messageId = getNextMessageId();

bearerData.deliveryAckReq = statusReportRequested;
bearerData.userAckReq = false;
bearerData.readAckReq = false;
bearerData.reportReq = false;

信息的MessageType,MessageId等信息加入到bearerData对象中。

bearerData.userData = userData;

将userData对象赋给bearerData对象的userData变量中,我们找到bearerData对象内部包含了信息的内容msg信息。
下面调用encode进行编码

public static byte[] encode(BearerData bData) {
bData.hasUserDataHeader = ((bData.userData != null) &&
(bData.userData.userDataHeader != null));
try {
BitwiseOutputStream outStream = new BitwiseOutputStream(200);
outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
encodeMessageId(bData, outStream);
if (bData.userData != null) {
outStream.write(8, SUBPARAM_USER_DATA);
encodeUserData(bData, outStream);//编码信息内容
}
if (bData.callbackNumber != null) {
outStream.write(8, SUBPARAM_CALLBACK_NUMBER);
encodeCallbackNumber(bData, outStream);
}
if (bData.userAckReq || bData.deliveryAckReq || bData.readAckReq || bData.reportReq) {
outStream.write(8, SUBPARAM_REPLY_OPTION);
encodeReplyOption(bData, outStream);//编码ackReq(信息确认相关),deliveryAckReq(发送报告相关),readAckReq(读取报告相关)等
}
if (bData.numberOfMessages != 0) {
outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES);
encodeMsgCount(bData, outStream);//编码信息数目
}
if (bData.validityPeriodRelativeSet) {
outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE);
encodeValidityPeriodRel(bData, outStream);
}
if (bData.privacyIndicatorSet) {
outStream.write(8, SUBPARAM_PRIVACY_INDICATOR);
encodePrivacyIndicator(bData, outStream);
}
if (bData.languageIndicatorSet) {
outStream.write(8, SUBPARAM_LANGUAGE_INDICATOR);
encodeLanguageIndicator(bData, outStream);//编码语言
}
if (bData.displayModeSet) {
outStream.write(8, SUBPARAM_MESSAGE_DISPLAY_MODE);
encodeDisplayMode(bData, outStream);//编码显示模式
}
if (bData.priorityIndicatorSet) {
outStream.write(8, SUBPARAM_PRIORITY_INDICATOR);
encodePriorityIndicator(bData, outStream);
}
if (bData.alertIndicatorSet) {
outStream.write(8, SUBPARAM_ALERT_ON_MESSAGE_DELIVERY);
encodeMsgDeliveryAlert(bData, outStream);
}
if (bData.messageStatusSet) {
outStream.write(8, SUBPARAM_MESSAGE_STATUS);
encodeMsgStatus(bData, outStream);//编码信息状态
}
return outStream.toByteArray();
} catch (BitwiseOutputStream.AccessException ex) {
Log.e(LOG_TAG, "BearerData encode failed: " + ex);
} catch (CodingException ex) {
Log.e(LOG_TAG, "BearerData encode failed: " + ex);
}
return null;
}

首先判断信息是否包含userDataHeader,接着初始化一个BitwiseOutputStream对象,注意:此处已经开始将信息内容写入byte数组了,只不过信息内容变成了byte数组,还没有写入pdu对象中。

关于信息内容的编码:

private static void encodeUserDataPayload(UserData uData)
throws CodingException
{
if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) {
Log.e(LOG_TAG, "user data with null payloadStr");
uData.payloadStr = "";
}

if (uData.userDataHeader != null) {
encodeEmsUserDataPayload(uData);
return;
}

if (uData.msgEncodingSet) {
if (uData.msgEncoding == UserData.ENCODING_OCTET) {
if (uData.payload == null) {
Log.e(LOG_TAG, "user data with octet encoding but null payload");
uData.payload = new byte[0];
uData.numFields = 0;
} else {
uData.numFields = uData.payload.length;
}
} else {
if (uData.payloadStr == null) {
Log.e(LOG_TAG, "non-octet user data with null payloadStr");
uData.payloadStr = "";
}
if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true);
uData.payload = gcr.data;
uData.numFields = gcr.septets;
} else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
uData.payload = encode7bitAscii(uData.payloadStr, true);
uData.numFields = uData.payloadStr.length();
} else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
uData.payload = encodeUtf16(uData.payloadStr);
uData.numFields = uData.payloadStr.length();
} else {
throw new CodingException("unsupported user data encoding (" +
uData.msgEncoding + ")");
}
}
} else {
try {
uData.payload = encode7bitAscii(uData.payloadStr, false);
uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
} catch (CodingException ex) {
uData.payload = encodeUtf16(uData.payloadStr);
uData.msgEncoding = UserData.ENCODING_UNICODE_16;
}
uData.numFields = uData.payloadStr.length();
uData.msgEncodingSet = true;
}
}


系统有许多编码方式,像octet,7bit_ascii,7bit_alphabet,16bit等。具体的流程以实际代码为准。

 

总结:此部分编码完成之后会生成一个byte数组

byte[] encodedBearerData = BearerData.encode(bearerData);


这个数组内包含了信息内容以及信息内容相关的一些属性的值。数值编码格式大体如:[类别][长度][内容][类别][长度][内容][类别][长度][内容]

3.3、初始化SmsEnvelope对象,并加入pdu信息头部属性标识

int teleservice = bearerData.hasUserDataHeader ?
SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;

SmsEnvelope envelope = new SmsEnvelope();
envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
envelope.teleService = teleservice;
envelope.destAddress = destAddr;
envelope.bearerReply = RETURN_ACK;
envelope.bearerData = encodedBearerData;

pdu的信息头部包含着:messageType,teleService等信息。我们看到我们之前的destAddr对象,以及encodeBearerData数组也被加入到这个对象中。

3.4、以上可以说是pdu构建的准备阶段,接下来就是pdu数组构建的实施阶段了

/*1、初始化对象*/
ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
DataOutputStream dos = new DataOutputStream(baos);
/*2、写入头部标识属性信息*/
dos.writeInt(envelope.teleService);
dos.writeInt(0); //servicePresent
dos.writeInt(0); //serviceCategory
/*3、写入收件人地址信息*/
dos.write(destAddr.digitMode);
dos.write(destAddr.numberMode);
dos.write(destAddr.ton); // number_type
dos.write(destAddr.numberPlan);
dos.write(destAddr.numberOfDigits);
dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
// Subaddress is not supported.
/*4、写入信息内容正文信息内容*/
dos.write(0); //subaddressType
dos.write(0); //subaddr_odd
dos.write(0); //subaddr_nbr_of_digits
dos.write(encodedBearerData.length);
dos.write(encodedBearerData, 0, encodedBearerData.length);
dos.close();
/*5、初始化pdu对象*/
SubmitPdu pdu = new SubmitPdu();
/*6、将pdu数组内容赋给初始化的pdu的encodedMessage变量中*/
pdu.encodedMessage = baos.toByteArray();
pdu.encodedScAddress = null;

代码比较简单,具体每步大体干什么标在注释上了。

最后将pdu对象返回。

 

4、总结:

cdma的短信pdu压缩过程大体就是这样子了,Gsm短信pdu压缩与之类似。

pdu解码也就是pdu压缩的逆过程。

解析的过程在此就不介绍了,解析的方法也在本类中,parsePdu。

解析短信的入口在RIL.java中,在RIL.java接收到来自moderm的信息上报,将上报的数组(pdu),加入parsePdu方法内,进行解析。

private void parsePdu(byte[] pdu) {
ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
DataInputStream dis = new DataInputStream(bais);
byte length;
int bearerDataLength;
SmsEnvelope env = new SmsEnvelope();
CdmaSmsAddress addr = new CdmaSmsAddress();

try {
env.messageType = dis.readInt();
env.teleService = dis.readInt();
env.serviceCategory = dis.readInt();

addr.digitMode = dis.readByte();
addr.numberMode = dis.readByte();
addr.ton = dis.readByte();
addr.numberPlan = dis.readByte();

length = dis.readByte();
addr.numberOfDigits = length;
addr.origBytes = new byte[length];
dis.read(addr.origBytes, 0, length); // digits

env.bearerReply = dis.readInt();
// CauseCode values:
env.replySeqNo = dis.readByte();
env.errorClass = dis.readByte();
env.causeCode = dis.readByte();

//encoded BearerData:
bearerDataLength = dis.readInt();
env.bearerData = new byte[bearerDataLength];
dis.read(env.bearerData, 0, bearerDataLength);
dis.close();
} catch (Exception ex) {
Log.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex);
}

// link the filled objects to this SMS
originatingAddress = addr;
env.origAddress = addr;
mEnvelope = env;
mPdu = pdu;

parseSms();
}



===================================================================================================================================

 

大神的pdu解析之路:

CDMA SMS pdu解码

Posted on June 28, 2010 by deli去年玩过via cdma模组,刚开始对cdmasms pdu真是一脸茫然,意识到我也可能不会长时间做这个,网上这类帖子少之又少,体会初学者的辛苦,重新翻尸体,记录下来。

 

收到两条短信,现在用AT把它们读取出来。

 

at+cmgr=5

+CMGR:5,REC UNREAD,15338896020,UNKNOWNNUMBER,N/A,176

 

0000021002020702c54ce225a8a80601c0089d00031001e801

8e2230018801780193108b09fb087b317b012b6a080162e38c8e6

3b422e07b65980162b942e872e4b3b4246f7a70500162e54bbf9a7

053f67c7e3801729f544c0b108bb423918a75d00163317a70033b0

ae07ce3e00162b943108bb4236b54158a71680162ff5a7283b423

71c33b2b71c29dd80173108b09fb087b317c1a933cb80162b943659

b6a0bb4227122e5c00306081229192611

 

at+cmgr=21

+CMGR:21,REC UNREAD,15338896020,UNKNOWNNUMBER,N/A,96

 

0000021002020702c54ce225a8a806014c084d00031001f8013

e20f00190017801900162dfca7004b1acb1abb4239614c67001629

63b2b12b9827ae310c001729f544c0b108bb423918a75d00163317

a70029f52e07cf0f80306081229192616

 

[deli@deli example]$ ./test_pdu_decode

0000021002020702c54ce225a8a80601c0089d00031001e8018e22

30018801780193108b09fb087b317b012b6a080162e38c8e63b422

e07b65980162b942e872e4b3b4246f7a70500162e54bbf9a7053f67

c7e3801729f544c0b108bb423918a75d00163317a70033b0ae07ce3

e00162b943108bb4236b54158a71680162ff5a7283b42371c33b2b7

1c29dd80173108b09fb087b317c1a933cb80162b943659b6a0bb422

7122e5c00306081229192611

sms context == 1/2我愿意是急流,山里的小河,在崎岖的路上,岩石上经过.只要我的爱人,是一条小鱼,在我的浪花中,快乐的游来游去.我愿意是荒林,在河流的两岸

SM_ENCODING == 4

TPA == 15338896020

SCTS == 19:26:11 12/29/08

 

[deli@deli example]$ ./test_pdu_decode

0000021002020702c54ce225a8a806014c084d00031001f8013e20f

00190017801900162dfca7004b1acb1abb4239614c6700162963b2

b12b9827ae310c001729f544c0b108bb423918a75d00163317a700

29f52e07cf0f80306081229192616

sms context == 2/2,对一阵阵的狂风,勇敢地作战.只要我的爱人,是一只小鸟

SM_ENCODING == 4

TPA == 15338896020

SCTS == 19:26:16 12/29/08

 

PDU串解析

CDMApdu格式与GSM的相差很多,不能直接用肉眼看出来。第一条内容比较长,就拿第二条pdu串来分析吧。

1.首先将PDU串打成PDU

PDU串相邻的两个ascii字符拼凑成一个8bit数据

 

如下:

00 00 02 10 02 02 07 02 c5 4c e2 25 a8 a806 01 4c 08 4d 00 03 10 01 f8 01 3e 20 f0 01 90 01 78 01 90 01 62 df ca 70 04b1 ac b1 ab b4 23 96 14 c6 70 01 62 96 3b 2b 12 b9 82 7a e3 10 c0 01 72 9f 544c 0b 10 8b b4 23 91 8a 75 d0 01 63 31 7a 70 02 9f 52 e0 7c f0 f8 03 06 08 1229 19 26 16

 

消息传送类型: 0×00 point to point message(表示点对点消息)

 

下面的内容为短消息的各个字段每个字段分为三个部分:字段类型(ID)(8bit)、字段长度(Length)(8bit)和字段内容.

 

第一个字段: 00 02 10 02

0×00,表示uTeleserviceID字段

0×02,字段长度,该长度必须为2,否则为错误的pdu信息

 

字段内容为:0×1002,十进制是4098,

 

第二个字段: 02 07 02 c5 4c e2 25 a8 a8

0×02, SMS_TL_ORIG_ADDR表示 (短信发送地址)

0×07,字段长度为7

字段内容: 02 c5 4c e2 25 a8 a8

只看前面几个 02 c5 4c e2的:

0000 0010 1100 0101 0100 1100 1110 00100010

取第一个bit 0 表示 RIL_CDMA_SMS_DIGIT_MODE_4_BIT 4bit压缩

第二个bit 0 表示 RIL_CDMA_SMS_NUMBER_MODE_NOT_DATA_NETWORK

下来8bit 00 0010 11 = 11,表示号码长度为11

由于是4bit压缩,后面44bit4*11)表示号码,解析出来是15338896020

 

第三个字段: 06 01 4c

表示SMS_TL_BEARER_RPLY_OPT

 

第四个字段:08 4d 00 03 10 01 f8 01 3e 20 f0 01 90 01 78 01 90 01 62 df ca 70 04b1 ac b1 ab b4 23 96 14 c6 70 01 62 96 3b 2b 12 b9 82 7a e3 10 c0 01 72 9f 544c 0b 10 8b b4 23 91 8a 75 d0 01 63 31 7a 70 02 9f 52 e0 7c f0 f8 03 06 08 1229 19 26 16

 

0×08,表示SMS_TL_BEARER_DATA字段(短信内容)

0x4d,字段长度为77

 

这个字段也分成各个子字段:

 

第一子字段: 00 03 10 01 f8

0×00 Mesage Id

0×03内容长度

10 01 f8 === 0001 0000 0000 0001 1111 1000

0001 表示 DELIVER短信

0000 0000 0001 1111表示 messageid.

紧接后面的 1, 表示HEADER_IND

 

第二个子字段:01 3e 20 f0 01 90 01 78 01 90 01 62 df ca 70 04 b1 ac b1 ab b4 23 9614 c6 70 01 62 96 3b 2b 12 b9 82 7a e3 10 c0 01 72 9f 54 4c 0b 10 8b b4 23 918a 75 d0 01 63 31 7a 70 02 9f 52 e0 7c f0 f8

0×01,字段类型

0x3e(62),字段长度

20 f0 01 === 0010 0000 1111 0000 0000 0001

0×20的前5bit00100,0×04,表示短信编码方式为RIL_CDMA_SMS_ENCODING_UNICODE(UNICODE)

0×20的后3bit, 0xf0的前5bit, 000 1111 0,0x1e (30),表示有30UNICODE字符。0xf0的后3bit0×018bit,再加上0×90的前5bit,

 

0000 0000 0011 0010 拼成一个16位数是 0×0032表示字符:2,在vim下了解一个字符的16进制码很简单,光标在该字符,按ga,底端显示结果如下:

 

 

<2> 50,  Hex 32,  Octal 062                                  2,1           All

依次下去,内容就是上面运行./test_pdu_decode的结果: 2/2,对一阵阵的狂风,勇敢地作战.只要我的爱人,是一只小鸟

 

第三个子字段: 03 06 08 12 29 19 26 16

 

这是一个时间戳字段: 081229192616秒(短消息发送时间)

 

字符串分析结束。当然,还有很多可选项在这条短信没有加上,更完整的SMS格式,请参考CDMA SMS standard on 3GPP2 website.

 

 

实际编码时,一个while循环遍历,再加上switch简单的状态机即可。

 

 

长短信何在?

用的这个模组,不支持长短信,厂家回答“作了预处理,把User Data Header去掉了,因为客户一般不愿意自行处理这个User Data Header,他们只要内容、号码等其它信息。所以,模块送出的PDU中不含有User Data Header。”

 

为实现长短信的拆分和组合功能,终端应支持 IS637C协议中关于长短消息处理的参数 HEADER_IND,以及在短消息数据中增加对 User Data Subparameter参数增加用户数据消息头(User DataHeader)和短信拆分、组合的处理。HEADER_IND为消息头标识位,用于指示 User Data Header是否包含消息头,若包含消息头则 HEADER_IND设置为’1,否则设置为’0’。具体的拆分与组合,请参考IS637C,或《中国电信CDMA终端需求规范-SMS分册-V1.0》。需要说明的是,对长短信的处理,有的CDMA模组厂家为了让用户省事,帮你处理了用户数据头,正如前面所看到的,自动加上(1/2), (2/2)等,若想自己手动解析,务必跟模组厂家沟通确认好。