/* * Copyright (C) 2008 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 android.content.res.Resources; import android.telephony.SmsCbCmasInfo; import android.telephony.cdma.CdmaSmsCbProgramData; import android.telephony.cdma.CdmaSmsCbProgramResults; import android.text.format.Time; import android.telephony.Rlog; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; import com.android.internal.telephony.SmsConstants; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.util.BitwiseInputStream; import com.android.internal.util.BitwiseOutputStream; import java.util.ArrayList; import java.util.TimeZone; /** * An object to encode and decode CDMA SMS bearer data. */ public final class BearerData { private final static String LOG_TAG = "BearerData"; /** * Bearer Data Subparameter Identifiers * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1) * NOTE: Commented subparameter types are not implemented. */ private final static byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00; private final static byte SUBPARAM_USER_DATA = 0x01; private final static byte SUBPARAM_USER_RESPONSE_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; // All other values after this are reserved. private final static byte SUBPARAM_ID_LAST_DEFINED = 0x17; /** * Supported message types for CDMA SMS messages * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1) */ public static final int MESSAGE_TYPE_DELIVER = 0x01; public static final int MESSAGE_TYPE_SUBMIT = 0x02; public static final int MESSAGE_TYPE_CANCELLATION = 0x03; public static final int MESSAGE_TYPE_DELIVERY_ACK = 0x04; public static final int MESSAGE_TYPE_USER_ACK = 0x05; public static final int MESSAGE_TYPE_READ_ACK = 0x06; public static final int MESSAGE_TYPE_DELIVER_REPORT = 0x07; public static final int MESSAGE_TYPE_SUBMIT_REPORT = 0x08; public int messageType; /** * 16-bit value indicating the message ID, which increments modulo 65536. * (Special rules apply for WAP-messages.) * (See 3GPP2 C.S0015-B, v2, 4.5.1) */ public int messageId; /** * Supported priority modes for CDMA SMS messages * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1) */ public static final int PRIORITY_NORMAL = 0x0; public static final int PRIORITY_INTERACTIVE = 0x1; public static final int PRIORITY_URGENT = 0x2; public static final int PRIORITY_EMERGENCY = 0x3; public boolean priorityIndicatorSet = false; public int priority = PRIORITY_NORMAL; /** * Supported privacy modes for CDMA SMS messages * (See 3GPP2 C.S0015-B, v2.0, table 4.5.10-1) */ public static final int PRIVACY_NOT_RESTRICTED = 0x0; public static final int PRIVACY_RESTRICTED = 0x1; public static final int PRIVACY_CONFIDENTIAL = 0x2; public static final int PRIVACY_SECRET = 0x3; public boolean privacyIndicatorSet = false; public int privacy = PRIVACY_NOT_RESTRICTED; /** * Supported alert priority modes for CDMA SMS messages * (See 3GPP2 C.S0015-B, v2.0, table 4.5.13-1) */ public static final int ALERT_DEFAULT = 0x0; public static final int ALERT_LOW_PRIO = 0x1; public static final int ALERT_MEDIUM_PRIO = 0x2; public static final int ALERT_HIGH_PRIO = 0x3; public boolean alertIndicatorSet = false; public int alert = ALERT_DEFAULT; /** * Supported display modes for CDMA SMS messages. Display mode is * a 2-bit value used to indicate to the mobile station when to * display the received message. (See 3GPP2 C.S0015-B, v2, * 4.5.16) */ public static final int DISPLAY_MODE_IMMEDIATE = 0x0; public static final int DISPLAY_MODE_DEFAULT = 0x1; public static final int DISPLAY_MODE_USER = 0x2; public boolean displayModeSet = false; public int displayMode = DISPLAY_MODE_DEFAULT; /** * Language Indicator values. NOTE: the spec (3GPP2 C.S0015-B, * v2, 4.5.14) is ambiguous as to the meaning of this field, as it * refers to C.R1001-D but that reference has been crossed out. * It would seem reasonable to assume the values from C.R1001-F * (table 9.2-1) are to be used instead. */ public static final int LANGUAGE_UNKNOWN = 0x00; public static final int LANGUAGE_ENGLISH = 0x01; public static final int LANGUAGE_FRENCH = 0x02; public static final int LANGUAGE_SPANISH = 0x03; public static final int LANGUAGE_JAPANESE = 0x04; public static final int LANGUAGE_KOREAN = 0x05; public static final int LANGUAGE_CHINESE = 0x06; public static final int LANGUAGE_HEBREW = 0x07; public boolean languageIndicatorSet = false; public int language = LANGUAGE_UNKNOWN; /** * SMS Message Status Codes. The first component of the Message * status indicates if an error has occurred and whether the error * is considered permanent or temporary. The second component of * the Message status indicates the cause of the error (if any). * (See 3GPP2 C.S0015-B, v2.0, 4.5.21) */ /* no-error codes */ public static final int ERROR_NONE = 0x00; public static final int STATUS_ACCEPTED = 0x00; public static final int STATUS_DEPOSITED_TO_INTERNET = 0x01; public static final int STATUS_DELIVERED = 0x02; public static final int STATUS_CANCELLED = 0x03; /* temporary-error and permanent-error codes */ public static final int ERROR_TEMPORARY = 0x02; public static final int STATUS_NETWORK_CONGESTION = 0x04; public static final int STATUS_NETWORK_ERROR = 0x05; public static final int STATUS_UNKNOWN_ERROR = 0x1F; /* permanent-error codes */ public static final int ERROR_PERMANENT = 0x03; public static final int STATUS_CANCEL_FAILED = 0x06; public static final int STATUS_BLOCKED_DESTINATION = 0x07; public static final int STATUS_TEXT_TOO_LONG = 0x08; public static final int STATUS_DUPLICATE_MESSAGE = 0x09; public static final int STATUS_INVALID_DESTINATION = 0x0A; public static final int STATUS_MESSAGE_EXPIRED = 0x0D; /* undefined-status codes */ public static final int ERROR_UNDEFINED = 0xFF; public static final int STATUS_UNDEFINED = 0xFF; public boolean messageStatusSet = false; public int errorClass = ERROR_UNDEFINED; public int messageStatus = STATUS_UNDEFINED; /** * 1-bit value that indicates whether a User Data Header (UDH) is present. * (See 3GPP2 C.S0015-B, v2, 4.5.1) * * NOTE: during encoding, this value will be set based on the * presence of a UDH in the structured data, any existing setting * will be overwritten. */ public boolean hasUserDataHeader; /** * provides the information for the user data * (e.g. padding bits, user data, user data header, etc) * (See 3GPP2 C.S.0015-B, v2, 4.5.2) */ public UserData userData; /** * The User Response Code subparameter is used in the SMS User * Acknowledgment Message to respond to previously received short * messages. This message center-specific element carries the * identifier of a predefined response. (See 3GPP2 C.S.0015-B, v2, * 4.5.3) */ public boolean userResponseCodeSet = false; public int userResponseCode; /** * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4 */ public static class TimeStamp extends Time { public TimeStamp() { super(TimeZone.getDefault().getID()); // 3GPP2 timestamps use the local timezone } public static TimeStamp fromByteArray(byte[] data) { TimeStamp ts = new TimeStamp(); // C.S0015-B v2.0, 4.5.4: range is 1996-2095 int year = IccUtils.cdmaBcdByteToInt(data[0]); if (year > 99 || year < 0) return null; ts.year = year >= 96 ? year + 1900 : year + 2000; int month = IccUtils.cdmaBcdByteToInt(data[1]); if (month < 1 || month > 12) return null; ts.month = month - 1; int day = IccUtils.cdmaBcdByteToInt(data[2]); if (day < 1 || day > 31) return null; ts.monthDay = day; int hour = IccUtils.cdmaBcdByteToInt(data[3]); if (hour < 0 || hour > 23) return null; ts.hour = hour; int minute = IccUtils.cdmaBcdByteToInt(data[4]); if (minute < 0 || minute > 59) return null; ts.minute = minute; int second = IccUtils.cdmaBcdByteToInt(data[5]); if (second < 0 || second > 59) return null; ts.second = second; return ts; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("TimeStamp "); builder.append("{ year=" + year); builder.append(", month=" + month); builder.append(", day=" + monthDay); builder.append(", hour=" + hour); builder.append(", minute=" + minute); builder.append(", second=" + second); builder.append(" }"); return builder.toString(); } } public TimeStamp msgCenterTimeStamp; public TimeStamp validityPeriodAbsolute; public TimeStamp deferredDeliveryTimeAbsolute; /** * Relative time is specified as one byte, the value of which * falls into a series of ranges, as specified below. The idea is * that shorter time intervals allow greater precision -- the * value means minutes from zero until the MINS_LIMIT (inclusive), * upon which it means hours until the HOURS_LIMIT, and so * forth. (See 3GPP2 C.S0015-B, v2, 4.5.6-1) */ public static final int RELATIVE_TIME_MINS_LIMIT = 143; public static final int RELATIVE_TIME_HOURS_LIMIT = 167; public static final int RELATIVE_TIME_DAYS_LIMIT = 196; public static final int RELATIVE_TIME_WEEKS_LIMIT = 244; public static final int RELATIVE_TIME_INDEFINITE = 245; public static final int RELATIVE_TIME_NOW = 246; public static final int RELATIVE_TIME_MOBILE_INACTIVE = 247; public static final int RELATIVE_TIME_RESERVED = 248; public boolean validityPeriodRelativeSet; public int validityPeriodRelative; public boolean deferredDeliveryTimeRelativeSet; public int deferredDeliveryTimeRelative; /** * The Reply Option subparameter contains 1-bit values which * indicate whether SMS acknowledgment is requested or not. (See * 3GPP2 C.S0015-B, v2, 4.5.11) */ public boolean userAckReq; public boolean deliveryAckReq; public boolean readAckReq; public boolean reportReq; /** * The Number of Messages subparameter (8-bit value) is a decimal * number in the 0 to 99 range representing the number of messages * stored at the Voice Mail System. This element is used by the * Voice Mail Notification service. (See 3GPP2 C.S0015-B, v2, * 4.5.12) */ public int numberOfMessages; /** * The Message Deposit Index subparameter is assigned by the * message center as a unique index to the contents of the User * Data subparameter in each message sent to a particular mobile * station. The mobile station, when replying to a previously * received short message which included a Message Deposit Index * subparameter, may include the Message Deposit Index of the * received message to indicate to the message center that the * original contents of the message are to be included in the * reply. (See 3GPP2 C.S0015-B, v2, 4.5.18) */ public int depositIndex; /** * 4-bit or 8-bit value that indicates the number to be dialed in reply to a * received SMS message. * (See 3GPP2 C.S0015-B, v2, 4.5.15) */ public CdmaSmsAddress callbackNumber; /** * CMAS warning notification information. * @see #decodeCmasUserData(BearerData, int) */ public SmsCbCmasInfo cmasWarningInfo; /** * The Service Category Program Data subparameter is used to enable and disable * SMS broadcast service categories to display. If this subparameter is present, * this field will contain a list of one or more * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the * operation(s) to perform. */ public ArrayList serviceCategoryProgramData; /** * The Service Category Program Results subparameter informs the message center * of the results of a Service Category Program Data request. */ public ArrayList serviceCategoryProgramResults; private static class CodingException extends Exception { public CodingException(String s) { super(s); } } /** * Returns the language indicator as a two-character ISO 639 string. * @return a two character ISO 639 language code */ public String getLanguage() { return getLanguageCodeForValue(language); } /** * Converts a CDMA language indicator value to an ISO 639 two character language code. * @param languageValue the CDMA language value to convert * @return the two character ISO 639 language code for the specified value, or null if unknown */ private static String getLanguageCodeForValue(int languageValue) { switch (languageValue) { case LANGUAGE_ENGLISH: return "en"; case LANGUAGE_FRENCH: return "fr"; case LANGUAGE_SPANISH: return "es"; case LANGUAGE_JAPANESE: return "ja"; case LANGUAGE_KOREAN: return "ko"; case LANGUAGE_CHINESE: return "zh"; case LANGUAGE_HEBREW: return "he"; default: return null; } } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("BearerData "); builder.append("{ messageType=" + messageType); builder.append(", messageId=" + messageId); builder.append(", priority=" + (priorityIndicatorSet ? priority : "unset")); builder.append(", privacy=" + (privacyIndicatorSet ? privacy : "unset")); builder.append(", alert=" + (alertIndicatorSet ? alert : "unset")); builder.append(", displayMode=" + (displayModeSet ? displayMode : "unset")); builder.append(", language=" + (languageIndicatorSet ? language : "unset")); builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset")); builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset")); builder.append(", msgCenterTimeStamp=" + ((msgCenterTimeStamp != null) ? msgCenterTimeStamp : "unset")); builder.append(", validityPeriodAbsolute=" + ((validityPeriodAbsolute != null) ? validityPeriodAbsolute : "unset")); builder.append(", validityPeriodRelative=" + ((validityPeriodRelativeSet) ? validityPeriodRelative : "unset")); builder.append(", deferredDeliveryTimeAbsolute=" + ((deferredDeliveryTimeAbsolute != null) ? deferredDeliveryTimeAbsolute : "unset")); builder.append(", deferredDeliveryTimeRelative=" + ((deferredDeliveryTimeRelativeSet) ? deferredDeliveryTimeRelative : "unset")); builder.append(", userAckReq=" + userAckReq); builder.append(", deliveryAckReq=" + deliveryAckReq); builder.append(", readAckReq=" + readAckReq); builder.append(", reportReq=" + reportReq); builder.append(", numberOfMessages=" + numberOfMessages); builder.append(", callbackNumber=" + Rlog.pii(LOG_TAG, callbackNumber)); builder.append(", depositIndex=" + depositIndex); builder.append(", hasUserDataHeader=" + hasUserDataHeader); builder.append(", userData=" + userData); builder.append(" }"); 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 int countAsciiSeptets(CharSequence msg, boolean force) { int msgLen = msg.length(); if (force) return msgLen; for (int i = 0; i < msgLen; i++) { if (UserData.charToAscii.get(msg.charAt(i), -1) == -1) { return -1; } } return msgLen; } /** * Calculate the message text encoding length, fragmentation, and other details. * * @param msg message text * @param force7BitEncoding ignore (but still count) illegal characters if true * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg * @return septet count, or -1 on failure */ public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg, boolean force7BitEncoding, boolean isEntireMsg) { TextEncodingDetails ted; int septets = countAsciiSeptets(msg, force7BitEncoding); if (septets != -1 && septets <= SmsConstants.MAX_USER_DATA_SEPTETS) { ted = new TextEncodingDetails(); ted.msgCount = 1; ted.codeUnitCount = septets; ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets; ted.codeUnitSize = SmsConstants.ENCODING_7BIT; } else { ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength( msg, force7BitEncoding); if (ted.msgCount == 1 && ted.codeUnitSize == SmsConstants.ENCODING_7BIT && isEntireMsg) { // We don't support single-segment EMS, so calculate for 16-bit // TODO: Consider supporting single-segment EMS return SmsMessageBase.calcUnicodeEncodingDetails(msg); } } return ted; } private static byte[] encode7bitAscii(String msg, boolean force) throws CodingException { try { BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length()); int msgLen = msg.length(); for (int i = 0; i < msgLen; i++) { int charCode = UserData.charToAscii.get(msg.charAt(i), -1); if (charCode == -1) { if (force) { outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR); } else { throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")"); } } else { outStream.write(7, charCode); } } return outStream.toByteArray(); } catch (BitwiseOutputStream.AccessException ex) { throw new CodingException("7bit ASCII encode failed: " + ex); } } private static byte[] encodeUtf16(String msg) throws CodingException { try { return msg.getBytes("utf-16be"); } catch (java.io.UnsupportedEncodingException ex) { throw new CodingException("UTF-16 encode failed: " + ex); } } private static class Gsm7bitCodingResult { int septets; byte[] data; } private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force) throws CodingException { try { /* * TODO(cleanup): It would be nice if GsmAlphabet provided * an option to produce just the data without prepending * the septet count, as this function is really just a * wrapper to strip that off. Not to mention that the * septet count is generally known prior to invocation of * the encoder. Note that it cannot be derived from the * resulting array length, since that cannot distinguish * if the last contains either 1 or 8 valid bits. * * TODO(cleanup): The BitwiseXStreams could also be * extended with byte-wise reversed endianness read/write * routines to allow a corresponding implementation of * stringToGsm7BitPacked, and potentially directly support * access to the main bitwise stream from encode/decode. */ byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0); Gsm7bitCodingResult result = new Gsm7bitCodingResult(); result.data = new byte[fullData.length - 1]; System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1); result.septets = fullData[0] & 0x00FF; return result; } catch (com.android.internal.telephony.EncodeException ex) { throw new CodingException("7bit GSM encode failed: " + ex); } } private static void encode7bitEms(UserData uData, byte[] udhData, boolean force) throws CodingException { int udhBytes = udhData.length + 1; // Add length octet. int udhSeptets = ((udhBytes * 8) + 6) / 7; Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force); uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; uData.msgEncodingSet = true; uData.numFields = gcr.septets; uData.payload = gcr.data; uData.payload[0] = (byte)udhData.length; System.arraycopy(udhData, 0, uData.payload, 1, udhData.length); } private static void encode16bitEms(UserData uData, byte[] udhData) throws CodingException { byte[] payload = encodeUtf16(uData.payloadStr); int udhBytes = udhData.length + 1; // Add length octet. int udhCodeUnits = (udhBytes + 1) / 2; int payloadCodeUnits = payload.length / 2; uData.msgEncoding = UserData.ENCODING_UNICODE_16; uData.msgEncodingSet = true; uData.numFields = udhCodeUnits + payloadCodeUnits; uData.payload = new byte[uData.numFields * 2]; uData.payload[0] = (byte)udhData.length; System.arraycopy(udhData, 0, uData.payload, 1, udhData.length); System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length); } private static void encodeEmsUserDataPayload(UserData uData) throws CodingException { byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader); if (uData.msgEncodingSet) { if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) { encode7bitEms(uData, headerData, true); } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) { encode16bitEms(uData, headerData); } else { throw new CodingException("unsupported EMS user data encoding (" + uData.msgEncoding + ")"); } } else { try { encode7bitEms(uData, headerData, false); } catch (CodingException ex) { encode16bitEms(uData, headerData); } } } private static byte[] encodeShiftJis(String msg) throws CodingException { try { return msg.getBytes("Shift_JIS"); } catch (java.io.UnsupportedEncodingException ex) { throw new CodingException("Shift-JIS encode failed: " + ex); } } private static void encodeUserDataPayload(UserData uData) throws CodingException { if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) { Rlog.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) { Rlog.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) { Rlog.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 if (uData.msgEncoding == UserData.ENCODING_SHIFT_JIS) { uData.payload = encodeShiftJis(uData.payloadStr); uData.numFields = uData.payload.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; } } private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream) throws BitwiseOutputStream.AccessException, CodingException { /* * TODO(cleanup): Do we really need to set userData.payload as * a side effect of encoding? If not, we could avoid data * copies by passing outStream directly. */ encodeUserDataPayload(bData.userData); bData.hasUserDataHeader = bData.userData.userDataHeader != null; if (bData.userData.payload.length > SmsConstants.MAX_USER_DATA_BYTES) { throw new CodingException("encoded user data too large (" + bData.userData.payload.length + " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)"); } /* * TODO(cleanup): figure out what the right answer is WRT paddingBits field * * userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7); * userData.paddingBits = 0; // XXX this seems better, but why? * */ int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits; 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); 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; } /* * TODO(cleanup): CdmaSmsAddress encoding should make use of * CdmaSmsAddress.parse provided that DTMF encoding is unified, * and the difference in 4-bit vs. 8-bit is resolved. */ 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 encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream) throws BitwiseOutputStream.AccessException { outStream.write(8, 1); outStream.write(8, bData.validityPeriodRelative); } private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream) throws BitwiseOutputStream.AccessException { outStream.write(8, 1); outStream.write(2, bData.privacy); outStream.skip(6); } private static void encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream) throws BitwiseOutputStream.AccessException { outStream.write(8, 1); outStream.write(8, bData.language); } private static void encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream) throws BitwiseOutputStream.AccessException { outStream.write(8, 1); outStream.write(2, bData.displayMode); outStream.skip(6); } private static void encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream) throws BitwiseOutputStream.AccessException { outStream.write(8, 1); outStream.write(2, bData.priority); outStream.skip(6); } private static void encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream) throws BitwiseOutputStream.AccessException { outStream.write(8, 1); outStream.write(2, bData.alert); outStream.skip(6); } private static void encodeScpResults(BearerData bData, BitwiseOutputStream outStream) throws BitwiseOutputStream.AccessException { ArrayList results = bData.serviceCategoryProgramResults; outStream.write(8, (results.size() * 4)); // 4 octets per program result for (CdmaSmsCbProgramResults result : results) { int category = result.getCategory(); outStream.write(8, category >> 8); outStream.write(8, category); outStream.write(8, result.getLanguage()); outStream.write(4, result.getCategoryResult()); outStream.skip(4); } } /** * Create serialized representation for BearerData object. * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) * * @param bData an instance of BearerData. * * @return byte array of raw encoded SMS bearer data. */ 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); } 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); } if (bData.serviceCategoryProgramResults != null) { outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS); encodeScpResults(bData, outStream); } return outStream.toByteArray(); } catch (BitwiseOutputStream.AccessException ex) { Rlog.e(LOG_TAG, "BearerData encode failed: " + ex); } catch (CodingException ex) { Rlog.e(LOG_TAG, "BearerData encode failed: " + ex); } return null; } private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 3 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; 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); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); return decodeSuccess; } private static boolean decodeReserved( BearerData bData, BitwiseInputStream inStream, int subparamId) throws BitwiseInputStream.AccessException, CodingException { boolean decodeSuccess = false; int subparamLen = inStream.read(8); // SUBPARAM_LEN int paramBits = subparamLen * 8; if (paramBits <= inStream.available()) { decodeSuccess = true; inStream.skip(paramBits); } Rlog.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode " + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")"); if (!decodeSuccess) { throw new CodingException("RESERVED bearer data subparameter " + subparamId + " had invalid SUBPARAM_LEN " + subparamLen); } return decodeSuccess; } private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { int paramBits = inStream.read(8) * 8; bData.userData = new UserData(); bData.userData.msgEncoding = inStream.read(5); bData.userData.msgEncodingSet = true; 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 = paramBits - consumedBits; bData.userData.payload = inStream.readByteArray(dataBits); return true; } private static String decodeUtf8(byte[] data, int offset, int numFields) throws CodingException { return decodeCharset(data, offset, numFields, 1, "UTF-8"); } private static String decodeUtf16(byte[] data, int offset, int numFields) throws CodingException { // Subtract header and possible padding byte (at end) from num fields. int padding = offset % 2; numFields -= (offset + padding) / 2; return decodeCharset(data, offset, numFields, 2, "utf-16be"); } private static String decodeCharset(byte[] data, int offset, int numFields, int width, String charset) throws CodingException { if (numFields < 0 || (numFields * width + offset) > data.length) { // Try to decode the max number of characters in payload int padding = offset % width; int maxNumFields = (data.length - offset - padding) / width; if (maxNumFields < 0) { throw new CodingException(charset + " decode failed: offset out of range"); } Rlog.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = " + numFields + " data.length = " + data.length + " maxNumFields = " + maxNumFields); numFields = maxNumFields; } try { return new String(data, offset, numFields * width, charset); } catch (java.io.UnsupportedEncodingException ex) { throw new CodingException(charset + " decode failed: " + ex); } } private static String decode7bitAscii(byte[] data, int offset, int numFields) throws CodingException { try { offset *= 8; StringBuffer strBuf = new StringBuffer(numFields); BitwiseInputStream inStream = new BitwiseInputStream(data); int wantedBits = (offset * 8) + (numFields * 7); if (inStream.available() < wantedBits) { throw new CodingException("insufficient data (wanted " + wantedBits + " bits, but only have " + inStream.available() + ")"); } inStream.skip(offset); for (int i = 0; i < numFields; i++) { int charCode = inStream.read(7); if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) && (charCode <= UserData.ASCII_MAP_MAX_INDEX)) { strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]); } else if (charCode == UserData.ASCII_NL_INDEX) { strBuf.append('\n'); } else if (charCode == UserData.ASCII_CR_INDEX) { strBuf.append('\r'); } else { /* For other charCodes, they are unprintable, and so simply use SPACE. */ strBuf.append(' '); } } return strBuf.toString(); } catch (BitwiseInputStream.AccessException ex) { throw new CodingException("7bit ASCII decode failed: " + ex); } } private static String decode7bitGsm(byte[] data, int offset, int numFields) throws CodingException { // Start reading from the next 7-bit aligned boundary after offset. int offsetBits = offset * 8; int offsetSeptets = (offsetBits + 6) / 7; numFields -= offsetSeptets; int paddingBits = (offsetSeptets * 7) - offsetBits; String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits, 0, 0); if (result == null) { throw new CodingException("7bit GSM decoding failed"); } return result; } private static String decodeLatin(byte[] data, int offset, int numFields) throws CodingException { return decodeCharset(data, offset, numFields, 1, "ISO-8859-1"); } private static String decodeShiftJis(byte[] data, int offset, int numFields) throws CodingException { return decodeCharset(data, offset, numFields, 1, "Shift_JIS"); } private static String decodeGsmDcs(byte[] data, int offset, int numFields, int msgType) throws CodingException { if ((msgType & 0xC0) != 0) { throw new CodingException("unsupported coding group (" + msgType + ")"); } switch ((msgType >> 2) & 0x3) { case UserData.ENCODING_GSM_DCS_7BIT: return decode7bitGsm(data, offset, numFields); case UserData.ENCODING_GSM_DCS_8BIT: return decodeUtf8(data, offset, numFields); case UserData.ENCODING_GSM_DCS_16BIT: return decodeUtf16(data, offset, numFields); default: throw new CodingException("unsupported user msgType encoding (" + msgType + ")"); } } private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader) throws CodingException { int offset = 0; if (hasUserDataHeader) { int udhLen = userData.payload[0] & 0x00FF; offset += udhLen + 1; byte[] headerData = new byte[udhLen]; System.arraycopy(userData.payload, 1, headerData, 0, udhLen); userData.userDataHeader = SmsHeader.fromByteArray(headerData); } switch (userData.msgEncoding) { case UserData.ENCODING_OCTET: /* * Octet decoding depends on the carrier service. */ boolean decodingtypeUTF8 = Resources.getSystem() .getBoolean(com.android.internal.R.bool.config_sms_utf8_support); // Strip off any padding bytes, meaning any differences between the length of the // array and the target length specified by numFields. This is to avoid any // confusion by code elsewhere that only considers the payload array length. byte[] payload = new byte[userData.numFields]; int copyLen = userData.numFields < userData.payload.length ? userData.numFields : userData.payload.length; System.arraycopy(userData.payload, 0, payload, 0, copyLen); userData.payload = payload; if (!decodingtypeUTF8) { // There are many devices in the market that send 8bit text sms (latin encoded) as // octet encoded. userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields); } else { userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields); } break; case UserData.ENCODING_IA5: case UserData.ENCODING_7BIT_ASCII: userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields); break; case UserData.ENCODING_UNICODE_16: userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields); break; case UserData.ENCODING_GSM_7BIT_ALPHABET: userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields); break; case UserData.ENCODING_LATIN: userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields); break; case UserData.ENCODING_SHIFT_JIS: userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields); break; case UserData.ENCODING_GSM_DCS: userData.payloadStr = decodeGsmDcs(userData.payload, offset, userData.numFields, userData.msgType); break; default: throw new CodingException("unsupported user data encoding (" + userData.msgEncoding + ")"); } } /** * IS-91 Voice Mail message decoding * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) * (For character encodings, see TIA/EIA/IS-91, Annex B) * * Protocol Summary: The user data payload may contain 3-14 * characters. The first two characters are parsed as a number * and indicate the number of voicemails. The third character is * either a SPACE or '!' to indicate normal or urgent priority, * respectively. Any following characters are treated as normal * text user data payload. * * Note that the characters encoding is 6-bit packed. */ private static void decodeIs91VoicemailStatus(BearerData bData) throws BitwiseInputStream.AccessException, CodingException { BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); int dataLen = inStream.available() / 6; // 6-bit packed character encoding. int numFields = bData.userData.numFields; if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) { throw new CodingException("IS-91 voicemail status decoding failed"); } try { StringBuffer strbuf = new StringBuffer(dataLen); while (inStream.available() >= 6) { strbuf.append(UserData.ASCII_MAP[inStream.read(6)]); } String data = strbuf.toString(); bData.numberOfMessages = Integer.parseInt(data.substring(0, 2)); char prioCode = data.charAt(2); if (prioCode == ' ') { bData.priority = PRIORITY_NORMAL; } else if (prioCode == '!') { bData.priority = PRIORITY_URGENT; } else { throw new CodingException("IS-91 voicemail status decoding failed: " + "illegal priority setting (" + prioCode + ")"); } bData.priorityIndicatorSet = true; bData.userData.payloadStr = data.substring(3, numFields - 3); } catch (java.lang.NumberFormatException ex) { throw new CodingException("IS-91 voicemail status decoding failed: " + ex); } catch (java.lang.IndexOutOfBoundsException ex) { throw new CodingException("IS-91 voicemail status decoding failed: " + ex); } } /** * IS-91 Short Message decoding * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) * (For character encodings, see TIA/EIA/IS-91, Annex B) * * Protocol Summary: The user data payload may contain 1-14 * characters, which are treated as normal text user data payload. * Note that the characters encoding is 6-bit packed. */ private static void decodeIs91ShortMessage(BearerData bData) throws BitwiseInputStream.AccessException, CodingException { BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); int dataLen = inStream.available() / 6; // 6-bit packed character encoding. int numFields = bData.userData.numFields; // dataLen may be > 14 characters due to octet padding if ((numFields > 14) || (dataLen < numFields)) { throw new CodingException("IS-91 short message decoding failed"); } StringBuffer strbuf = new StringBuffer(dataLen); for (int i = 0; i < numFields; i++) { strbuf.append(UserData.ASCII_MAP[inStream.read(6)]); } bData.userData.payloadStr = strbuf.toString(); } /** * IS-91 CLI message (callback number) decoding * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) * * Protocol Summary: The data payload may contain 1-32 digits, * encoded using standard 4-bit DTMF, which are treated as a * callback number. */ private static void decodeIs91Cli(BearerData bData) throws CodingException { BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); int dataLen = inStream.available() / 4; // 4-bit packed DTMF digit encoding. int numFields = bData.userData.numFields; if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) { throw new CodingException("IS-91 voicemail status decoding failed"); } CdmaSmsAddress addr = new CdmaSmsAddress(); addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF; addr.origBytes = bData.userData.payload; addr.numberOfDigits = (byte)numFields; decodeSmsAddress(addr); bData.callbackNumber = addr; } private static void decodeIs91(BearerData bData) throws BitwiseInputStream.AccessException, CodingException { switch (bData.userData.msgType) { case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS: decodeIs91VoicemailStatus(bData); break; case UserData.IS91_MSG_TYPE_CLI: decodeIs91Cli(bData); break; case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL: case UserData.IS91_MSG_TYPE_SHORT_MESSAGE: decodeIs91ShortMessage(bData); break; default: throw new CodingException("unsupported IS-91 message type (" + bData.userData.msgType + ")"); } } private static boolean decodeReplyOption(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; 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); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "REPLY_OPTION decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); return decodeSuccess; } private static boolean decodeMsgCount(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8)); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); return decodeSuccess; } private static boolean decodeDepositIndex(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 2 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); return decodeSuccess; } 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 boolean decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException { final int EXPECTED_PARAM_SIZE = 1 * 8; //at least int paramBits = inStream.read(8) * 8; if (paramBits < EXPECTED_PARAM_SIZE) { inStream.skip(paramBits); return false; } 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 = paramBits - 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; return true; } private static boolean decodeMsgStatus(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.errorClass = inStream.read(2); bData.messageStatus = inStream.read(6); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); bData.messageStatusSet = decodeSuccess; return decodeSuccess; } private static boolean decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 6 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); return decodeSuccess; } private static boolean decodeValidityAbs(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 6 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); return decodeSuccess; } private static boolean decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 6 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray( inStream.readByteArray(6 * 8)); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); return decodeSuccess; } private static boolean decodeValidityRel(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.deferredDeliveryTimeRelative = inStream.read(8); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); bData.deferredDeliveryTimeRelativeSet = decodeSuccess; return decodeSuccess; } private static boolean decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.validityPeriodRelative = inStream.read(8); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); bData.validityPeriodRelativeSet = decodeSuccess; return decodeSuccess; } private static boolean decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.privacy = inStream.read(2); inStream.skip(6); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); bData.privacyIndicatorSet = decodeSuccess; return decodeSuccess; } private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.language = inStream.read(8); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); bData.languageIndicatorSet = decodeSuccess; return decodeSuccess; } private static boolean decodeDisplayMode(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.displayMode = inStream.read(2); inStream.skip(6); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "DISPLAY_MODE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); bData.displayModeSet = decodeSuccess; return decodeSuccess; } private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.priority = inStream.read(2); inStream.skip(6); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); bData.priorityIndicatorSet = decodeSuccess; return decodeSuccess; } private static boolean decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.alert = inStream.read(2); inStream.skip(6); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); bData.alertIndicatorSet = decodeSuccess; return decodeSuccess; } private static boolean decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { final int EXPECTED_PARAM_SIZE = 1 * 8; boolean decodeSuccess = false; int paramBits = inStream.read(8) * 8; if (paramBits >= EXPECTED_PARAM_SIZE) { paramBits -= EXPECTED_PARAM_SIZE; decodeSuccess = true; bData.userResponseCode = inStream.read(8); } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } inStream.skip(paramBits); bData.userResponseCodeSet = decodeSuccess; return decodeSuccess; } private static boolean decodeServiceCategoryProgramData(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException { if (inStream.available() < 13) { throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " + inStream.available() + " bits available"); } int paramBits = inStream.read(8) * 8; int msgEncoding = inStream.read(5); paramBits -= 5; if (inStream.available() < paramBits) { throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " + inStream.available() + " bits available (" + paramBits + " bits expected)"); } ArrayList programDataList = new ArrayList(); final int CATEGORY_FIELD_MIN_SIZE = 6 * 8; boolean decodeSuccess = false; while (paramBits >= CATEGORY_FIELD_MIN_SIZE) { int operation = inStream.read(4); int category = (inStream.read(8) << 8) | inStream.read(8); int language = inStream.read(8); int maxMessages = inStream.read(8); int alertOption = inStream.read(4); int numFields = inStream.read(8); paramBits -= CATEGORY_FIELD_MIN_SIZE; int textBits = getBitsForNumFields(msgEncoding, numFields); if (paramBits < textBits) { throw new CodingException("category name is " + textBits + " bits in length," + " but there are only " + paramBits + " bits available"); } UserData userData = new UserData(); userData.msgEncoding = msgEncoding; userData.msgEncodingSet = true; userData.numFields = numFields; userData.payload = inStream.readByteArray(textBits); paramBits -= textBits; decodeUserDataPayload(userData, false); String categoryName = userData.payloadStr; CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category, language, maxMessages, alertOption, categoryName); programDataList.add(programData); decodeSuccess = true; } if ((! decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ')'); } inStream.skip(paramBits); bData.serviceCategoryProgramData = programDataList; return decodeSuccess; } private static int serviceCategoryToCmasMessageClass(int serviceCategory) { switch (serviceCategory) { case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT: return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT: return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT: return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE: return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; default: return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; } } /** * Calculates the number of bits to read for the specified number of encoded characters. * @param msgEncoding the message encoding to use * @param numFields the number of characters to read. For Shift-JIS and Korean encodings, * this is the number of bytes to read. * @return the number of bits to read from the stream * @throws CodingException if the specified encoding is not supported */ private static int getBitsForNumFields(int msgEncoding, int numFields) throws CodingException { switch (msgEncoding) { case UserData.ENCODING_OCTET: case UserData.ENCODING_SHIFT_JIS: case UserData.ENCODING_KOREAN: case UserData.ENCODING_LATIN: case UserData.ENCODING_LATIN_HEBREW: return numFields * 8; case UserData.ENCODING_IA5: case UserData.ENCODING_7BIT_ASCII: case UserData.ENCODING_GSM_7BIT_ALPHABET: return numFields * 7; case UserData.ENCODING_UNICODE_16: return numFields * 16; default: throw new CodingException("unsupported message encoding (" + msgEncoding + ')'); } } /** * CMAS message decoding. * (See TIA-1149-0-1, CMAS over CDMA) * * @param serviceCategory is the service category from the SMS envelope */ private static void decodeCmasUserData(BearerData bData, int serviceCategory) throws BitwiseInputStream.AccessException, CodingException { BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); if (inStream.available() < 8) { throw new CodingException("emergency CB with no CMAE_protocol_version"); } int protocolVersion = inStream.read(8); if (protocolVersion != 0) { throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion); } int messageClass = serviceCategoryToCmasMessageClass(serviceCategory); int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN; int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN; int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; while (inStream.available() >= 16) { int recordType = inStream.read(8); int recordLen = inStream.read(8); switch (recordType) { case 0: // Type 0 elements (Alert text) UserData alertUserData = new UserData(); alertUserData.msgEncoding = inStream.read(5); alertUserData.msgEncodingSet = true; alertUserData.msgType = 0; int numFields; // number of chars to decode switch (alertUserData.msgEncoding) { case UserData.ENCODING_OCTET: case UserData.ENCODING_LATIN: numFields = recordLen - 1; // subtract 1 byte for encoding break; case UserData.ENCODING_IA5: case UserData.ENCODING_7BIT_ASCII: case UserData.ENCODING_GSM_7BIT_ALPHABET: numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding break; case UserData.ENCODING_UNICODE_16: numFields = (recordLen - 1) / 2; break; default: numFields = 0; // unsupported encoding } alertUserData.numFields = numFields; alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5); decodeUserDataPayload(alertUserData, false); bData.userData = alertUserData; break; case 1: // Type 1 elements category = inStream.read(8); responseType = inStream.read(8); severity = inStream.read(4); urgency = inStream.read(4); certainty = inStream.read(4); inStream.skip(recordLen * 8 - 28); break; default: Rlog.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType); inStream.skip(recordLen * 8); break; } } bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity, urgency, certainty); } /** * 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) { return decode(smsData, 0); } private static boolean isCmasAlertCategory(int category) { return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE; } /** * 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. * @param serviceCategory the envelope service category (for CMAS alert handling) * @return an instance of BearerData. */ public static BearerData decode(byte[] smsData, int serviceCategory) { 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; // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8) // as 32th bit is the max bit in int. // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers: // last defined subparam ID is 23 (00010111 = 0x17 = 23). // Only do duplicate subparam ID check if subparam is within defined value as // reserved subparams are just skipped. if ((foundSubparamMask & subparamIdBit) != 0 && (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER && subparamId <= SUBPARAM_ID_LAST_DEFINED)) { throw new CodingException("illegal duplicate subparameter (" + subparamId + ")"); } boolean decodeSuccess; switch (subparamId) { case SUBPARAM_MESSAGE_IDENTIFIER: decodeSuccess = decodeMessageId(bData, inStream); break; case SUBPARAM_USER_DATA: decodeSuccess = decodeUserData(bData, inStream); break; case SUBPARAM_USER_RESPONSE_CODE: decodeSuccess = decodeUserResponseCode(bData, inStream); break; case SUBPARAM_REPLY_OPTION: decodeSuccess = decodeReplyOption(bData, inStream); break; case SUBPARAM_NUMBER_OF_MESSAGES: decodeSuccess = decodeMsgCount(bData, inStream); break; case SUBPARAM_CALLBACK_NUMBER: decodeSuccess = decodeCallbackNumber(bData, inStream); break; case SUBPARAM_MESSAGE_STATUS: decodeSuccess = decodeMsgStatus(bData, inStream); break; case SUBPARAM_MESSAGE_CENTER_TIME_STAMP: decodeSuccess = decodeMsgCenterTimeStamp(bData, inStream); break; case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE: decodeSuccess = decodeValidityAbs(bData, inStream); break; case SUBPARAM_VALIDITY_PERIOD_RELATIVE: decodeSuccess = decodeValidityRel(bData, inStream); break; case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE: decodeSuccess = decodeDeferredDeliveryAbs(bData, inStream); break; case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE: decodeSuccess = decodeDeferredDeliveryRel(bData, inStream); break; case SUBPARAM_PRIVACY_INDICATOR: decodeSuccess = decodePrivacyIndicator(bData, inStream); break; case SUBPARAM_LANGUAGE_INDICATOR: decodeSuccess = decodeLanguageIndicator(bData, inStream); break; case SUBPARAM_MESSAGE_DISPLAY_MODE: decodeSuccess = decodeDisplayMode(bData, inStream); break; case SUBPARAM_PRIORITY_INDICATOR: decodeSuccess = decodePriorityIndicator(bData, inStream); break; case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY: decodeSuccess = decodeMsgDeliveryAlert(bData, inStream); break; case SUBPARAM_MESSAGE_DEPOSIT_INDEX: decodeSuccess = decodeDepositIndex(bData, inStream); break; case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA: decodeSuccess = decodeServiceCategoryProgramData(bData, inStream); break; default: decodeSuccess = decodeReserved(bData, inStream, subparamId); } if (decodeSuccess && (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER && subparamId <= SUBPARAM_ID_LAST_DEFINED)) { foundSubparamMask |= subparamIdBit; } } if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) { throw new CodingException("missing MESSAGE_IDENTIFIER subparam"); } if (bData.userData != null) { if (isCmasAlertCategory(serviceCategory)) { decodeCmasUserData(bData, serviceCategory); } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { if ((foundSubparamMask ^ (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^ (1 << SUBPARAM_USER_DATA)) != 0) { Rlog.e(LOG_TAG, "IS-91 must occur without extra subparams (" + foundSubparamMask + ")"); } decodeIs91(bData); } else { decodeUserDataPayload(bData.userData, bData.hasUserDataHeader); } } return bData; } catch (BitwiseInputStream.AccessException ex) { Rlog.e(LOG_TAG, "BearerData decode failed: " + ex); } catch (CodingException ex) { Rlog.e(LOG_TAG, "BearerData decode failed: " + ex); } return null; } }