blob: 7bc48ca02c19db9d823c3b745a97b89e8106383c [file] [log] [blame]
/*
* 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 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 android.util.Log;
import android.util.SparseIntArray;
import android.telephony.SmsMessage;
import android.text.format.Time;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import com.android.internal.util.HexDump;
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.BitwiseOutputStream;
import android.content.res.Resources;
/**
* An object to encode and decode CDMA SMS bearer data.
*/
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)
* 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_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;
/**
* 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(Time.TIMEZONE_UTC);
}
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;
private static class CodingException extends Exception {
public CodingException(String s) {
super(s);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("BearerData ");
builder.append("{ messageType=" + messageType);
builder.append(", messageId=" + (int)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=" + 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
* @return septet count, or -1 on failure
*/
public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg,
boolean force7BitEncoding) {
TextEncodingDetails ted;
int septets = countAsciiSeptets(msg, force7BitEncoding);
if (septets != -1 && septets <= SmsMessage.MAX_USER_DATA_SEPTETS) {
ted = new TextEncodingDetails();
ted.msgCount = 1;
ted.codeUnitCount = septets;
ted.codeUnitsRemaining = SmsMessage.MAX_USER_DATA_SEPTETS - septets;
ted.codeUnitSize = SmsMessage.ENCODING_7BIT;
} else {
ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength(
msg, force7BitEncoding);
if (ted.msgCount == 1 && ted.codeUnitSize == SmsMessage.ENCODING_7BIT) {
// We don't support single-segment EMS, so calculate for 16-bit
// TODO: Consider supporting single-segment EMS
ted.codeUnitCount = msg.length();
int octets = ted.codeUnitCount * 2;
if (octets > MAX_USER_DATA_BYTES) {
ted.msgCount = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) /
MAX_USER_DATA_BYTES_WITH_HEADER;
ted.codeUnitsRemaining = ((ted.msgCount *
MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2;
} else {
ted.msgCount = 1;
ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2;
}
ted.codeUnitSize = ENCODING_16BIT;
}
}
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);
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 udhPadding = udhBytes % 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 + udhPadding, 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 void encodeUserDataPayload(UserData uData)
throws CodingException
{
if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) {
Log.e(LOG_TAG, "user data with null payloadStr");
uData.payloadStr = "";
}
if (uData.userDataHeader != null) {
encodeEmsUserDataPayload(uData);
return;
}
if (uData.msgEncodingSet) {
if (uData.msgEncoding == UserData.ENCODING_OCTET) {
if (uData.payload == null) {
Log.e(LOG_TAG, "user data with octet encoding but null payload");
uData.payload = new byte[0];
uData.numFields = 0;
} else {
uData.payload = uData.payload;
uData.numFields = uData.payload.length;
}
} else {
if (uData.payloadStr == null) {
Log.e(LOG_TAG, "non-octet user data with null payloadStr");
uData.payloadStr = "";
}
if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true);
uData.payload = gcr.data;
uData.numFields = gcr.septets;
} else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
uData.payload = encode7bitAscii(uData.payloadStr, true);
uData.numFields = uData.payloadStr.length();
} else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
uData.payload = encodeUtf16(uData.payloadStr);
uData.numFields = uData.payloadStr.length();
} else {
throw new CodingException("unsupported user data encoding (" +
uData.msgEncoding + ")");
}
}
} else {
try {
uData.payload = encode7bitAscii(uData.payloadStr, false);
uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
} catch (CodingException ex) {
uData.payload = encodeUtf16(uData.payloadStr);
uData.msgEncoding = UserData.ENCODING_UNICODE_16;
}
uData.numFields = uData.payloadStr.length();
uData.msgEncodingSet = true;
}
}
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 > SmsMessage.MAX_USER_DATA_BYTES) {
throw new CodingException("encoded user data too large (" +
bData.userData.payload.length +
" > " + SmsMessage.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 4bit vs 8bit 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);
}
/**
* 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);
}
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 boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
throws BitwiseInputStream.AccessException, CodingException
{
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)) {
Log.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
}
inStream.skip(paramBits);
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
{
try {
return new String(data, offset, numFields, "UTF-8");
} catch (java.io.UnsupportedEncodingException ex) {
throw new CodingException("UTF-8 decode failed: " + ex);
}
}
private static String decodeUtf16(byte[] data, int offset, int numFields)
throws CodingException
{
// Start reading from the next 16-bit aligned boundry after offset.
int padding = offset % 2;
numFields -= (offset + padding) / 2;
try {
return new String(data, offset, numFields * 2, "utf-16be");
} catch (java.io.UnsupportedEncodingException ex) {
throw new CodingException("UTF-16 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 boundry 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);
if (result == null) {
throw new CodingException("7bit GSM decoding failed");
}
return result;
}
private static String decodeLatin(byte[] data, int offset, int numFields)
throws CodingException
{
try {
return new String(data, offset, numFields - offset, "ISO-8859-1");
} catch (java.io.UnsupportedEncodingException ex) {
throw new CodingException("ISO-8859-1 decode failed: " + ex);
}
}
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;
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;
if ((dataLen > 14) || (dataLen < numFields)) {
throw new CodingException("IS-91 voicemail status 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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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
{
int paramBits = inStream.read(8) * 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 = 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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.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, CodingException
{
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)) {
Log.d(LOG_TAG, "USER_REPONSE_CODE decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
}
inStream.skip(paramBits);
bData.userResponseCodeSet = decodeSuccess;
return decodeSuccess;
}
/**
* 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) {
boolean decodeSuccess = false;
int subparamId = inStream.read(8);
int subparamIdBit = 1 << subparamId;
if ((foundSubparamMask & subparamIdBit) != 0) {
throw new CodingException("illegal duplicate subparameter (" +
subparamId + ")");
}
switch (subparamId) {
case SUBPARAM_MESSAGE_IDENTIFIER:
decodeSuccess = decodeMessageId(bData, inStream);
break;
case SUBPARAM_USER_DATA:
decodeSuccess = decodeUserData(bData, inStream);
break;
case SUBPARAM_USER_REPONSE_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;
default:
throw new CodingException("unsupported bearer data subparameter ("
+ subparamId + ")");
}
if (decodeSuccess) foundSubparamMask |= subparamIdBit;
}
if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
}
if (bData.userData != null) {
if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
if ((foundSubparamMask ^
(1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
(1 << SUBPARAM_USER_DATA))
!= 0) {
Log.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) {
Log.e(LOG_TAG, "BearerData decode failed: " + ex);
} catch (CodingException ex) {
Log.e(LOG_TAG, "BearerData decode failed: " + ex);
}
return null;
}
}