From 83917db040bd7498ebca3b74f879dc1c9e223d8e Mon Sep 17 00:00:00 2001 From: Tammo Spalink Date: Tue, 14 Apr 2009 10:01:23 +0800 Subject: Initial code for cdma sms encode and decode, in java, with simple tests. (direct cherry-pick of master 42/42/8) --- .../android/internal/telephony/SmsMessageBase.java | 14 - .../internal/telephony/cdma/SmsMessage.java | 62 +-- .../internal/telephony/cdma/sms/BearerData.java | 487 ++++++++++++++++++++- .../telephony/cdma/sms/CdmaSmsAddress.java | 15 + .../internal/telephony/cdma/sms/SmsDataCoding.java | 371 ---------------- .../internal/telephony/cdma/sms/UserData.java | 50 ++- 6 files changed, 553 insertions(+), 446 deletions(-) delete mode 100644 telephony/java/com/android/internal/telephony/cdma/sms/SmsDataCoding.java (limited to 'telephony/java/com') diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java index 7c32451..1aad38d 100644 --- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java +++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java @@ -20,20 +20,7 @@ import android.util.Log; import com.android.internal.telephony.SmsHeader; import java.util.Arrays; -import static android.telephony.SmsMessage.ENCODING_7BIT; -import static android.telephony.SmsMessage.ENCODING_16BIT; -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; -import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS; -import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER; import static android.telephony.SmsMessage.MessageClass; -import static com.android.internal.telephony.SmsAddress.TON_ABBREVIATED; -import static com.android.internal.telephony.SmsAddress.TON_ALPHANUMERIC; -import static com.android.internal.telephony.SmsAddress.TON_INTERNATIONAL; -import static com.android.internal.telephony.SmsAddress.TON_NATIONAL; -import static com.android.internal.telephony.SmsAddress.TON_NETWORK; -import static com.android.internal.telephony.SmsAddress.TON_SUBSCRIBER; -import static com.android.internal.telephony.SmsAddress.TON_UNKNOWN; /** * Base class declaring the specific methods and members for SmsMessage. @@ -385,4 +372,3 @@ public abstract class SmsMessageBase { } } - diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index e4b474a..2f26bdc 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -27,7 +27,6 @@ import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.cdma.sms.BearerData; import com.android.internal.telephony.cdma.sms.CdmaSmsAddress; -import com.android.internal.telephony.cdma.sms.SmsDataCoding; import com.android.internal.telephony.cdma.sms.SmsEnvelope; import com.android.internal.telephony.cdma.sms.UserData; @@ -50,23 +49,6 @@ import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS; import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER; import static android.telephony.SmsMessage.MessageClass; -import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_NONE; -import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_TEMPORARY; -import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_PERMANENT; -import static com.android.internal.telephony.cdma.sms.BearerData.MESSAGE_TYPE_DELIVER; -import static com.android.internal.telephony.cdma.sms.BearerData.MESSAGE_TYPE_SUBMIT; -import static com.android.internal.telephony.cdma.sms.BearerData.MESSAGE_TYPE_CANCELLATION; -import static com.android.internal.telephony.cdma.sms.BearerData.MESSAGE_TYPE_DELIVERY_ACK; -import static com.android.internal.telephony.cdma.sms.BearerData.MESSAGE_TYPE_USER_ACK; -import static com.android.internal.telephony.cdma.sms.BearerData.MESSAGE_TYPE_READ_ACK; -import static com.android.internal.telephony.cdma.sms.CdmaSmsAddress.SMS_ADDRESS_MAX; -import static com.android.internal.telephony.cdma.sms.CdmaSmsAddress.SMS_SUBADDRESS_MAX; -import static com.android.internal.telephony.cdma.sms.SmsEnvelope.SMS_BEARER_DATA_MAX; -import static com.android.internal.telephony.cdma.sms.UserData.UD_ENCODING_7BIT_ASCII; -import static com.android.internal.telephony.cdma.sms.UserData.UD_ENCODING_GSM_7BIT_ALPHABET; -import static com.android.internal.telephony.cdma.sms.UserData.UD_ENCODING_IA5; -import static com.android.internal.telephony.cdma.sms.UserData.UD_ENCODING_OCTET; -import static com.android.internal.telephony.cdma.sms.UserData.UD_ENCODING_UNICODE_16; /** * A Short Message Service message. @@ -186,7 +168,7 @@ public class SmsMessage extends SmsMessageBase { // ignore subaddress p.readInt(); //p_cur->sSubAddress.subaddressType - p.readByte(); //p_cur->sSubAddress.odd + p.readInt(); //p_cur->sSubAddress.odd count = p.readByte(); //p_cur->sSubAddress.number_of_digits //p_cur->sSubAddress.digits[digitCount] : for (int index=0; index < count; index++) { @@ -309,15 +291,15 @@ public class SmsMessage extends SmsMessageBase { int septetCount = GsmAlphabet.countGsmSeptets(message, true); // User Data (and length) - uData.userData = message.getBytes(); + uData.payload = message.getBytes(); - if (uData.userData.length > MAX_USER_DATA_SEPTETS) { + if (uData.payload.length > MAX_USER_DATA_SEPTETS) { // Message too long return null; } // desired TP-Data-Coding-Scheme - uData.userDataEncoding = UserData.UD_ENCODING_GSM_7BIT_ALPHABET; + uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; // paddingBits not needed for UD_ENCODING_GSM_7BIT_ALPHABET @@ -341,15 +323,15 @@ public class SmsMessage extends SmsMessageBase { return null; } - uData.userData = textPart; + uData.payload = textPart; - if (uData.userData.length > MAX_USER_DATA_BYTES) { + if (uData.payload.length > MAX_USER_DATA_BYTES) { // Message too long return null; } // TP-Data-Coding-Scheme - uData.userDataEncoding = UserData.UD_ENCODING_UNICODE_16; + uData.msgEncoding = UserData.ENCODING_UNICODE_16; // sms header if(headerData != null) { @@ -425,8 +407,8 @@ public class SmsMessage extends SmsMessageBase { // TP-Data-Coding-Scheme // No class, 8 bit data - uData.userDataEncoding = UserData.UD_ENCODING_OCTET; - uData.userData = data; + uData.msgEncoding = UserData.ENCODING_OCTET; + uData.payload = data; byte[] msgData = sms.getEnvelope(destinationAddress, statusReportRequested, uData, true, true); @@ -619,21 +601,21 @@ public class SmsMessage extends SmsMessageBase { * Parses a SMS message from its BearerData stream. (mobile-terminated only) */ protected void parseSms() { - mBearerData = SmsDataCoding.decodeCdmaSms(mEnvelope.bearerData); - messageRef = mBearerData.messageID; + mBearerData = BearerData.decode(mEnvelope.bearerData); + messageRef = mBearerData.messageId; // TP-Message-Type-Indicator // (See 3GPP2 C.S0015-B, v2, 4.5.1) int messageType = mBearerData.messageType; switch (messageType) { - case MESSAGE_TYPE_USER_ACK: - case MESSAGE_TYPE_READ_ACK: - case MESSAGE_TYPE_DELIVER: + case BearerData.MESSAGE_TYPE_USER_ACK: + case BearerData.MESSAGE_TYPE_READ_ACK: + case BearerData.MESSAGE_TYPE_DELIVER: // Deliver (mobile-terminated only) parseSmsDeliver(); break; - case MESSAGE_TYPE_DELIVERY_ACK: + case BearerData.MESSAGE_TYPE_DELIVERY_ACK: parseSmsDeliveryAck(); break; @@ -699,22 +681,22 @@ public class SmsMessage extends SmsMessageBase { return; } - encodingType = uData.userDataEncoding; + encodingType = uData.msgEncoding; // insert DCS-decoding here when type is supported by ril-library - userData = uData.userData; + userData = uData.payload; userDataHeader = uData.userDataHeader; switch (encodingType) { - case UD_ENCODING_GSM_7BIT_ALPHABET: - case UD_ENCODING_UNICODE_16: + case UserData.ENCODING_GSM_7BIT_ALPHABET: + case UserData.ENCODING_UNICODE_16: // user data was already decoded by wmsts-library messageBody = new String(userData); break; // data and unsupported encodings: - case UD_ENCODING_OCTET: + case UserData.ENCODING_OCTET: default: messageBody = null; break; @@ -771,7 +753,7 @@ public class SmsMessage extends SmsMessageBase { if (useNewId) { setNextMessageId(); } - mBearerData.messageID = nextMessageId; + mBearerData.messageId = nextMessageId; // Set the reply options (See C.S0015-B, v2.0, 4.5.11) if(statusReportRequested) { @@ -795,7 +777,7 @@ public class SmsMessage extends SmsMessageBase { // ** encode BearerData ** byte[] encodedBearerData = null; try { - encodedBearerData = SmsDataCoding.encodeCdmaSms(mBearerData); + encodedBearerData = BearerData.encode(mBearerData); } catch (Exception e) { Log.e(LOG_TAG, "doGetSubmitPdu: EncodeCdmaSMS function in JNI interface failed: " + e.getMessage()); diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index fec9529..8e67387 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -16,7 +16,52 @@ package com.android.internal.telephony.cdma.sms; +import android.util.Log; + +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.SmsHeader; +import com.android.internal.telephony.cdma.sms.UserData; + +import com.android.internal.util.HexDump; +import com.android.internal.util.BitwiseInputStream; +import com.android.internal.util.BitwiseOutputStream; + +/** + * XXX + * + * + */ public final class BearerData{ + private final static String LOG_TAG = "SMS"; + + /** + * Bearer Data Subparameter Indentifiers + * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1) + */ + private final static byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00; + private final static byte SUBPARAM_USER_DATA = 0x01; + private final static byte SUBPARAM_USER_REPONSE_CODE = 0x02; + private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP = 0x03; + //private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE = 0x04; + //private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE = 0x05; + //private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE = 0x06; + //private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE = 0x07; + //private final static byte SUBPARAM_PRIORITY_INDICATOR = 0x08; + //private final static byte SUBPARAM_PRIVACY_INDICATOR = 0x09; + private final static byte SUBPARAM_REPLY_OPTION = 0x0A; + private final static byte SUBPARAM_NUMBER_OF_MESSAGES = 0x0B; + //private final static byte SUBPARAM_ALERT_ON_MESSAGE_DELIVERY = 0x0C; + //private final static byte SUBPARAM_LANGUAGE_INDICATOR = 0x0D; + private final static byte SUBPARAM_CALLBACK_NUMBER = 0x0E; + //private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE = 0x0F; + //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA = 0x10; + //private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX = 0x11; + //private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12; + //private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13; + private final static byte SUBPARAM_MESSAGE_STATUS = 0x14; + //private final static byte SUBPARAM_TP_FAILURE_CAUSE = 0x15; + //private final static byte SUBPARAM_ENHANCED_VMN = 0x16; + //private final static byte SUBPARAM_ENHANCED_VMN_ACK = 0x17; // For completeness the following fields are listed, though not used yet. /** @@ -94,9 +139,6 @@ public final class BearerData{ public static final int ERROR_UNDEFINED = 0xFF; public static final int STATUS_UNDEFINED = 0xFF; - /** Bit-mask indicating used fields for SmsDataCoding */ - public int mask; - /** * 4-bit value indicating the message type in accordance to * table 4.5.1-1 @@ -109,7 +151,7 @@ public final class BearerData{ * (Special rules apply for WAP-messages.) * (See 3GPP2 C.S0015-B, v2, 4.5.1) */ - public int messageID; + public int messageId; /** * 1-bit value that indicates whether a User Data Header is present. @@ -188,5 +230,440 @@ public final class BearerData{ */ public int messageStatus = STATUS_UNDEFINED; -} + private static class CodingException extends Exception { + public CodingException(String s) { + super(s); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BearerData:\n"); + builder.append(" messageType: " + messageType + "\n"); + builder.append(" messageId: " + (int)messageId + "\n"); + builder.append(" hasUserDataHeader: " + hasUserDataHeader + "\n"); + builder.append(" timeStamp: " + timeStamp + "\n"); + builder.append(" userAckReq: " + userAckReq + "\n"); + builder.append(" deliveryAckReq: " + deliveryAckReq + "\n"); + builder.append(" readAckReq: " + readAckReq + "\n"); + builder.append(" reportReq: " + reportReq + "\n"); + builder.append(" numberOfMessages: " + numberOfMessages + "\n"); + builder.append(" callbackNumber: " + callbackNumber + "\n"); + builder.append(" displayMode: " + displayMode + "\n"); + builder.append(" errorClass: " + errorClass + "\n"); + builder.append(" messageStatus: " + messageStatus + "\n"); + builder.append(" userData: " + userData + "\n"); + return builder.toString(); + } + + private static void encodeMessageId(BearerData bData, BitwiseOutputStream outStream) + throws BitwiseOutputStream.AccessException + { + outStream.write(8, 3); + outStream.write(4, bData.messageType); + outStream.write(8, bData.messageId >> 8); + outStream.write(8, bData.messageId); + outStream.write(1, bData.hasUserDataHeader ? 1 : 0); + outStream.skip(3); + } + + private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream) + throws BitwiseOutputStream.AccessException + { + int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits; + byte[] headerData = null; + if (bData.hasUserDataHeader) { + headerData = bData.userData.userDataHeader.toByteArray(); + dataBits += headerData.length * 8; + } + int paramBits = dataBits + 13; + if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || + (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { + paramBits += 8; + } + int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0); + int paddingBits = (paramBytes * 8) - paramBits; + outStream.write(8, paramBytes); + outStream.write(5, bData.userData.msgEncoding); + if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || + (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { + outStream.write(8, bData.userData.msgType); + } + outStream.write(8, bData.userData.numFields); + if (headerData != null) outStream.writeByteArray(headerData.length * 8, headerData); + outStream.writeByteArray(dataBits, bData.userData.payload); + if (paddingBits > 0) outStream.write(paddingBits, 0); + } + + private static void encodeReplyOption(BearerData bData, BitwiseOutputStream outStream) + throws BitwiseOutputStream.AccessException + { + outStream.write(8, 1); + outStream.write(1, bData.userAckReq ? 1 : 0); + outStream.write(1, bData.deliveryAckReq ? 1 : 0); + outStream.write(1, bData.readAckReq ? 1 : 0); + outStream.write(1, bData.reportReq ? 1 : 0); + outStream.write(4, 0); + } + + private static byte[] encodeDtmfSmsAddress(String address) { + int digits = address.length(); + int dataBits = digits * 4; + int dataBytes = (dataBits / 8); + dataBytes += (dataBits % 8) > 0 ? 1 : 0; + byte[] rawData = new byte[dataBytes]; + for (int i = 0; i < digits; i++) { + char c = address.charAt(i); + int val = 0; + if ((c >= '1') && (c <= '9')) val = c - '0'; + else if (c == '0') val = 10; + else if (c == '*') val = 11; + else if (c == '#') val = 12; + else return null; + rawData[i / 2] |= val << (4 - ((i % 2) * 4)); + } + return rawData; + } + + private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException { + if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { + try { + addr.origBytes = addr.address.getBytes("US-ASCII"); + } catch (java.io.UnsupportedEncodingException ex) { + throw new CodingException("invalid SMS address, cannot convert to ASCII"); + } + } else { + addr.origBytes = encodeDtmfSmsAddress(addr.address); + } + } + + private static void encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream) + throws BitwiseOutputStream.AccessException, CodingException + { + CdmaSmsAddress addr = bData.callbackNumber; + encodeCdmaSmsAddress(addr); + int paramBits = 9; + int dataBits = 0; + if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { + paramBits += 7; + dataBits = addr.numberOfDigits * 8; + } else { + dataBits = addr.numberOfDigits * 4; + } + paramBits += dataBits; + int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0); + int paddingBits = (paramBytes * 8) - paramBits; + outStream.write(8, paramBytes); + outStream.write(1, addr.digitMode); + if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { + outStream.write(3, addr.ton); + outStream.write(4, addr.numberPlan); + } + outStream.write(8, addr.numberOfDigits); + outStream.writeByteArray(dataBits, addr.origBytes); + if (paddingBits > 0) outStream.write(paddingBits, 0); + } + + private static void encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream) + throws BitwiseOutputStream.AccessException + { + outStream.write(8, 1); + outStream.write(2, bData.errorClass); + outStream.write(6, bData.messageStatus); + } + private static void encodeMsgCount(BearerData bData, BitwiseOutputStream outStream) + throws BitwiseOutputStream.AccessException + { + outStream.write(8, 1); + outStream.write(8, bData.numberOfMessages); + } + + private static void encodeMsgCenterTimeStamp(BearerData bData, BitwiseOutputStream outStream) + throws BitwiseOutputStream.AccessException + { + outStream.write(8, 6); + outStream.writeByteArray(6 * 8, bData.timeStamp); + } + + /** + * Create serialized representation for BearerData object. + * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) + * + * @param bearerData an instance of BearerData. + * + * @return data byta array of raw encoded SMS bearer data. + */ + public static byte[] encode(BearerData bData) { + 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); + } + if (bData.numberOfMessages != 0) { + outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES); + encodeMsgCount(bData, outStream); + } + if (bData.timeStamp != null) { + outStream.write(8, SUBPARAM_MESSAGE_CENTER_TIME_STAMP); + encodeMsgCenterTimeStamp(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; + } + + private static void decodeMessageId(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 3) { + throw new CodingException("MESSAGE_IDENTIFIER subparam size incorrect"); + } + bData.messageType = inStream.read(4); + bData.messageId = inStream.read(8) << 8; + bData.messageId |= inStream.read(8); + bData.hasUserDataHeader = (inStream.read(1) == 1); + inStream.skip(3); + } + + private static void decodeUserData(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException + { + byte paramBytes = inStream.read(8); + bData.userData = new UserData(); + bData.userData.msgEncoding = inStream.read(5); + bData.userData.msgType = 0; + int consumedBits = 5; + if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || + (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { + bData.userData.msgType = inStream.read(8); + consumedBits += 8; + } + bData.userData.numFields = inStream.read(8); + consumedBits += 8; + int dataBits = (paramBytes * 8) - consumedBits; + bData.userData.payload = inStream.readByteArray(dataBits); + } + + private static String decodePayloadStr(byte[] data, int offset, int numFields, String format) + throws CodingException + { + try { + return new String(data, offset, numFields, format); + } catch (java.io.UnsupportedEncodingException ex) { + throw new CodingException("invalid ASCII user data code"); + } + } + + private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader) + throws CodingException + { + int offset = 0; + if (hasUserDataHeader) { + int UdhLen = userData.payload[0]; + byte[] headerData = new byte[UdhLen]; + System.arraycopy(userData.payload, 1, headerData, 0, UdhLen); + userData.userDataHeader = SmsHeader.parse(headerData); + } + switch (userData.msgEncoding) { + case UserData.ENCODING_GSM_7BIT_ALPHABET: + userData.payloadStr = GsmAlphabet.gsm7BitPackedToString(userData.payload, + offset, userData.numFields); + break; + case UserData.ENCODING_7BIT_ASCII: + userData.payloadStr = decodePayloadStr(userData.payload, offset, + userData.numFields, "US-ASCII"); + break; + case UserData.ENCODING_UNICODE_16: + userData.payloadStr = decodePayloadStr(userData.payload, offset, + userData.numFields * 2, "UTF-16"); + break; + default: + throw new CodingException("unsupported user data encoding (" + + userData.msgEncoding + ")"); + } + } + + private static void decodeReplyOption(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + byte paramBytes = inStream.read(8); + if (paramBytes != 1) { + throw new CodingException("REPLY_OPTION subparam size incorrect"); + } + bData.userAckReq = (inStream.read(1) == 1); + bData.deliveryAckReq = (inStream.read(1) == 1); + bData.readAckReq = (inStream.read(1) == 1); + bData.reportReq = (inStream.read(1) == 1); + inStream.skip(4); + } + + private static void decodeMsgCount(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 1) { + throw new CodingException("NUMBER_OF_MESSAGES subparam size incorrect"); + } + bData.numberOfMessages = inStream.read(8); + } + + private static String decodeDtmfSmsAddress(byte[] rawData, int numFields) + throws CodingException + { + /* DTMF 4-bit digit encoding, defined in at + * 3GPP2 C.S005-D, v2.0, table 2.7.1.3.2.4-4 */ + StringBuffer strBuf = new StringBuffer(numFields); + for (int i = 0; i < numFields; i++) { + int val = 0x0F & (rawData[i / 2] >>> (4 - ((i % 2) * 4))); + if ((val >= 1) && (val <= 9)) strBuf.append(Integer.toString(val, 10)); + else if (val == 10) strBuf.append('0'); + else if (val == 11) strBuf.append('*'); + else if (val == 12) strBuf.append('#'); + else throw new CodingException("invalid SMS address DTMF code (" + val + ")"); + } + return strBuf.toString(); + } + + private static void decodeSmsAddress(CdmaSmsAddress addr) throws CodingException { + if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { + try { + /* As specified in 3GPP2 C.S0015-B, v2, 4.5.15 -- actually + * just 7-bit ASCII encoding, with the MSB being zero. */ + addr.address = new String(addr.origBytes, 0, addr.origBytes.length, "US-ASCII"); + } catch (java.io.UnsupportedEncodingException ex) { + throw new CodingException("invalid SMS address ASCII code"); + } + } else { + addr.address = decodeDtmfSmsAddress(addr.origBytes, addr.numberOfDigits); + } + } + + private static void decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + byte paramBytes = inStream.read(8); + CdmaSmsAddress addr = new CdmaSmsAddress(); + addr.digitMode = inStream.read(1); + byte fieldBits = 4; + byte consumedBits = 1; + if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { + addr.ton = inStream.read(3); + addr.numberPlan = inStream.read(4); + fieldBits = 8; + consumedBits += 7; + } + addr.numberOfDigits = inStream.read(8); + consumedBits += 8; + int remainingBits = (paramBytes * 8) - consumedBits; + int dataBits = addr.numberOfDigits * fieldBits; + int paddingBits = remainingBits - dataBits; + if (remainingBits < dataBits) { + throw new CodingException("CALLBACK_NUMBER subparam encoding size error (" + + "remainingBits " + remainingBits + ", dataBits " + + dataBits + ", paddingBits " + paddingBits + ")"); + } + addr.origBytes = inStream.readByteArray(dataBits); + inStream.skip(paddingBits); + decodeSmsAddress(addr); + bData.callbackNumber = addr; + } + + private static void decodeMsgStatus(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 1) { + throw new CodingException("MESSAGE_STATUS subparam size incorrect"); + } + bData.errorClass = inStream.read(2); + bData.messageStatus = inStream.read(6); + } + + private static void decodeMsgCenterTimeStamp(BearerData bData, + BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 6) { + throw new CodingException("MESSAGE_CENTER_TIME_STAMP subparam size incorrect"); + } + bData.timeStamp = inStream.readByteArray(6 * 8); + } + + /** + * Create BearerData object from serialized representation. + * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) + * + * @param smsData byte array of raw encoded SMS bearer data. + * + * @return an instance of BearerData. + */ + public static BearerData decode(byte[] smsData) { + try { + BitwiseInputStream inStream = new BitwiseInputStream(smsData); + BearerData bData = new BearerData(); + int foundSubparamMask = 0; + while (inStream.available() > 0) { + int subparamId = inStream.read(8); + int subparamIdBit = 1 << subparamId; + if ((foundSubparamMask & subparamIdBit) != 0) { + throw new CodingException("illegal duplicate subparameter (" + + subparamId + ")"); + } + foundSubparamMask |= subparamIdBit; + switch (subparamId) { + case SUBPARAM_MESSAGE_IDENTIFIER: + decodeMessageId(bData, inStream); + break; + case SUBPARAM_USER_DATA: + decodeUserData(bData, inStream); + break; + case SUBPARAM_REPLY_OPTION: + decodeReplyOption(bData, inStream); + break; + case SUBPARAM_NUMBER_OF_MESSAGES: + decodeMsgCount(bData, inStream); + break; + case SUBPARAM_CALLBACK_NUMBER: + decodeCallbackNumber(bData, inStream); + break; + case SUBPARAM_MESSAGE_STATUS: + decodeMsgStatus(bData, inStream); + break; + case SUBPARAM_MESSAGE_CENTER_TIME_STAMP: + decodeMsgCenterTimeStamp(bData, inStream); + break; + default: + throw new CodingException("unsupported bearer data subparameter (" + + subparamId + ")"); + } + } + if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) { + throw new CodingException("missing MESSAGE_IDENTIFIER subparam"); + } + if (bData.userData != null) { + decodeUserDataPayload(bData.userData, bData.hasUserDataHeader); + } + return bData; + } catch (BitwiseInputStream.AccessException ex) { + Log.e(LOG_TAG, "BearerData decode failed: " + ex); + } catch (CodingException ex) { + Log.e(LOG_TAG, "BearerData decode failed: " + ex); + } + return null; + } +} diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java index 1643cab..440debb 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java @@ -17,6 +17,7 @@ package com.android.internal.telephony.cdma.sms; import com.android.internal.telephony.SmsAddress; +import com.android.internal.util.HexDump; public class CdmaSmsAddress extends SmsAddress { /** @@ -95,4 +96,18 @@ public class CdmaSmsAddress extends SmsAddress { public CdmaSmsAddress(){ } + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("CdmaSmsAddress:\n"); + builder.append(" digitMode: " + digitMode + "\n"); + builder.append(" numberMode: " + numberMode + "\n"); + builder.append(" numberPlan: " + numberPlan + "\n"); + builder.append(" numberOfDigits: " + numberOfDigits + "\n"); + builder.append(" ton: " + ton + "\n"); + builder.append(" address: " + address + "\n"); + builder.append(" origBytes: " + HexDump.toHexString(origBytes) + "\n"); + return builder.toString(); + } + } diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsDataCoding.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsDataCoding.java deleted file mode 100644 index 6ba7463..0000000 --- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsDataCoding.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.telephony.cdma.sms; - -import com.android.internal.telephony.SmsHeader; - -/* - * The SMSDataCoding class encodes and decodes CDMA SMS messages. - */ -public class SmsDataCoding { - private final static String TAG = "CDMA_SMS_JNI"; - - private final static int CDMA_SMS_WMS_MASK_BD_NULL = 0x00000000; - private final static int CDMA_SMS_WMS_MASK_BD_MSG_ID = 0x00000001; - private final static int CDMA_SMS_WMS_MASK_BD_USER_DATA = 0x00000002; -// private final static int CDMA_SMS_WMS_MASK_BD_USER_RESP = 0x00000004; - private final static int CDMA_SMS_WMS_MASK_BD_MC_TIME = 0x00000008; -// private final static int CDMA_SMS_WMS_MASK_BD_VALID_ABS = 0x00000010; -// private final static int CDMA_SMS_WMS_MASK_BD_VALID_REL = 0x00000020; -// private final static int CDMA_SMS_WMS_MASK_BD_DEFER_ABS = 0x00000040; -// private final static int CDMA_SMS_WMS_MASK_BD_DEFER_REL = 0x00000080; -// private final static int CDMA_SMS_WMS_MASK_BD_PRIORITY = 0x00000100; -// private final static int CDMA_SMS_WMS_MASK_BD_PRIVACY = 0x00000200; -// private final static int CDMA_SMS_WMS_MASK_BD_REPLY_OPTION = 0x00000400; - private final static int CDMA_SMS_WMS_MASK_BD_NUM_OF_MSGS = 0x00000800; -// private final static int CDMA_SMS_WMS_MASK_BD_ALERT = 0x00001000; -// private final static int CDMA_SMS_WMS_MASK_BD_LANGUAGE = 0x00002000; - private final static int CDMA_SMS_WMS_MASK_BD_CALLBACK = 0x00004000; - private final static int CDMA_SMS_WMS_MASK_BD_DISPLAY_MODE = 0x00008000; -// private final static int CDMA_SMS_WMS_MASK_BD_SCPT_DATA = 0x00010000; -// private final static int CDMA_SMS_WMS_MASK_BD_SCPT_RESULT = 0x00020000; -// private final static int CDMA_SMS_WMS_MASK_BD_DEPOSIT_INDEX = 0x00040000; -// private final static int CDMA_SMS_WMS_MASK_BD_DELIVERY_STATUS = 0x00080000; -// private final static int CDMA_SMS_WMS_MASK_BD_IP_ADDRESS = 0x10000000; -// private final static int CDMA_SMS_WMS_MASK_BD_RSN_NO_NOTIFY = 0x20000000; -// private final static int CDMA_SMS_WMS_MASK_BD_OTHER = 0x40000000; - - /** - * Successful operation. - */ - private static final int JNI_CDMA_SMS_SUCCESS = 0; - - /** - * General failure. - */ - private static final int JNI_CDMA_SMS_FAILURE = 1; - - /** - * Data length is out of length. - */ - private static final int JNI_CDMA_SMS_DATA_LEN_OUT_OF_RANGE = 2; - - /** - * Class name unknown. - */ - private static final int JNI_CDMA_SMS_CLASS_UNKNOWN = 3; - - /** - * Field ID unknown. - */ - private static final int JNI_CDMA_SMS_FIELD_ID_UNKNOWN = 4; - - /** - * Memory allocation failed. - */ - private static final int JNI_CDMA_SMS_OUT_OF_MEMORY = 5; - - /** - * Encode SMS. - * - * @param bearerData an instance of BearerData. - * - * @return the encoded SMS as byte[]. - */ - public static byte[] encodeCdmaSms(BearerData bearerData) { - byte[] encodedSms; - - if( nativeCdmaSmsConstructClientBD() == JNI_CDMA_SMS_FAILURE){ - return null; - } - - // check bearer data and generate bit mask - generateBearerDataBitMask(bearerData); - encodedSms = startEncoding(bearerData); - - if( nativeCdmaSmsDestructClientBD() == JNI_CDMA_SMS_FAILURE){ - return null; - } - return encodedSms; - } - - /** - * Decode SMS. - * - * @param SmsData the encoded SMS. - * - * @return an instance of BearerData. - */ - public static BearerData decodeCdmaSms(byte[] SmsData) { - BearerData bearerData; - - if( nativeCdmaSmsConstructClientBD() == JNI_CDMA_SMS_FAILURE){ - return null; - } - - bearerData = startDecoding(SmsData); - - if( nativeCdmaSmsDestructClientBD() == JNI_CDMA_SMS_FAILURE){ - return null; - } - return bearerData; - } - - private static void generateBearerDataBitMask(BearerData bearerData) { - // initial - bearerData.mask = CDMA_SMS_WMS_MASK_BD_NULL; - - // check message type - if (bearerData.messageType != 0){ - bearerData.mask |= CDMA_SMS_WMS_MASK_BD_MSG_ID; - } - - // check mUserData - if (bearerData.userData != null){ - bearerData.mask |= CDMA_SMS_WMS_MASK_BD_USER_DATA; - } - - // check mTimeStamp - if (bearerData.timeStamp != null){ - bearerData.mask |= CDMA_SMS_WMS_MASK_BD_MC_TIME; - } - - // check mNumberOfMessages - if (bearerData.numberOfMessages > 0){ - bearerData.mask |= CDMA_SMS_WMS_MASK_BD_NUM_OF_MSGS; - } - - // check mCallbackNumber - if(bearerData.callbackNumber != null){ - bearerData.mask |= CDMA_SMS_WMS_MASK_BD_CALLBACK; - } - - // check DisplayMode - if(bearerData.displayMode == BearerData.DISPLAY_DEFAULT || - bearerData.displayMode == BearerData.DISPLAY_IMMEDIATE || - bearerData.displayMode == BearerData.DISPLAY_USER){ - bearerData.mask |= CDMA_SMS_WMS_MASK_BD_DISPLAY_MODE; - } - } - - private static byte[] startEncoding(BearerData bearerData) { - int m_id; - byte[] m_data; - int dataLength; - byte[] encodedSms; - int nbrOfHeaders = 0; - - if( nativeCdmaSmsSetBearerDataPrimitives(bearerData) == JNI_CDMA_SMS_FAILURE){ - return null; - } - - if ((bearerData.mask & CDMA_SMS_WMS_MASK_BD_USER_DATA) == CDMA_SMS_WMS_MASK_BD_USER_DATA){ - if( nativeCdmaSmsSetUserData(bearerData.userData) == JNI_CDMA_SMS_FAILURE){ - return null; - } - - if (bearerData.userData.userDataHeader != null){ - nbrOfHeaders = bearerData.userData.userDataHeader.nbrOfHeaders; - } - - for (int i = 0; i < nbrOfHeaders; i++) { - m_id = bearerData.userData.userDataHeader.getElements().get(i).getID(); - m_data = bearerData.userData.userDataHeader.getElements().get(i).getData(); - dataLength = m_data.length; - if( nativeCdmaSmsSetUserDataHeader(m_id, m_data, dataLength, i) - == JNI_CDMA_SMS_FAILURE){ - return null; - } - } - } - - if ((bearerData.mask & CDMA_SMS_WMS_MASK_BD_CALLBACK) == CDMA_SMS_WMS_MASK_BD_CALLBACK) { - if( nativeCdmaSmsSetSmsAddress(bearerData.callbackNumber) == JNI_CDMA_SMS_FAILURE){ - return null; - } - } - - /* call native method to encode SMS */ - encodedSms = nativeCdmaSmsEncodeSms(); - - return encodedSms; - } - - private static BearerData startDecoding(byte[] SmsData) { - BearerData bData = new BearerData(); - byte[] udhData; - - /* call native method to decode SMS */ - if( nativeCdmaSmsDecodeSms(SmsData) == JNI_CDMA_SMS_FAILURE){ - return null; - } - - if( nativeCdmaSmsGetBearerDataPrimitives(bData) == JNI_CDMA_SMS_FAILURE){ - return null; - } - - if ((bData.mask & CDMA_SMS_WMS_MASK_BD_USER_DATA) == CDMA_SMS_WMS_MASK_BD_USER_DATA) { - bData.userData = new UserData(); - if( nativeCdmaSmsGetUserData(bData.userData) == JNI_CDMA_SMS_FAILURE){ - return null; - } - - udhData = nativeCdmaSmsGetUserDataHeader(); - if (udhData != null) { - bData.userData.userDataHeader = SmsHeader.parse(udhData); - } - } - - if ((bData.mask & CDMA_SMS_WMS_MASK_BD_CALLBACK) == CDMA_SMS_WMS_MASK_BD_CALLBACK) { - bData.callbackNumber = new CdmaSmsAddress(); - if( nativeCdmaSmsGetSmsAddress(bData.callbackNumber) == JNI_CDMA_SMS_FAILURE){ - return null; - } - } - - return bData; - } - - // native methods - - /** - * native method: Allocate memory for clientBD structure - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsConstructClientBD(); - - /** - * native method: Free memory used for clientBD structure - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsDestructClientBD(); - - /** - * native method: fill clientBD structure with bearerData primitives - * - * @param bearerData an instance of BearerData. - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsSetBearerDataPrimitives(BearerData bearerData); - - /** - * native method: fill bearerData primitives with clientBD variables - * - * @param bearerData an instance of BearerData. - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsGetBearerDataPrimitives(BearerData bearerData); - - /** - * native method: fill clientBD.user_data with UserData primitives - * - * @param userData an instance of UserData. - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsSetUserData(UserData userData); - - /** - * native method: fill UserData primitives with clientBD.user_data - * - * @param userData an instance of UserData. - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsGetUserData(UserData userData); - - /** - * native method: fill clientBD.user_data.headers with UserDataHeader primitives - * - * @param ID ID of element. - * @param data element data. - * @param dataLength data length - * @param index index of element - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsSetUserDataHeader( - int ID, byte[] data, int dataLength, int index); - - /** - * native method: fill UserDataHeader primitives with clientBD.user_data.headers - * - * @return user data headers - */ - private static native byte[] nativeCdmaSmsGetUserDataHeader(); - - /** - * native method: fill clientBD.callback with SmsAddress primitives - * - * @param smsAddr an instance of SmsAddress. - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsSetSmsAddress(CdmaSmsAddress smsAddr); - - /** - * native method: fill SmsAddress primitives with clientBD.callback - * - * @param smsAddr an instance of SmsAddress. - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsGetSmsAddress(CdmaSmsAddress smsAddr); - - /** - * native method: call encoding functions and get encoded SMS - * - * @return the encoded SMS - */ - private static native byte[] nativeCdmaSmsEncodeSms(); - - /** - * native method: call decode functions - * - * @param encodedSMS encoded SMS. - * - * @return #JNI_CDMA_SMS_SUCCESS if succeed. - * #JNI_CDMA_SMS_FAILURE if fail. - */ - private static native int nativeCdmaSmsDecodeSms(byte[] encodedSMS); - - /** - * Load the shared library to link the native methods. - */ - static { - try { - System.loadLibrary("cdma_sms_jni"); - } - catch (UnsatisfiedLinkError ule) { - System.err.println("WARNING: Could not load cdma_sms_jni.so"); - } - } -} - diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java index e761469..bd6fbb4 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java @@ -17,24 +17,25 @@ package com.android.internal.telephony.cdma.sms; import com.android.internal.telephony.SmsHeader; +import com.android.internal.util.HexDump; public class UserData{ /** - * Supported user data encoding types + * User data encoding types * (See 3GPP2 C.R1001-F, v1.0, table 9.1-1) */ - public static final int UD_ENCODING_OCTET = 0x00; - //public static final int UD_ENCODING_EXTENDED_PROTOCOL = 0x01; - public static final int UD_ENCODING_7BIT_ASCII = 0x02; - public static final int UD_ENCODING_IA5 = 0x03; - public static final int UD_ENCODING_UNICODE_16 = 0x04; - //public static final int UD_ENCODING_SHIFT_JIS = 0x05; - //public static final int UD_ENCODING_KOREAN = 0x06; - //public static final int UD_ENCODING_LATIN_HEBREW = 0x07; - //public static final int UD_ENCODING_LATIN = 0x08; - public static final int UD_ENCODING_GSM_7BIT_ALPHABET = 0x09; - //public static final int UD_ENCODING_GSM_DCS = 0x0A; + public static final int ENCODING_OCTET = 0x00; + public static final int ENCODING_IS91_EXTENDED_PROTOCOL = 0x01; + public static final int ENCODING_7BIT_ASCII = 0x02; + //public static final int ENCODING_IA5 = 0x03; + public static final int ENCODING_UNICODE_16 = 0x04; + //public static final int ENCODING_SHIFT_JIS = 0x05; + //public static final int ENCODING_KOREAN = 0x06; + //public static final int ENCODING_LATIN_HEBREW = 0x07; + //public static final int ENCODING_LATIN = 0x08; + public static final int ENCODING_GSM_7BIT_ALPHABET = 0x09; + public static final int ENCODING_GSM_DCS = 0x0A; /** * Contains the data header of the user data @@ -44,20 +45,37 @@ public class UserData{ /** * Contains the data encoding type for the SMS message */ - public int userDataEncoding; + public int msgEncoding; - // needed when encoding is IS91 or DCS (not supported yet): - //public int messageType; + // XXX needed when encoding is IS91 or DCS (not supported yet): + public int msgType; /** * Number of invalid bits in the last byte of data. */ public int paddingBits; + public int numFields; + /** * Contains the user data of a SMS message * (See 3GPP2 C.S0015-B, v2, 4.5.2) */ - public byte[] userData; + public byte[] payload; + public String payloadStr; + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("UserData:\n"); + builder.append(" msgEncoding: " + msgEncoding + "\n"); + builder.append(" msgType: " + msgType + "\n"); + builder.append(" paddingBits: " + paddingBits + "\n"); + builder.append(" numFields: " + (int)numFields + "\n"); + builder.append(" userDataHeader: " + userDataHeader + "\n"); + builder.append(" payload: " + HexDump.toHexString(payload)); + builder.append(" payloadStr: " + payloadStr); + return builder.toString(); + } } -- cgit v1.1