Home | History | Annotate | Download | only in sms
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.telephony.cdma.sms;
     18 
     19 import android.content.res.Resources;
     20 import android.telephony.SmsCbCmasInfo;
     21 import android.telephony.cdma.CdmaSmsCbProgramData;
     22 import android.telephony.cdma.CdmaSmsCbProgramResults;
     23 import android.text.format.Time;
     24 import android.telephony.Rlog;
     25 
     26 import com.android.internal.telephony.GsmAlphabet;
     27 import com.android.internal.telephony.SmsConstants;
     28 import com.android.internal.telephony.SmsHeader;
     29 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
     30 import com.android.internal.telephony.uicc.IccUtils;
     31 import com.android.internal.util.BitwiseInputStream;
     32 import com.android.internal.util.BitwiseOutputStream;
     33 
     34 import java.util.ArrayList;
     35 import java.util.TimeZone;
     36 
     37 /**
     38  * An object to encode and decode CDMA SMS bearer data.
     39  */
     40 public final class BearerData {
     41     private final static String LOG_TAG = "BearerData";
     42 
     43     /**
     44      * Bearer Data Subparameter Identifiers
     45      * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
     46      * NOTE: Commented subparameter types are not implemented.
     47      */
     48     private final static byte SUBPARAM_MESSAGE_IDENTIFIER               = 0x00;
     49     private final static byte SUBPARAM_USER_DATA                        = 0x01;
     50     private final static byte SUBPARAM_USER_RESPONSE_CODE               = 0x02;
     51     private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP        = 0x03;
     52     private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE         = 0x04;
     53     private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE         = 0x05;
     54     private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE  = 0x06;
     55     private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE  = 0x07;
     56     private final static byte SUBPARAM_PRIORITY_INDICATOR               = 0x08;
     57     private final static byte SUBPARAM_PRIVACY_INDICATOR                = 0x09;
     58     private final static byte SUBPARAM_REPLY_OPTION                     = 0x0A;
     59     private final static byte SUBPARAM_NUMBER_OF_MESSAGES               = 0x0B;
     60     private final static byte SUBPARAM_ALERT_ON_MESSAGE_DELIVERY        = 0x0C;
     61     private final static byte SUBPARAM_LANGUAGE_INDICATOR               = 0x0D;
     62     private final static byte SUBPARAM_CALLBACK_NUMBER                  = 0x0E;
     63     private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE             = 0x0F;
     64     //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA      = 0x10;
     65     private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX            = 0x11;
     66     private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA    = 0x12;
     67     private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13;
     68     private final static byte SUBPARAM_MESSAGE_STATUS                   = 0x14;
     69     //private final static byte SUBPARAM_TP_FAILURE_CAUSE                 = 0x15;
     70     //private final static byte SUBPARAM_ENHANCED_VMN                     = 0x16;
     71     //private final static byte SUBPARAM_ENHANCED_VMN_ACK                 = 0x17;
     72 
     73     // All other values after this are reserved.
     74     private final static byte SUBPARAM_ID_LAST_DEFINED                    = 0x17;
     75 
     76     /**
     77      * Supported message types for CDMA SMS messages
     78      * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
     79      */
     80     public static final int MESSAGE_TYPE_DELIVER        = 0x01;
     81     public static final int MESSAGE_TYPE_SUBMIT         = 0x02;
     82     public static final int MESSAGE_TYPE_CANCELLATION   = 0x03;
     83     public static final int MESSAGE_TYPE_DELIVERY_ACK   = 0x04;
     84     public static final int MESSAGE_TYPE_USER_ACK       = 0x05;
     85     public static final int MESSAGE_TYPE_READ_ACK       = 0x06;
     86     public static final int MESSAGE_TYPE_DELIVER_REPORT = 0x07;
     87     public static final int MESSAGE_TYPE_SUBMIT_REPORT  = 0x08;
     88 
     89     public int messageType;
     90 
     91     /**
     92      * 16-bit value indicating the message ID, which increments modulo 65536.
     93      * (Special rules apply for WAP-messages.)
     94      * (See 3GPP2 C.S0015-B, v2, 4.5.1)
     95      */
     96     public int messageId;
     97 
     98     /**
     99      * Supported priority modes for CDMA SMS messages
    100      * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
    101      */
    102     public static final int PRIORITY_NORMAL        = 0x0;
    103     public static final int PRIORITY_INTERACTIVE   = 0x1;
    104     public static final int PRIORITY_URGENT        = 0x2;
    105     public static final int PRIORITY_EMERGENCY     = 0x3;
    106 
    107     public boolean priorityIndicatorSet = false;
    108     public int priority = PRIORITY_NORMAL;
    109 
    110     /**
    111      * Supported privacy modes for CDMA SMS messages
    112      * (See 3GPP2 C.S0015-B, v2.0, table 4.5.10-1)
    113      */
    114     public static final int PRIVACY_NOT_RESTRICTED = 0x0;
    115     public static final int PRIVACY_RESTRICTED     = 0x1;
    116     public static final int PRIVACY_CONFIDENTIAL   = 0x2;
    117     public static final int PRIVACY_SECRET         = 0x3;
    118 
    119     public boolean privacyIndicatorSet = false;
    120     public int privacy = PRIVACY_NOT_RESTRICTED;
    121 
    122     /**
    123      * Supported alert priority modes for CDMA SMS messages
    124      * (See 3GPP2 C.S0015-B, v2.0, table 4.5.13-1)
    125      */
    126     public static final int ALERT_DEFAULT          = 0x0;
    127     public static final int ALERT_LOW_PRIO         = 0x1;
    128     public static final int ALERT_MEDIUM_PRIO      = 0x2;
    129     public static final int ALERT_HIGH_PRIO        = 0x3;
    130 
    131     public boolean alertIndicatorSet = false;
    132     public int alert = ALERT_DEFAULT;
    133 
    134     /**
    135      * Supported display modes for CDMA SMS messages.  Display mode is
    136      * a 2-bit value used to indicate to the mobile station when to
    137      * display the received message.  (See 3GPP2 C.S0015-B, v2,
    138      * 4.5.16)
    139      */
    140     public static final int DISPLAY_MODE_IMMEDIATE      = 0x0;
    141     public static final int DISPLAY_MODE_DEFAULT        = 0x1;
    142     public static final int DISPLAY_MODE_USER           = 0x2;
    143 
    144     public boolean displayModeSet = false;
    145     public int displayMode = DISPLAY_MODE_DEFAULT;
    146 
    147     /**
    148      * Language Indicator values.  NOTE: the spec (3GPP2 C.S0015-B,
    149      * v2, 4.5.14) is ambiguous as to the meaning of this field, as it
    150      * refers to C.R1001-D but that reference has been crossed out.
    151      * It would seem reasonable to assume the values from C.R1001-F
    152      * (table 9.2-1) are to be used instead.
    153      */
    154     public static final int LANGUAGE_UNKNOWN  = 0x00;
    155     public static final int LANGUAGE_ENGLISH  = 0x01;
    156     public static final int LANGUAGE_FRENCH   = 0x02;
    157     public static final int LANGUAGE_SPANISH  = 0x03;
    158     public static final int LANGUAGE_JAPANESE = 0x04;
    159     public static final int LANGUAGE_KOREAN   = 0x05;
    160     public static final int LANGUAGE_CHINESE  = 0x06;
    161     public static final int LANGUAGE_HEBREW   = 0x07;
    162 
    163     public boolean languageIndicatorSet = false;
    164     public int language = LANGUAGE_UNKNOWN;
    165 
    166     /**
    167      * SMS Message Status Codes.  The first component of the Message
    168      * status indicates if an error has occurred and whether the error
    169      * is considered permanent or temporary.  The second component of
    170      * the Message status indicates the cause of the error (if any).
    171      * (See 3GPP2 C.S0015-B, v2.0, 4.5.21)
    172      */
    173     /* no-error codes */
    174     public static final int ERROR_NONE                   = 0x00;
    175     public static final int STATUS_ACCEPTED              = 0x00;
    176     public static final int STATUS_DEPOSITED_TO_INTERNET = 0x01;
    177     public static final int STATUS_DELIVERED             = 0x02;
    178     public static final int STATUS_CANCELLED             = 0x03;
    179     /* temporary-error and permanent-error codes */
    180     public static final int ERROR_TEMPORARY              = 0x02;
    181     public static final int STATUS_NETWORK_CONGESTION    = 0x04;
    182     public static final int STATUS_NETWORK_ERROR         = 0x05;
    183     public static final int STATUS_UNKNOWN_ERROR         = 0x1F;
    184     /* permanent-error codes */
    185     public static final int ERROR_PERMANENT              = 0x03;
    186     public static final int STATUS_CANCEL_FAILED         = 0x06;
    187     public static final int STATUS_BLOCKED_DESTINATION   = 0x07;
    188     public static final int STATUS_TEXT_TOO_LONG         = 0x08;
    189     public static final int STATUS_DUPLICATE_MESSAGE     = 0x09;
    190     public static final int STATUS_INVALID_DESTINATION   = 0x0A;
    191     public static final int STATUS_MESSAGE_EXPIRED       = 0x0D;
    192     /* undefined-status codes */
    193     public static final int ERROR_UNDEFINED              = 0xFF;
    194     public static final int STATUS_UNDEFINED             = 0xFF;
    195 
    196     public boolean messageStatusSet = false;
    197     public int errorClass = ERROR_UNDEFINED;
    198     public int messageStatus = STATUS_UNDEFINED;
    199 
    200     /**
    201      * 1-bit value that indicates whether a User Data Header (UDH) is present.
    202      * (See 3GPP2 C.S0015-B, v2, 4.5.1)
    203      *
    204      * NOTE: during encoding, this value will be set based on the
    205      * presence of a UDH in the structured data, any existing setting
    206      * will be overwritten.
    207      */
    208     public boolean hasUserDataHeader;
    209 
    210     /**
    211      * provides the information for the user data
    212      * (e.g. padding bits, user data, user data header, etc)
    213      * (See 3GPP2 C.S.0015-B, v2, 4.5.2)
    214      */
    215     public UserData userData;
    216 
    217     /**
    218      * The User Response Code subparameter is used in the SMS User
    219      * Acknowledgment Message to respond to previously received short
    220      * messages. This message center-specific element carries the
    221      * identifier of a predefined response. (See 3GPP2 C.S.0015-B, v2,
    222      * 4.5.3)
    223      */
    224     public boolean userResponseCodeSet = false;
    225     public int userResponseCode;
    226 
    227     /**
    228      * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4
    229      */
    230     public static class TimeStamp extends Time {
    231 
    232         public TimeStamp() {
    233             super(TimeZone.getDefault().getID());   // 3GPP2 timestamps use the local timezone
    234         }
    235 
    236         public static TimeStamp fromByteArray(byte[] data) {
    237             TimeStamp ts = new TimeStamp();
    238             // C.S0015-B v2.0, 4.5.4: range is 1996-2095
    239             int year = IccUtils.cdmaBcdByteToInt(data[0]);
    240             if (year > 99 || year < 0) return null;
    241             ts.year = year >= 96 ? year + 1900 : year + 2000;
    242             int month = IccUtils.cdmaBcdByteToInt(data[1]);
    243             if (month < 1 || month > 12) return null;
    244             ts.month = month - 1;
    245             int day = IccUtils.cdmaBcdByteToInt(data[2]);
    246             if (day < 1 || day > 31) return null;
    247             ts.monthDay = day;
    248             int hour = IccUtils.cdmaBcdByteToInt(data[3]);
    249             if (hour < 0 || hour > 23) return null;
    250             ts.hour = hour;
    251             int minute = IccUtils.cdmaBcdByteToInt(data[4]);
    252             if (minute < 0 || minute > 59) return null;
    253             ts.minute = minute;
    254             int second = IccUtils.cdmaBcdByteToInt(data[5]);
    255             if (second < 0 || second > 59) return null;
    256             ts.second = second;
    257             return ts;
    258         }
    259 
    260         @Override
    261         public String toString() {
    262             StringBuilder builder = new StringBuilder();
    263             builder.append("TimeStamp ");
    264             builder.append("{ year=" + year);
    265             builder.append(", month=" + month);
    266             builder.append(", day=" + monthDay);
    267             builder.append(", hour=" + hour);
    268             builder.append(", minute=" + minute);
    269             builder.append(", second=" + second);
    270             builder.append(" }");
    271             return builder.toString();
    272         }
    273     }
    274 
    275     public TimeStamp msgCenterTimeStamp;
    276     public TimeStamp validityPeriodAbsolute;
    277     public TimeStamp deferredDeliveryTimeAbsolute;
    278 
    279     /**
    280      * Relative time is specified as one byte, the value of which
    281      * falls into a series of ranges, as specified below.  The idea is
    282      * that shorter time intervals allow greater precision -- the
    283      * value means minutes from zero until the MINS_LIMIT (inclusive),
    284      * upon which it means hours until the HOURS_LIMIT, and so
    285      * forth. (See 3GPP2 C.S0015-B, v2, 4.5.6-1)
    286      */
    287     public static final int RELATIVE_TIME_MINS_LIMIT      = 143;
    288     public static final int RELATIVE_TIME_HOURS_LIMIT     = 167;
    289     public static final int RELATIVE_TIME_DAYS_LIMIT      = 196;
    290     public static final int RELATIVE_TIME_WEEKS_LIMIT     = 244;
    291     public static final int RELATIVE_TIME_INDEFINITE      = 245;
    292     public static final int RELATIVE_TIME_NOW             = 246;
    293     public static final int RELATIVE_TIME_MOBILE_INACTIVE = 247;
    294     public static final int RELATIVE_TIME_RESERVED        = 248;
    295 
    296     public boolean validityPeriodRelativeSet;
    297     public int validityPeriodRelative;
    298     public boolean deferredDeliveryTimeRelativeSet;
    299     public int deferredDeliveryTimeRelative;
    300 
    301     /**
    302      * The Reply Option subparameter contains 1-bit values which
    303      * indicate whether SMS acknowledgment is requested or not.  (See
    304      * 3GPP2 C.S0015-B, v2, 4.5.11)
    305      */
    306     public boolean userAckReq;
    307     public boolean deliveryAckReq;
    308     public boolean readAckReq;
    309     public boolean reportReq;
    310 
    311     /**
    312      * The Number of Messages subparameter (8-bit value) is a decimal
    313      * number in the 0 to 99 range representing the number of messages
    314      * stored at the Voice Mail System. This element is used by the
    315      * Voice Mail Notification service.  (See 3GPP2 C.S0015-B, v2,
    316      * 4.5.12)
    317      */
    318     public int numberOfMessages;
    319 
    320     /**
    321      * The Message Deposit Index subparameter is assigned by the
    322      * message center as a unique index to the contents of the User
    323      * Data subparameter in each message sent to a particular mobile
    324      * station. The mobile station, when replying to a previously
    325      * received short message which included a Message Deposit Index
    326      * subparameter, may include the Message Deposit Index of the
    327      * received message to indicate to the message center that the
    328      * original contents of the message are to be included in the
    329      * reply.  (See 3GPP2 C.S0015-B, v2, 4.5.18)
    330      */
    331     public int depositIndex;
    332 
    333     /**
    334      * 4-bit or 8-bit value that indicates the number to be dialed in reply to a
    335      * received SMS message.
    336      * (See 3GPP2 C.S0015-B, v2, 4.5.15)
    337      */
    338     public CdmaSmsAddress callbackNumber;
    339 
    340     /**
    341      * CMAS warning notification information.
    342      * @see #decodeCmasUserData(BearerData, int)
    343      */
    344     public SmsCbCmasInfo cmasWarningInfo;
    345 
    346     /**
    347      * The Service Category Program Data subparameter is used to enable and disable
    348      * SMS broadcast service categories to display. If this subparameter is present,
    349      * this field will contain a list of one or more
    350      * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the
    351      * operation(s) to perform.
    352      */
    353     public ArrayList<CdmaSmsCbProgramData> serviceCategoryProgramData;
    354 
    355     /**
    356      * The Service Category Program Results subparameter informs the message center
    357      * of the results of a Service Category Program Data request.
    358      */
    359     public ArrayList<CdmaSmsCbProgramResults> serviceCategoryProgramResults;
    360 
    361 
    362     private static class CodingException extends Exception {
    363         public CodingException(String s) {
    364             super(s);
    365         }
    366     }
    367 
    368     /**
    369      * Returns the language indicator as a two-character ISO 639 string.
    370      * @return a two character ISO 639 language code
    371      */
    372     public String getLanguage() {
    373         return getLanguageCodeForValue(language);
    374     }
    375 
    376     /**
    377      * Converts a CDMA language indicator value to an ISO 639 two character language code.
    378      * @param languageValue the CDMA language value to convert
    379      * @return the two character ISO 639 language code for the specified value, or null if unknown
    380      */
    381     private static String getLanguageCodeForValue(int languageValue) {
    382         switch (languageValue) {
    383             case LANGUAGE_ENGLISH:
    384                 return "en";
    385 
    386             case LANGUAGE_FRENCH:
    387                 return "fr";
    388 
    389             case LANGUAGE_SPANISH:
    390                 return "es";
    391 
    392             case LANGUAGE_JAPANESE:
    393                 return "ja";
    394 
    395             case LANGUAGE_KOREAN:
    396                 return "ko";
    397 
    398             case LANGUAGE_CHINESE:
    399                 return "zh";
    400 
    401             case LANGUAGE_HEBREW:
    402                 return "he";
    403 
    404             default:
    405                 return null;
    406         }
    407     }
    408 
    409     @Override
    410     public String toString() {
    411         StringBuilder builder = new StringBuilder();
    412         builder.append("BearerData ");
    413         builder.append("{ messageType=" + messageType);
    414         builder.append(", messageId=" + messageId);
    415         builder.append(", priority=" + (priorityIndicatorSet ? priority : "unset"));
    416         builder.append(", privacy=" + (privacyIndicatorSet ? privacy : "unset"));
    417         builder.append(", alert=" + (alertIndicatorSet ? alert : "unset"));
    418         builder.append(", displayMode=" + (displayModeSet ? displayMode : "unset"));
    419         builder.append(", language=" + (languageIndicatorSet ? language : "unset"));
    420         builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset"));
    421         builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset"));
    422         builder.append(", msgCenterTimeStamp=" +
    423                 ((msgCenterTimeStamp != null) ? msgCenterTimeStamp : "unset"));
    424         builder.append(", validityPeriodAbsolute=" +
    425                 ((validityPeriodAbsolute != null) ? validityPeriodAbsolute : "unset"));
    426         builder.append(", validityPeriodRelative=" +
    427                 ((validityPeriodRelativeSet) ? validityPeriodRelative : "unset"));
    428         builder.append(", deferredDeliveryTimeAbsolute=" +
    429                 ((deferredDeliveryTimeAbsolute != null) ? deferredDeliveryTimeAbsolute : "unset"));
    430         builder.append(", deferredDeliveryTimeRelative=" +
    431                 ((deferredDeliveryTimeRelativeSet) ? deferredDeliveryTimeRelative : "unset"));
    432         builder.append(", userAckReq=" + userAckReq);
    433         builder.append(", deliveryAckReq=" + deliveryAckReq);
    434         builder.append(", readAckReq=" + readAckReq);
    435         builder.append(", reportReq=" + reportReq);
    436         builder.append(", numberOfMessages=" + numberOfMessages);
    437         builder.append(", callbackNumber=" + callbackNumber);
    438         builder.append(", depositIndex=" + depositIndex);
    439         builder.append(", hasUserDataHeader=" + hasUserDataHeader);
    440         builder.append(", userData=" + userData);
    441         builder.append(" }");
    442         return builder.toString();
    443     }
    444 
    445     private static void encodeMessageId(BearerData bData, BitwiseOutputStream outStream)
    446         throws BitwiseOutputStream.AccessException
    447     {
    448         outStream.write(8, 3);
    449         outStream.write(4, bData.messageType);
    450         outStream.write(8, bData.messageId >> 8);
    451         outStream.write(8, bData.messageId);
    452         outStream.write(1, bData.hasUserDataHeader ? 1 : 0);
    453         outStream.skip(3);
    454     }
    455 
    456     private static int countAsciiSeptets(CharSequence msg, boolean force) {
    457         int msgLen = msg.length();
    458         if (force) return msgLen;
    459         for (int i = 0; i < msgLen; i++) {
    460             if (UserData.charToAscii.get(msg.charAt(i), -1) == -1) {
    461                 return -1;
    462             }
    463         }
    464         return msgLen;
    465     }
    466 
    467     /**
    468      * Calculate the message text encoding length, fragmentation, and other details.
    469      *
    470      * @param msg message text
    471      * @param force7BitEncoding ignore (but still count) illegal characters if true
    472      * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
    473      * @return septet count, or -1 on failure
    474      */
    475     public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg,
    476             boolean force7BitEncoding, boolean isEntireMsg) {
    477         TextEncodingDetails ted;
    478         int septets = countAsciiSeptets(msg, force7BitEncoding);
    479         if (septets != -1 && septets <= SmsConstants.MAX_USER_DATA_SEPTETS) {
    480             ted = new TextEncodingDetails();
    481             ted.msgCount = 1;
    482             ted.codeUnitCount = septets;
    483             ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets;
    484             ted.codeUnitSize = SmsConstants.ENCODING_7BIT;
    485         } else {
    486             ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength(
    487                     msg, force7BitEncoding);
    488             if (ted.msgCount == 1 && ted.codeUnitSize == SmsConstants.ENCODING_7BIT &&
    489                     isEntireMsg) {
    490                 // We don't support single-segment EMS, so calculate for 16-bit
    491                 // TODO: Consider supporting single-segment EMS
    492                 ted.codeUnitCount = msg.length();
    493                 int octets = ted.codeUnitCount * 2;
    494                 if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
    495                     // If EMS is not supported, break down EMS into single segment SMS
    496                     // and add page info " x/y".
    497                     // In the case of UCS2 encoding type, we need 8 bytes for this
    498                     // but we only have 6 bytes from UDH, so truncate the limit for
    499                     // each segment by 2 bytes (1 char).
    500                     int max_user_data_bytes_with_header =
    501                             SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
    502                     if (!android.telephony.SmsMessage.hasEmsSupport()) {
    503                         // make sure total number of segments is less than 10
    504                         if (octets <= 9 * (max_user_data_bytes_with_header - 2))
    505                             max_user_data_bytes_with_header -= 2;
    506                     }
    507 
    508                     ted.msgCount = (octets + (max_user_data_bytes_with_header - 1)) /
    509                             max_user_data_bytes_with_header;
    510                     ted.codeUnitsRemaining = ((ted.msgCount *
    511                             max_user_data_bytes_with_header) - octets) / 2;
    512                 } else {
    513                     ted.msgCount = 1;
    514                     ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets)/2;
    515                 }
    516                 ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
    517             }
    518         }
    519         return ted;
    520     }
    521 
    522     private static byte[] encode7bitAscii(String msg, boolean force)
    523         throws CodingException
    524     {
    525         try {
    526             BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length());
    527             int msgLen = msg.length();
    528             for (int i = 0; i < msgLen; i++) {
    529                 int charCode = UserData.charToAscii.get(msg.charAt(i), -1);
    530                 if (charCode == -1) {
    531                     if (force) {
    532                         outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR);
    533                     } else {
    534                         throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")");
    535                     }
    536                 } else {
    537                     outStream.write(7, charCode);
    538                 }
    539             }
    540             return outStream.toByteArray();
    541         } catch (BitwiseOutputStream.AccessException ex) {
    542             throw new CodingException("7bit ASCII encode failed: " + ex);
    543         }
    544     }
    545 
    546     private static byte[] encodeUtf16(String msg)
    547         throws CodingException
    548     {
    549         try {
    550             return msg.getBytes("utf-16be");
    551         } catch (java.io.UnsupportedEncodingException ex) {
    552             throw new CodingException("UTF-16 encode failed: " + ex);
    553         }
    554     }
    555 
    556     private static class Gsm7bitCodingResult {
    557         int septets;
    558         byte[] data;
    559     }
    560 
    561     private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force)
    562         throws CodingException
    563     {
    564         try {
    565             /*
    566              * TODO(cleanup): It would be nice if GsmAlphabet provided
    567              * an option to produce just the data without prepending
    568              * the septet count, as this function is really just a
    569              * wrapper to strip that off.  Not to mention that the
    570              * septet count is generally known prior to invocation of
    571              * the encoder.  Note that it cannot be derived from the
    572              * resulting array length, since that cannot distinguish
    573              * if the last contains either 1 or 8 valid bits.
    574              *
    575              * TODO(cleanup): The BitwiseXStreams could also be
    576              * extended with byte-wise reversed endianness read/write
    577              * routines to allow a corresponding implementation of
    578              * stringToGsm7BitPacked, and potentially directly support
    579              * access to the main bitwise stream from encode/decode.
    580              */
    581             byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0);
    582             Gsm7bitCodingResult result = new Gsm7bitCodingResult();
    583             result.data = new byte[fullData.length - 1];
    584             System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1);
    585             result.septets = fullData[0] & 0x00FF;
    586             return result;
    587         } catch (com.android.internal.telephony.EncodeException ex) {
    588             throw new CodingException("7bit GSM encode failed: " + ex);
    589         }
    590     }
    591 
    592     private static void encode7bitEms(UserData uData, byte[] udhData, boolean force)
    593         throws CodingException
    594     {
    595         int udhBytes = udhData.length + 1;  // Add length octet.
    596         int udhSeptets = ((udhBytes * 8) + 6) / 7;
    597         Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force);
    598         uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
    599         uData.msgEncodingSet = true;
    600         uData.numFields = gcr.septets;
    601         uData.payload = gcr.data;
    602         uData.payload[0] = (byte)udhData.length;
    603         System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
    604     }
    605 
    606     private static void encode16bitEms(UserData uData, byte[] udhData)
    607         throws CodingException
    608     {
    609         byte[] payload = encodeUtf16(uData.payloadStr);
    610         int udhBytes = udhData.length + 1;  // Add length octet.
    611         int udhCodeUnits = (udhBytes + 1) / 2;
    612         int payloadCodeUnits = payload.length / 2;
    613         uData.msgEncoding = UserData.ENCODING_UNICODE_16;
    614         uData.msgEncodingSet = true;
    615         uData.numFields = udhCodeUnits + payloadCodeUnits;
    616         uData.payload = new byte[uData.numFields * 2];
    617         uData.payload[0] = (byte)udhData.length;
    618         System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
    619         System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length);
    620     }
    621 
    622     private static void encodeEmsUserDataPayload(UserData uData)
    623         throws CodingException
    624     {
    625         byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader);
    626         if (uData.msgEncodingSet) {
    627             if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
    628                 encode7bitEms(uData, headerData, true);
    629             } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
    630                 encode16bitEms(uData, headerData);
    631             } else {
    632                 throw new CodingException("unsupported EMS user data encoding (" +
    633                                           uData.msgEncoding + ")");
    634             }
    635         } else {
    636             try {
    637                 encode7bitEms(uData, headerData, false);
    638             } catch (CodingException ex) {
    639                 encode16bitEms(uData, headerData);
    640             }
    641         }
    642     }
    643 
    644     private static byte[] encodeShiftJis(String msg) throws CodingException {
    645         try {
    646             return msg.getBytes("Shift_JIS");
    647         } catch (java.io.UnsupportedEncodingException ex) {
    648             throw new CodingException("Shift-JIS encode failed: " + ex);
    649         }
    650     }
    651 
    652     private static void encodeUserDataPayload(UserData uData)
    653         throws CodingException
    654     {
    655         if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) {
    656             Rlog.e(LOG_TAG, "user data with null payloadStr");
    657             uData.payloadStr = "";
    658         }
    659 
    660         if (uData.userDataHeader != null) {
    661             encodeEmsUserDataPayload(uData);
    662             return;
    663         }
    664 
    665         if (uData.msgEncodingSet) {
    666             if (uData.msgEncoding == UserData.ENCODING_OCTET) {
    667                 if (uData.payload == null) {
    668                     Rlog.e(LOG_TAG, "user data with octet encoding but null payload");
    669                     uData.payload = new byte[0];
    670                     uData.numFields = 0;
    671                 } else {
    672                     uData.numFields = uData.payload.length;
    673                 }
    674             } else {
    675                 if (uData.payloadStr == null) {
    676                     Rlog.e(LOG_TAG, "non-octet user data with null payloadStr");
    677                     uData.payloadStr = "";
    678                 }
    679                 if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
    680                     Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true);
    681                     uData.payload = gcr.data;
    682                     uData.numFields = gcr.septets;
    683                 } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
    684                     uData.payload = encode7bitAscii(uData.payloadStr, true);
    685                     uData.numFields = uData.payloadStr.length();
    686                 } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
    687                     uData.payload = encodeUtf16(uData.payloadStr);
    688                     uData.numFields = uData.payloadStr.length();
    689                 } else if (uData.msgEncoding == UserData.ENCODING_SHIFT_JIS) {
    690                     uData.payload = encodeShiftJis(uData.payloadStr);
    691                     uData.numFields = uData.payload.length;
    692                 } else {
    693                     throw new CodingException("unsupported user data encoding (" +
    694                                               uData.msgEncoding + ")");
    695                 }
    696             }
    697         } else {
    698             try {
    699                 uData.payload = encode7bitAscii(uData.payloadStr, false);
    700                 uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
    701             } catch (CodingException ex) {
    702                 uData.payload = encodeUtf16(uData.payloadStr);
    703                 uData.msgEncoding = UserData.ENCODING_UNICODE_16;
    704             }
    705             uData.numFields = uData.payloadStr.length();
    706             uData.msgEncodingSet = true;
    707         }
    708     }
    709 
    710     private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream)
    711         throws BitwiseOutputStream.AccessException, CodingException
    712     {
    713         /*
    714          * TODO(cleanup): Do we really need to set userData.payload as
    715          * a side effect of encoding?  If not, we could avoid data
    716          * copies by passing outStream directly.
    717          */
    718         encodeUserDataPayload(bData.userData);
    719         bData.hasUserDataHeader = bData.userData.userDataHeader != null;
    720 
    721         if (bData.userData.payload.length > SmsConstants.MAX_USER_DATA_BYTES) {
    722             throw new CodingException("encoded user data too large (" +
    723                                       bData.userData.payload.length +
    724                                       " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)");
    725         }
    726 
    727         /*
    728          * TODO(cleanup): figure out what the right answer is WRT paddingBits field
    729          *
    730          *   userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7);
    731          *   userData.paddingBits = 0; // XXX this seems better, but why?
    732          *
    733          */
    734         int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits;
    735         int paramBits = dataBits + 13;
    736         if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
    737             (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
    738             paramBits += 8;
    739         }
    740         int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
    741         int paddingBits = (paramBytes * 8) - paramBits;
    742         outStream.write(8, paramBytes);
    743         outStream.write(5, bData.userData.msgEncoding);
    744         if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
    745             (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
    746             outStream.write(8, bData.userData.msgType);
    747         }
    748         outStream.write(8, bData.userData.numFields);
    749         outStream.writeByteArray(dataBits, bData.userData.payload);
    750         if (paddingBits > 0) outStream.write(paddingBits, 0);
    751     }
    752 
    753     private static void encodeReplyOption(BearerData bData, BitwiseOutputStream outStream)
    754         throws BitwiseOutputStream.AccessException
    755     {
    756         outStream.write(8, 1);
    757         outStream.write(1, bData.userAckReq     ? 1 : 0);
    758         outStream.write(1, bData.deliveryAckReq ? 1 : 0);
    759         outStream.write(1, bData.readAckReq     ? 1 : 0);
    760         outStream.write(1, bData.reportReq      ? 1 : 0);
    761         outStream.write(4, 0);
    762     }
    763 
    764     private static byte[] encodeDtmfSmsAddress(String address) {
    765         int digits = address.length();
    766         int dataBits = digits * 4;
    767         int dataBytes = (dataBits / 8);
    768         dataBytes += (dataBits % 8) > 0 ? 1 : 0;
    769         byte[] rawData = new byte[dataBytes];
    770         for (int i = 0; i < digits; i++) {
    771             char c = address.charAt(i);
    772             int val = 0;
    773             if ((c >= '1') && (c <= '9')) val = c - '0';
    774             else if (c == '0') val = 10;
    775             else if (c == '*') val = 11;
    776             else if (c == '#') val = 12;
    777             else return null;
    778             rawData[i / 2] |= val << (4 - ((i % 2) * 4));
    779         }
    780         return rawData;
    781     }
    782 
    783     /*
    784      * TODO(cleanup): CdmaSmsAddress encoding should make use of
    785      * CdmaSmsAddress.parse provided that DTMF encoding is unified,
    786      * and the difference in 4-bit vs. 8-bit is resolved.
    787      */
    788 
    789     private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException {
    790         if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
    791             try {
    792                 addr.origBytes = addr.address.getBytes("US-ASCII");
    793             } catch (java.io.UnsupportedEncodingException ex) {
    794                 throw new CodingException("invalid SMS address, cannot convert to ASCII");
    795             }
    796         } else {
    797             addr.origBytes = encodeDtmfSmsAddress(addr.address);
    798         }
    799     }
    800 
    801     private static void encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream)
    802         throws BitwiseOutputStream.AccessException, CodingException
    803     {
    804         CdmaSmsAddress addr = bData.callbackNumber;
    805         encodeCdmaSmsAddress(addr);
    806         int paramBits = 9;
    807         int dataBits = 0;
    808         if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
    809             paramBits += 7;
    810             dataBits = addr.numberOfDigits * 8;
    811         } else {
    812             dataBits = addr.numberOfDigits * 4;
    813         }
    814         paramBits += dataBits;
    815         int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
    816         int paddingBits = (paramBytes * 8) - paramBits;
    817         outStream.write(8, paramBytes);
    818         outStream.write(1, addr.digitMode);
    819         if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
    820             outStream.write(3, addr.ton);
    821             outStream.write(4, addr.numberPlan);
    822         }
    823         outStream.write(8, addr.numberOfDigits);
    824         outStream.writeByteArray(dataBits, addr.origBytes);
    825         if (paddingBits > 0) outStream.write(paddingBits, 0);
    826     }
    827 
    828     private static void encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream)
    829         throws BitwiseOutputStream.AccessException
    830     {
    831         outStream.write(8, 1);
    832         outStream.write(2, bData.errorClass);
    833         outStream.write(6, bData.messageStatus);
    834     }
    835 
    836     private static void encodeMsgCount(BearerData bData, BitwiseOutputStream outStream)
    837         throws BitwiseOutputStream.AccessException
    838     {
    839         outStream.write(8, 1);
    840         outStream.write(8, bData.numberOfMessages);
    841     }
    842 
    843     private static void encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream)
    844         throws BitwiseOutputStream.AccessException
    845     {
    846         outStream.write(8, 1);
    847         outStream.write(8, bData.validityPeriodRelative);
    848     }
    849 
    850     private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream)
    851         throws BitwiseOutputStream.AccessException
    852     {
    853         outStream.write(8, 1);
    854         outStream.write(2, bData.privacy);
    855         outStream.skip(6);
    856     }
    857 
    858     private static void encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream)
    859         throws BitwiseOutputStream.AccessException
    860     {
    861         outStream.write(8, 1);
    862         outStream.write(8, bData.language);
    863     }
    864 
    865     private static void encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream)
    866         throws BitwiseOutputStream.AccessException
    867     {
    868         outStream.write(8, 1);
    869         outStream.write(2, bData.displayMode);
    870         outStream.skip(6);
    871     }
    872 
    873     private static void encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream)
    874         throws BitwiseOutputStream.AccessException
    875     {
    876         outStream.write(8, 1);
    877         outStream.write(2, bData.priority);
    878         outStream.skip(6);
    879     }
    880 
    881     private static void encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream)
    882         throws BitwiseOutputStream.AccessException
    883     {
    884         outStream.write(8, 1);
    885         outStream.write(2, bData.alert);
    886         outStream.skip(6);
    887     }
    888 
    889     private static void encodeScpResults(BearerData bData, BitwiseOutputStream outStream)
    890         throws BitwiseOutputStream.AccessException
    891     {
    892         ArrayList<CdmaSmsCbProgramResults> results = bData.serviceCategoryProgramResults;
    893         outStream.write(8, (results.size() * 4));   // 4 octets per program result
    894         for (CdmaSmsCbProgramResults result : results) {
    895             int category = result.getCategory();
    896             outStream.write(8, category >> 8);
    897             outStream.write(8, category);
    898             outStream.write(8, result.getLanguage());
    899             outStream.write(4, result.getCategoryResult());
    900             outStream.skip(4);
    901         }
    902     }
    903 
    904     /**
    905      * Create serialized representation for BearerData object.
    906      * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
    907      *
    908      * @param bData an instance of BearerData.
    909      *
    910      * @return byte array of raw encoded SMS bearer data.
    911      */
    912     public static byte[] encode(BearerData bData) {
    913         bData.hasUserDataHeader = ((bData.userData != null) &&
    914                 (bData.userData.userDataHeader != null));
    915         try {
    916             BitwiseOutputStream outStream = new BitwiseOutputStream(200);
    917             outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
    918             encodeMessageId(bData, outStream);
    919             if (bData.userData != null) {
    920                 outStream.write(8, SUBPARAM_USER_DATA);
    921                 encodeUserData(bData, outStream);
    922             }
    923             if (bData.callbackNumber != null) {
    924                 outStream.write(8, SUBPARAM_CALLBACK_NUMBER);
    925                 encodeCallbackNumber(bData, outStream);
    926             }
    927             if (bData.userAckReq || bData.deliveryAckReq || bData.readAckReq || bData.reportReq) {
    928                 outStream.write(8, SUBPARAM_REPLY_OPTION);
    929                 encodeReplyOption(bData, outStream);
    930             }
    931             if (bData.numberOfMessages != 0) {
    932                 outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES);
    933                 encodeMsgCount(bData, outStream);
    934             }
    935             if (bData.validityPeriodRelativeSet) {
    936                 outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE);
    937                 encodeValidityPeriodRel(bData, outStream);
    938             }
    939             if (bData.privacyIndicatorSet) {
    940                 outStream.write(8, SUBPARAM_PRIVACY_INDICATOR);
    941                 encodePrivacyIndicator(bData, outStream);
    942             }
    943             if (bData.languageIndicatorSet) {
    944                 outStream.write(8, SUBPARAM_LANGUAGE_INDICATOR);
    945                 encodeLanguageIndicator(bData, outStream);
    946             }
    947             if (bData.displayModeSet) {
    948                 outStream.write(8, SUBPARAM_MESSAGE_DISPLAY_MODE);
    949                 encodeDisplayMode(bData, outStream);
    950             }
    951             if (bData.priorityIndicatorSet) {
    952                 outStream.write(8, SUBPARAM_PRIORITY_INDICATOR);
    953                 encodePriorityIndicator(bData, outStream);
    954             }
    955             if (bData.alertIndicatorSet) {
    956                 outStream.write(8, SUBPARAM_ALERT_ON_MESSAGE_DELIVERY);
    957                 encodeMsgDeliveryAlert(bData, outStream);
    958             }
    959             if (bData.messageStatusSet) {
    960                 outStream.write(8, SUBPARAM_MESSAGE_STATUS);
    961                 encodeMsgStatus(bData, outStream);
    962             }
    963             if (bData.serviceCategoryProgramResults != null) {
    964                 outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS);
    965                 encodeScpResults(bData, outStream);
    966             }
    967             return outStream.toByteArray();
    968         } catch (BitwiseOutputStream.AccessException ex) {
    969             Rlog.e(LOG_TAG, "BearerData encode failed: " + ex);
    970         } catch (CodingException ex) {
    971             Rlog.e(LOG_TAG, "BearerData encode failed: " + ex);
    972         }
    973         return null;
    974    }
    975 
    976     private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
    977         throws BitwiseInputStream.AccessException {
    978         final int EXPECTED_PARAM_SIZE = 3 * 8;
    979         boolean decodeSuccess = false;
    980         int paramBits = inStream.read(8) * 8;
    981         if (paramBits >= EXPECTED_PARAM_SIZE) {
    982             paramBits -= EXPECTED_PARAM_SIZE;
    983             decodeSuccess = true;
    984             bData.messageType = inStream.read(4);
    985             bData.messageId = inStream.read(8) << 8;
    986             bData.messageId |= inStream.read(8);
    987             bData.hasUserDataHeader = (inStream.read(1) == 1);
    988             inStream.skip(3);
    989         }
    990         if ((! decodeSuccess) || (paramBits > 0)) {
    991             Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
    992                       (decodeSuccess ? "succeeded" : "failed") +
    993                       " (extra bits = " + paramBits + ")");
    994         }
    995         inStream.skip(paramBits);
    996         return decodeSuccess;
    997     }
    998 
    999     private static boolean decodeReserved(
   1000             BearerData bData, BitwiseInputStream inStream, int subparamId)
   1001         throws BitwiseInputStream.AccessException, CodingException
   1002     {
   1003         boolean decodeSuccess = false;
   1004         int subparamLen = inStream.read(8); // SUBPARAM_LEN
   1005         int paramBits = subparamLen * 8;
   1006         if (paramBits <= inStream.available()) {
   1007             decodeSuccess = true;
   1008             inStream.skip(paramBits);
   1009         }
   1010         Rlog.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode "
   1011                 + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")");
   1012         if (!decodeSuccess) {
   1013             throw new CodingException("RESERVED bearer data subparameter " + subparamId
   1014                     + " had invalid SUBPARAM_LEN " + subparamLen);
   1015         }
   1016 
   1017         return decodeSuccess;
   1018     }
   1019 
   1020     private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
   1021         throws BitwiseInputStream.AccessException
   1022     {
   1023         int paramBits = inStream.read(8) * 8;
   1024         bData.userData = new UserData();
   1025         bData.userData.msgEncoding = inStream.read(5);
   1026         bData.userData.msgEncodingSet = true;
   1027         bData.userData.msgType = 0;
   1028         int consumedBits = 5;
   1029         if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
   1030             (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
   1031             bData.userData.msgType = inStream.read(8);
   1032             consumedBits += 8;
   1033         }
   1034         bData.userData.numFields = inStream.read(8);
   1035         consumedBits += 8;
   1036         int dataBits = paramBits - consumedBits;
   1037         bData.userData.payload = inStream.readByteArray(dataBits);
   1038         return true;
   1039     }
   1040 
   1041     private static String decodeUtf8(byte[] data, int offset, int numFields)
   1042         throws CodingException
   1043     {
   1044         return decodeCharset(data, offset, numFields, 1, "UTF-8");
   1045     }
   1046 
   1047     private static String decodeUtf16(byte[] data, int offset, int numFields)
   1048         throws CodingException
   1049     {
   1050         // Subtract header and possible padding byte (at end) from num fields.
   1051         int padding = offset % 2;
   1052         numFields -= (offset + padding) / 2;
   1053         return decodeCharset(data, offset, numFields, 2, "utf-16be");
   1054     }
   1055 
   1056     private static String decodeCharset(byte[] data, int offset, int numFields, int width,
   1057             String charset) throws CodingException
   1058     {
   1059         if (numFields < 0 || (numFields * width + offset) > data.length) {
   1060             // Try to decode the max number of characters in payload
   1061             int padding = offset % width;
   1062             int maxNumFields = (data.length - offset - padding) / width;
   1063             if (maxNumFields < 0) {
   1064                 throw new CodingException(charset + " decode failed: offset out of range");
   1065             }
   1066             Rlog.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
   1067                     + numFields + " data.length = " + data.length + " maxNumFields = "
   1068                     + maxNumFields);
   1069             numFields = maxNumFields;
   1070         }
   1071         try {
   1072             return new String(data, offset, numFields * width, charset);
   1073         } catch (java.io.UnsupportedEncodingException ex) {
   1074             throw new CodingException(charset + " decode failed: " + ex);
   1075         }
   1076     }
   1077 
   1078     private static String decode7bitAscii(byte[] data, int offset, int numFields)
   1079         throws CodingException
   1080     {
   1081         try {
   1082             offset *= 8;
   1083             StringBuffer strBuf = new StringBuffer(numFields);
   1084             BitwiseInputStream inStream = new BitwiseInputStream(data);
   1085             int wantedBits = (offset * 8) + (numFields * 7);
   1086             if (inStream.available() < wantedBits) {
   1087                 throw new CodingException("insufficient data (wanted " + wantedBits +
   1088                                           " bits, but only have " + inStream.available() + ")");
   1089             }
   1090             inStream.skip(offset);
   1091             for (int i = 0; i < numFields; i++) {
   1092                 int charCode = inStream.read(7);
   1093                 if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) &&
   1094                         (charCode <= UserData.ASCII_MAP_MAX_INDEX)) {
   1095                     strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]);
   1096                 } else if (charCode == UserData.ASCII_NL_INDEX) {
   1097                     strBuf.append('\n');
   1098                 } else if (charCode == UserData.ASCII_CR_INDEX) {
   1099                     strBuf.append('\r');
   1100                 } else {
   1101                     /* For other charCodes, they are unprintable, and so simply use SPACE. */
   1102                     strBuf.append(' ');
   1103                 }
   1104             }
   1105             return strBuf.toString();
   1106         } catch (BitwiseInputStream.AccessException ex) {
   1107             throw new CodingException("7bit ASCII decode failed: " + ex);
   1108         }
   1109     }
   1110 
   1111     private static String decode7bitGsm(byte[] data, int offset, int numFields)
   1112         throws CodingException
   1113     {
   1114         // Start reading from the next 7-bit aligned boundary after offset.
   1115         int offsetBits = offset * 8;
   1116         int offsetSeptets = (offsetBits + 6) / 7;
   1117         numFields -= offsetSeptets;
   1118         int paddingBits = (offsetSeptets * 7) - offsetBits;
   1119         String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits,
   1120                 0, 0);
   1121         if (result == null) {
   1122             throw new CodingException("7bit GSM decoding failed");
   1123         }
   1124         return result;
   1125     }
   1126 
   1127     private static String decodeLatin(byte[] data, int offset, int numFields)
   1128         throws CodingException
   1129     {
   1130         return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
   1131     }
   1132 
   1133     private static String decodeShiftJis(byte[] data, int offset, int numFields)
   1134         throws CodingException
   1135     {
   1136         return decodeCharset(data, offset, numFields, 1, "Shift_JIS");
   1137     }
   1138 
   1139     private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)
   1140         throws CodingException
   1141     {
   1142         int offset = 0;
   1143         if (hasUserDataHeader) {
   1144             int udhLen = userData.payload[0] & 0x00FF;
   1145             offset += udhLen + 1;
   1146             byte[] headerData = new byte[udhLen];
   1147             System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
   1148             userData.userDataHeader = SmsHeader.fromByteArray(headerData);
   1149         }
   1150         switch (userData.msgEncoding) {
   1151         case UserData.ENCODING_OCTET:
   1152             /*
   1153             *  Octet decoding depends on the carrier service.
   1154             */
   1155             boolean decodingtypeUTF8 = Resources.getSystem()
   1156                     .getBoolean(com.android.internal.R.bool.config_sms_utf8_support);
   1157 
   1158             // Strip off any padding bytes, meaning any differences between the length of the
   1159             // array and the target length specified by numFields.  This is to avoid any
   1160             // confusion by code elsewhere that only considers the payload array length.
   1161             byte[] payload = new byte[userData.numFields];
   1162             int copyLen = userData.numFields < userData.payload.length
   1163                     ? userData.numFields : userData.payload.length;
   1164 
   1165             System.arraycopy(userData.payload, 0, payload, 0, copyLen);
   1166             userData.payload = payload;
   1167 
   1168             if (!decodingtypeUTF8) {
   1169                 // There are many devices in the market that send 8bit text sms (latin encoded) as
   1170                 // octet encoded.
   1171                 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
   1172             } else {
   1173                 userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
   1174             }
   1175             break;
   1176 
   1177         case UserData.ENCODING_IA5:
   1178         case UserData.ENCODING_7BIT_ASCII:
   1179             userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
   1180             break;
   1181         case UserData.ENCODING_UNICODE_16:
   1182             userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields);
   1183             break;
   1184         case UserData.ENCODING_GSM_7BIT_ALPHABET:
   1185             userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields);
   1186             break;
   1187         case UserData.ENCODING_LATIN:
   1188             userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
   1189             break;
   1190         case UserData.ENCODING_SHIFT_JIS:
   1191             userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields);
   1192             break;
   1193         default:
   1194             throw new CodingException("unsupported user data encoding ("
   1195                                       + userData.msgEncoding + ")");
   1196         }
   1197     }
   1198 
   1199     /**
   1200      * IS-91 Voice Mail message decoding
   1201      * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
   1202      * (For character encodings, see TIA/EIA/IS-91, Annex B)
   1203      *
   1204      * Protocol Summary: The user data payload may contain 3-14
   1205      * characters.  The first two characters are parsed as a number
   1206      * and indicate the number of voicemails.  The third character is
   1207      * either a SPACE or '!' to indicate normal or urgent priority,
   1208      * respectively.  Any following characters are treated as normal
   1209      * text user data payload.
   1210      *
   1211      * Note that the characters encoding is 6-bit packed.
   1212      */
   1213     private static void decodeIs91VoicemailStatus(BearerData bData)
   1214         throws BitwiseInputStream.AccessException, CodingException
   1215     {
   1216         BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
   1217         int dataLen = inStream.available() / 6;  // 6-bit packed character encoding.
   1218         int numFields = bData.userData.numFields;
   1219         if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
   1220             throw new CodingException("IS-91 voicemail status decoding failed");
   1221         }
   1222         try {
   1223             StringBuffer strbuf = new StringBuffer(dataLen);
   1224             while (inStream.available() >= 6) {
   1225                 strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
   1226             }
   1227             String data = strbuf.toString();
   1228             bData.numberOfMessages = Integer.parseInt(data.substring(0, 2));
   1229             char prioCode = data.charAt(2);
   1230             if (prioCode == ' ') {
   1231                 bData.priority = PRIORITY_NORMAL;
   1232             } else if (prioCode == '!') {
   1233                 bData.priority = PRIORITY_URGENT;
   1234             } else {
   1235                 throw new CodingException("IS-91 voicemail status decoding failed: " +
   1236                         "illegal priority setting (" + prioCode + ")");
   1237             }
   1238             bData.priorityIndicatorSet = true;
   1239             bData.userData.payloadStr = data.substring(3, numFields - 3);
   1240        } catch (java.lang.NumberFormatException ex) {
   1241             throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
   1242         } catch (java.lang.IndexOutOfBoundsException ex) {
   1243             throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
   1244         }
   1245     }
   1246 
   1247     /**
   1248      * IS-91 Short Message decoding
   1249      * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
   1250      * (For character encodings, see TIA/EIA/IS-91, Annex B)
   1251      *
   1252      * Protocol Summary: The user data payload may contain 1-14
   1253      * characters, which are treated as normal text user data payload.
   1254      * Note that the characters encoding is 6-bit packed.
   1255      */
   1256     private static void decodeIs91ShortMessage(BearerData bData)
   1257         throws BitwiseInputStream.AccessException, CodingException
   1258     {
   1259         BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
   1260         int dataLen = inStream.available() / 6;  // 6-bit packed character encoding.
   1261         int numFields = bData.userData.numFields;
   1262         // dataLen may be > 14 characters due to octet padding
   1263         if ((numFields > 14) || (dataLen < numFields)) {
   1264             throw new CodingException("IS-91 short message decoding failed");
   1265         }
   1266         StringBuffer strbuf = new StringBuffer(dataLen);
   1267         for (int i = 0; i < numFields; i++) {
   1268             strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
   1269         }
   1270         bData.userData.payloadStr = strbuf.toString();
   1271     }
   1272 
   1273     /**
   1274      * IS-91 CLI message (callback number) decoding
   1275      * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
   1276      *
   1277      * Protocol Summary: The data payload may contain 1-32 digits,
   1278      * encoded using standard 4-bit DTMF, which are treated as a
   1279      * callback number.
   1280      */
   1281     private static void decodeIs91Cli(BearerData bData) throws CodingException {
   1282         BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
   1283         int dataLen = inStream.available() / 4;  // 4-bit packed DTMF digit encoding.
   1284         int numFields = bData.userData.numFields;
   1285         if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
   1286             throw new CodingException("IS-91 voicemail status decoding failed");
   1287         }
   1288         CdmaSmsAddress addr = new CdmaSmsAddress();
   1289         addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF;
   1290         addr.origBytes = bData.userData.payload;
   1291         addr.numberOfDigits = (byte)numFields;
   1292         decodeSmsAddress(addr);
   1293         bData.callbackNumber = addr;
   1294     }
   1295 
   1296     private static void decodeIs91(BearerData bData)
   1297         throws BitwiseInputStream.AccessException, CodingException
   1298     {
   1299         switch (bData.userData.msgType) {
   1300         case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS:
   1301             decodeIs91VoicemailStatus(bData);
   1302             break;
   1303         case UserData.IS91_MSG_TYPE_CLI:
   1304             decodeIs91Cli(bData);
   1305             break;
   1306         case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL:
   1307         case UserData.IS91_MSG_TYPE_SHORT_MESSAGE:
   1308             decodeIs91ShortMessage(bData);
   1309             break;
   1310         default:
   1311             throw new CodingException("unsupported IS-91 message type (" +
   1312                     bData.userData.msgType + ")");
   1313         }
   1314     }
   1315 
   1316     private static boolean decodeReplyOption(BearerData bData, BitwiseInputStream inStream)
   1317         throws BitwiseInputStream.AccessException {
   1318         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1319         boolean decodeSuccess = false;
   1320         int paramBits = inStream.read(8) * 8;
   1321         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1322             paramBits -= EXPECTED_PARAM_SIZE;
   1323             decodeSuccess = true;
   1324             bData.userAckReq     = (inStream.read(1) == 1);
   1325             bData.deliveryAckReq = (inStream.read(1) == 1);
   1326             bData.readAckReq     = (inStream.read(1) == 1);
   1327             bData.reportReq      = (inStream.read(1) == 1);
   1328             inStream.skip(4);
   1329         }
   1330         if ((! decodeSuccess) || (paramBits > 0)) {
   1331             Rlog.d(LOG_TAG, "REPLY_OPTION decode " +
   1332                       (decodeSuccess ? "succeeded" : "failed") +
   1333                       " (extra bits = " + paramBits + ")");
   1334         }
   1335         inStream.skip(paramBits);
   1336         return decodeSuccess;
   1337     }
   1338 
   1339     private static boolean decodeMsgCount(BearerData bData, BitwiseInputStream inStream)
   1340         throws BitwiseInputStream.AccessException {
   1341         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1342         boolean decodeSuccess = false;
   1343         int paramBits = inStream.read(8) * 8;
   1344         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1345             paramBits -= EXPECTED_PARAM_SIZE;
   1346             decodeSuccess = true;
   1347             bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8));
   1348         }
   1349         if ((! decodeSuccess) || (paramBits > 0)) {
   1350             Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " +
   1351                       (decodeSuccess ? "succeeded" : "failed") +
   1352                       " (extra bits = " + paramBits + ")");
   1353         }
   1354         inStream.skip(paramBits);
   1355         return decodeSuccess;
   1356     }
   1357 
   1358     private static boolean decodeDepositIndex(BearerData bData, BitwiseInputStream inStream)
   1359         throws BitwiseInputStream.AccessException {
   1360         final int EXPECTED_PARAM_SIZE = 2 * 8;
   1361         boolean decodeSuccess = false;
   1362         int paramBits = inStream.read(8) * 8;
   1363         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1364             paramBits -= EXPECTED_PARAM_SIZE;
   1365             decodeSuccess = true;
   1366             bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8);
   1367         }
   1368         if ((! decodeSuccess) || (paramBits > 0)) {
   1369             Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " +
   1370                       (decodeSuccess ? "succeeded" : "failed") +
   1371                       " (extra bits = " + paramBits + ")");
   1372         }
   1373         inStream.skip(paramBits);
   1374         return decodeSuccess;
   1375     }
   1376 
   1377     private static String decodeDtmfSmsAddress(byte[] rawData, int numFields)
   1378         throws CodingException
   1379     {
   1380         /* DTMF 4-bit digit encoding, defined in at
   1381          * 3GPP2 C.S005-D, v2.0, table 2.7.1.3.2.4-4 */
   1382         StringBuffer strBuf = new StringBuffer(numFields);
   1383         for (int i = 0; i < numFields; i++) {
   1384             int val = 0x0F & (rawData[i / 2] >>> (4 - ((i % 2) * 4)));
   1385             if ((val >= 1) && (val <= 9)) strBuf.append(Integer.toString(val, 10));
   1386             else if (val == 10) strBuf.append('0');
   1387             else if (val == 11) strBuf.append('*');
   1388             else if (val == 12) strBuf.append('#');
   1389             else throw new CodingException("invalid SMS address DTMF code (" + val + ")");
   1390         }
   1391         return strBuf.toString();
   1392     }
   1393 
   1394     private static void decodeSmsAddress(CdmaSmsAddress addr) throws CodingException {
   1395         if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
   1396             try {
   1397                 /* As specified in 3GPP2 C.S0015-B, v2, 4.5.15 -- actually
   1398                  * just 7-bit ASCII encoding, with the MSB being zero. */
   1399                 addr.address = new String(addr.origBytes, 0, addr.origBytes.length, "US-ASCII");
   1400             } catch (java.io.UnsupportedEncodingException ex) {
   1401                 throw new CodingException("invalid SMS address ASCII code");
   1402             }
   1403         } else {
   1404             addr.address = decodeDtmfSmsAddress(addr.origBytes, addr.numberOfDigits);
   1405         }
   1406     }
   1407 
   1408     private static boolean decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream)
   1409         throws BitwiseInputStream.AccessException, CodingException
   1410     {
   1411         final int EXPECTED_PARAM_SIZE = 1 * 8; //at least
   1412         int paramBits = inStream.read(8) * 8;
   1413         if (paramBits < EXPECTED_PARAM_SIZE) {
   1414             inStream.skip(paramBits);
   1415             return false;
   1416         }
   1417         CdmaSmsAddress addr = new CdmaSmsAddress();
   1418         addr.digitMode = inStream.read(1);
   1419         byte fieldBits = 4;
   1420         byte consumedBits = 1;
   1421         if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
   1422             addr.ton = inStream.read(3);
   1423             addr.numberPlan = inStream.read(4);
   1424             fieldBits = 8;
   1425             consumedBits += 7;
   1426         }
   1427         addr.numberOfDigits = inStream.read(8);
   1428         consumedBits += 8;
   1429         int remainingBits = paramBits - consumedBits;
   1430         int dataBits = addr.numberOfDigits * fieldBits;
   1431         int paddingBits = remainingBits - dataBits;
   1432         if (remainingBits < dataBits) {
   1433             throw new CodingException("CALLBACK_NUMBER subparam encoding size error (" +
   1434                                       "remainingBits + " + remainingBits + ", dataBits + " +
   1435                                       dataBits + ", paddingBits + " + paddingBits + ")");
   1436         }
   1437         addr.origBytes = inStream.readByteArray(dataBits);
   1438         inStream.skip(paddingBits);
   1439         decodeSmsAddress(addr);
   1440         bData.callbackNumber = addr;
   1441         return true;
   1442     }
   1443 
   1444     private static boolean decodeMsgStatus(BearerData bData, BitwiseInputStream inStream)
   1445         throws BitwiseInputStream.AccessException {
   1446         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1447         boolean decodeSuccess = false;
   1448         int paramBits = inStream.read(8) * 8;
   1449         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1450             paramBits -= EXPECTED_PARAM_SIZE;
   1451             decodeSuccess = true;
   1452             bData.errorClass = inStream.read(2);
   1453             bData.messageStatus = inStream.read(6);
   1454         }
   1455         if ((! decodeSuccess) || (paramBits > 0)) {
   1456             Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " +
   1457                       (decodeSuccess ? "succeeded" : "failed") +
   1458                       " (extra bits = " + paramBits + ")");
   1459         }
   1460         inStream.skip(paramBits);
   1461         bData.messageStatusSet = decodeSuccess;
   1462         return decodeSuccess;
   1463     }
   1464 
   1465     private static boolean decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream)
   1466         throws BitwiseInputStream.AccessException {
   1467         final int EXPECTED_PARAM_SIZE = 6 * 8;
   1468         boolean decodeSuccess = false;
   1469         int paramBits = inStream.read(8) * 8;
   1470         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1471             paramBits -= EXPECTED_PARAM_SIZE;
   1472             decodeSuccess = true;
   1473             bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
   1474         }
   1475         if ((! decodeSuccess) || (paramBits > 0)) {
   1476             Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " +
   1477                       (decodeSuccess ? "succeeded" : "failed") +
   1478                       " (extra bits = " + paramBits + ")");
   1479         }
   1480         inStream.skip(paramBits);
   1481         return decodeSuccess;
   1482     }
   1483 
   1484     private static boolean decodeValidityAbs(BearerData bData, BitwiseInputStream inStream)
   1485         throws BitwiseInputStream.AccessException {
   1486         final int EXPECTED_PARAM_SIZE = 6 * 8;
   1487         boolean decodeSuccess = false;
   1488         int paramBits = inStream.read(8) * 8;
   1489         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1490             paramBits -= EXPECTED_PARAM_SIZE;
   1491             decodeSuccess = true;
   1492             bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
   1493         }
   1494         if ((! decodeSuccess) || (paramBits > 0)) {
   1495             Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " +
   1496                       (decodeSuccess ? "succeeded" : "failed") +
   1497                       " (extra bits = " + paramBits + ")");
   1498         }
   1499         inStream.skip(paramBits);
   1500         return decodeSuccess;
   1501     }
   1502 
   1503     private static boolean decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream)
   1504         throws BitwiseInputStream.AccessException {
   1505         final int EXPECTED_PARAM_SIZE = 6 * 8;
   1506         boolean decodeSuccess = false;
   1507         int paramBits = inStream.read(8) * 8;
   1508         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1509             paramBits -= EXPECTED_PARAM_SIZE;
   1510             decodeSuccess = true;
   1511             bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray(
   1512                     inStream.readByteArray(6 * 8));
   1513         }
   1514         if ((! decodeSuccess) || (paramBits > 0)) {
   1515             Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " +
   1516                       (decodeSuccess ? "succeeded" : "failed") +
   1517                       " (extra bits = " + paramBits + ")");
   1518         }
   1519         inStream.skip(paramBits);
   1520         return decodeSuccess;
   1521     }
   1522 
   1523     private static boolean decodeValidityRel(BearerData bData, BitwiseInputStream inStream)
   1524         throws BitwiseInputStream.AccessException {
   1525         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1526         boolean decodeSuccess = false;
   1527         int paramBits = inStream.read(8) * 8;
   1528         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1529             paramBits -= EXPECTED_PARAM_SIZE;
   1530             decodeSuccess = true;
   1531             bData.deferredDeliveryTimeRelative = inStream.read(8);
   1532         }
   1533         if ((! decodeSuccess) || (paramBits > 0)) {
   1534             Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " +
   1535                       (decodeSuccess ? "succeeded" : "failed") +
   1536                       " (extra bits = " + paramBits + ")");
   1537         }
   1538         inStream.skip(paramBits);
   1539         bData.deferredDeliveryTimeRelativeSet = decodeSuccess;
   1540         return decodeSuccess;
   1541     }
   1542 
   1543     private static boolean decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream)
   1544         throws BitwiseInputStream.AccessException {
   1545         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1546         boolean decodeSuccess = false;
   1547         int paramBits = inStream.read(8) * 8;
   1548         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1549             paramBits -= EXPECTED_PARAM_SIZE;
   1550             decodeSuccess = true;
   1551             bData.validityPeriodRelative = inStream.read(8);
   1552         }
   1553         if ((! decodeSuccess) || (paramBits > 0)) {
   1554             Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " +
   1555                       (decodeSuccess ? "succeeded" : "failed") +
   1556                       " (extra bits = " + paramBits + ")");
   1557         }
   1558         inStream.skip(paramBits);
   1559         bData.validityPeriodRelativeSet = decodeSuccess;
   1560         return decodeSuccess;
   1561     }
   1562 
   1563     private static boolean decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream)
   1564         throws BitwiseInputStream.AccessException {
   1565         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1566         boolean decodeSuccess = false;
   1567         int paramBits = inStream.read(8) * 8;
   1568         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1569             paramBits -= EXPECTED_PARAM_SIZE;
   1570             decodeSuccess = true;
   1571             bData.privacy = inStream.read(2);
   1572             inStream.skip(6);
   1573         }
   1574         if ((! decodeSuccess) || (paramBits > 0)) {
   1575             Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " +
   1576                       (decodeSuccess ? "succeeded" : "failed") +
   1577                       " (extra bits = " + paramBits + ")");
   1578         }
   1579         inStream.skip(paramBits);
   1580         bData.privacyIndicatorSet = decodeSuccess;
   1581         return decodeSuccess;
   1582     }
   1583 
   1584     private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)
   1585         throws BitwiseInputStream.AccessException {
   1586         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1587         boolean decodeSuccess = false;
   1588         int paramBits = inStream.read(8) * 8;
   1589         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1590             paramBits -= EXPECTED_PARAM_SIZE;
   1591             decodeSuccess = true;
   1592             bData.language = inStream.read(8);
   1593         }
   1594         if ((! decodeSuccess) || (paramBits > 0)) {
   1595             Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " +
   1596                       (decodeSuccess ? "succeeded" : "failed") +
   1597                       " (extra bits = " + paramBits + ")");
   1598         }
   1599         inStream.skip(paramBits);
   1600         bData.languageIndicatorSet = decodeSuccess;
   1601         return decodeSuccess;
   1602     }
   1603 
   1604     private static boolean decodeDisplayMode(BearerData bData, BitwiseInputStream inStream)
   1605         throws BitwiseInputStream.AccessException {
   1606         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1607         boolean decodeSuccess = false;
   1608         int paramBits = inStream.read(8) * 8;
   1609         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1610             paramBits -= EXPECTED_PARAM_SIZE;
   1611             decodeSuccess = true;
   1612             bData.displayMode = inStream.read(2);
   1613             inStream.skip(6);
   1614         }
   1615         if ((! decodeSuccess) || (paramBits > 0)) {
   1616             Rlog.d(LOG_TAG, "DISPLAY_MODE decode " +
   1617                       (decodeSuccess ? "succeeded" : "failed") +
   1618                       " (extra bits = " + paramBits + ")");
   1619         }
   1620         inStream.skip(paramBits);
   1621         bData.displayModeSet = decodeSuccess;
   1622         return decodeSuccess;
   1623     }
   1624 
   1625     private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)
   1626         throws BitwiseInputStream.AccessException {
   1627         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1628         boolean decodeSuccess = false;
   1629         int paramBits = inStream.read(8) * 8;
   1630         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1631             paramBits -= EXPECTED_PARAM_SIZE;
   1632             decodeSuccess = true;
   1633             bData.priority = inStream.read(2);
   1634             inStream.skip(6);
   1635         }
   1636         if ((! decodeSuccess) || (paramBits > 0)) {
   1637             Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " +
   1638                       (decodeSuccess ? "succeeded" : "failed") +
   1639                       " (extra bits = " + paramBits + ")");
   1640         }
   1641         inStream.skip(paramBits);
   1642         bData.priorityIndicatorSet = decodeSuccess;
   1643         return decodeSuccess;
   1644     }
   1645 
   1646     private static boolean decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream)
   1647         throws BitwiseInputStream.AccessException {
   1648         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1649         boolean decodeSuccess = false;
   1650         int paramBits = inStream.read(8) * 8;
   1651         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1652             paramBits -= EXPECTED_PARAM_SIZE;
   1653             decodeSuccess = true;
   1654             bData.alert = inStream.read(2);
   1655             inStream.skip(6);
   1656         }
   1657         if ((! decodeSuccess) || (paramBits > 0)) {
   1658             Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " +
   1659                       (decodeSuccess ? "succeeded" : "failed") +
   1660                       " (extra bits = " + paramBits + ")");
   1661         }
   1662         inStream.skip(paramBits);
   1663         bData.alertIndicatorSet = decodeSuccess;
   1664         return decodeSuccess;
   1665     }
   1666 
   1667     private static boolean decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream)
   1668         throws BitwiseInputStream.AccessException {
   1669         final int EXPECTED_PARAM_SIZE = 1 * 8;
   1670         boolean decodeSuccess = false;
   1671         int paramBits = inStream.read(8) * 8;
   1672         if (paramBits >= EXPECTED_PARAM_SIZE) {
   1673             paramBits -= EXPECTED_PARAM_SIZE;
   1674             decodeSuccess = true;
   1675             bData.userResponseCode = inStream.read(8);
   1676         }
   1677         if ((! decodeSuccess) || (paramBits > 0)) {
   1678             Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
   1679                       (decodeSuccess ? "succeeded" : "failed") +
   1680                       " (extra bits = " + paramBits + ")");
   1681         }
   1682         inStream.skip(paramBits);
   1683         bData.userResponseCodeSet = decodeSuccess;
   1684         return decodeSuccess;
   1685     }
   1686 
   1687     private static boolean decodeServiceCategoryProgramData(BearerData bData,
   1688             BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException
   1689     {
   1690         if (inStream.available() < 13) {
   1691             throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
   1692                     + inStream.available() + " bits available");
   1693         }
   1694 
   1695         int paramBits = inStream.read(8) * 8;
   1696         int msgEncoding = inStream.read(5);
   1697         paramBits -= 5;
   1698 
   1699         if (inStream.available() < paramBits) {
   1700             throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
   1701                     + inStream.available() + " bits available (" + paramBits + " bits expected)");
   1702         }
   1703 
   1704         ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>();
   1705 
   1706         final int CATEGORY_FIELD_MIN_SIZE = 6 * 8;
   1707         boolean decodeSuccess = false;
   1708         while (paramBits >= CATEGORY_FIELD_MIN_SIZE) {
   1709             int operation = inStream.read(4);
   1710             int category = (inStream.read(8) << 8) | inStream.read(8);
   1711             int language = inStream.read(8);
   1712             int maxMessages = inStream.read(8);
   1713             int alertOption = inStream.read(4);
   1714             int numFields = inStream.read(8);
   1715             paramBits -= CATEGORY_FIELD_MIN_SIZE;
   1716 
   1717             int textBits = getBitsForNumFields(msgEncoding, numFields);
   1718             if (paramBits < textBits) {
   1719                 throw new CodingException("category name is " + textBits + " bits in length,"
   1720                         + " but there are only " + paramBits + " bits available");
   1721             }
   1722 
   1723             UserData userData = new UserData();
   1724             userData.msgEncoding = msgEncoding;
   1725             userData.msgEncodingSet = true;
   1726             userData.numFields = numFields;
   1727             userData.payload = inStream.readByteArray(textBits);
   1728             paramBits -= textBits;
   1729 
   1730             decodeUserDataPayload(userData, false);
   1731             String categoryName = userData.payloadStr;
   1732             CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category,
   1733                     language, maxMessages, alertOption, categoryName);
   1734             programDataList.add(programData);
   1735 
   1736             decodeSuccess = true;
   1737         }
   1738 
   1739         if ((! decodeSuccess) || (paramBits > 0)) {
   1740             Rlog.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " +
   1741                       (decodeSuccess ? "succeeded" : "failed") +
   1742                       " (extra bits = " + paramBits + ')');
   1743         }
   1744 
   1745         inStream.skip(paramBits);
   1746         bData.serviceCategoryProgramData = programDataList;
   1747         return decodeSuccess;
   1748     }
   1749 
   1750     private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
   1751         switch (serviceCategory) {
   1752             case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
   1753                 return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
   1754 
   1755             case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
   1756                 return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
   1757 
   1758             case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
   1759                 return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
   1760 
   1761             case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
   1762                 return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
   1763 
   1764             case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE:
   1765                 return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
   1766 
   1767             default:
   1768                 return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
   1769         }
   1770     }
   1771 
   1772     /**
   1773      * Calculates the number of bits to read for the specified number of encoded characters.
   1774      * @param msgEncoding the message encoding to use
   1775      * @param numFields the number of characters to read. For Shift-JIS and Korean encodings,
   1776      *  this is the number of bytes to read.
   1777      * @return the number of bits to read from the stream
   1778      * @throws CodingException if the specified encoding is not supported
   1779      */
   1780     private static int getBitsForNumFields(int msgEncoding, int numFields)
   1781             throws CodingException {
   1782         switch (msgEncoding) {
   1783             case UserData.ENCODING_OCTET:
   1784             case UserData.ENCODING_SHIFT_JIS:
   1785             case UserData.ENCODING_KOREAN:
   1786             case UserData.ENCODING_LATIN:
   1787             case UserData.ENCODING_LATIN_HEBREW:
   1788                 return numFields * 8;
   1789 
   1790             case UserData.ENCODING_IA5:
   1791             case UserData.ENCODING_7BIT_ASCII:
   1792             case UserData.ENCODING_GSM_7BIT_ALPHABET:
   1793                 return numFields * 7;
   1794 
   1795             case UserData.ENCODING_UNICODE_16:
   1796                 return numFields * 16;
   1797 
   1798             default:
   1799                 throw new CodingException("unsupported message encoding (" + msgEncoding + ')');
   1800         }
   1801     }
   1802 
   1803     /**
   1804      * CMAS message decoding.
   1805      * (See TIA-1149-0-1, CMAS over CDMA)
   1806      *
   1807      * @param serviceCategory is the service category from the SMS envelope
   1808      */
   1809     private static void decodeCmasUserData(BearerData bData, int serviceCategory)
   1810             throws BitwiseInputStream.AccessException, CodingException {
   1811         BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
   1812         if (inStream.available() < 8) {
   1813             throw new CodingException("emergency CB with no CMAE_protocol_version");
   1814         }
   1815         int protocolVersion = inStream.read(8);
   1816         if (protocolVersion != 0) {
   1817             throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
   1818         }
   1819 
   1820         int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
   1821         int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
   1822         int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
   1823         int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
   1824         int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
   1825         int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
   1826 
   1827         while (inStream.available() >= 16) {
   1828             int recordType = inStream.read(8);
   1829             int recordLen = inStream.read(8);
   1830             switch (recordType) {
   1831                 case 0:     // Type 0 elements (Alert text)
   1832                     UserData alertUserData = new UserData();
   1833                     alertUserData.msgEncoding = inStream.read(5);
   1834                     alertUserData.msgEncodingSet = true;
   1835                     alertUserData.msgType = 0;
   1836 
   1837                     int numFields;                          // number of chars to decode
   1838                     switch (alertUserData.msgEncoding) {
   1839                         case UserData.ENCODING_OCTET:
   1840                         case UserData.ENCODING_LATIN:
   1841                             numFields = recordLen - 1;      // subtract 1 byte for encoding
   1842                             break;
   1843 
   1844                         case UserData.ENCODING_IA5:
   1845                         case UserData.ENCODING_7BIT_ASCII:
   1846                         case UserData.ENCODING_GSM_7BIT_ALPHABET:
   1847                             numFields = ((recordLen * 8) - 5) / 7;  // subtract 5 bits for encoding
   1848                             break;
   1849 
   1850                         case UserData.ENCODING_UNICODE_16:
   1851                             numFields = (recordLen - 1) / 2;
   1852                             break;
   1853 
   1854                         default:
   1855                             numFields = 0;      // unsupported encoding
   1856                     }
   1857 
   1858                     alertUserData.numFields = numFields;
   1859                     alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
   1860                     decodeUserDataPayload(alertUserData, false);
   1861                     bData.userData = alertUserData;
   1862                     break;
   1863 
   1864                 case 1:     // Type 1 elements
   1865                     category = inStream.read(8);
   1866                     responseType = inStream.read(8);
   1867                     severity = inStream.read(4);
   1868                     urgency = inStream.read(4);
   1869                     certainty = inStream.read(4);
   1870                     inStream.skip(recordLen * 8 - 28);
   1871                     break;
   1872 
   1873                 default:
   1874                     Rlog.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
   1875                     inStream.skip(recordLen * 8);
   1876                     break;
   1877             }
   1878         }
   1879 
   1880         bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
   1881                 urgency, certainty);
   1882     }
   1883 
   1884     /**
   1885      * Create BearerData object from serialized representation.
   1886      * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
   1887      *
   1888      * @param smsData byte array of raw encoded SMS bearer data.
   1889      * @return an instance of BearerData.
   1890      */
   1891     public static BearerData decode(byte[] smsData) {
   1892         return decode(smsData, 0);
   1893     }
   1894 
   1895     private static boolean isCmasAlertCategory(int category) {
   1896         return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
   1897                 && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE;
   1898     }
   1899 
   1900     /**
   1901      * Create BearerData object from serialized representation.
   1902      * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
   1903      *
   1904      * @param smsData byte array of raw encoded SMS bearer data.
   1905      * @param serviceCategory the envelope service category (for CMAS alert handling)
   1906      * @return an instance of BearerData.
   1907      */
   1908     public static BearerData decode(byte[] smsData, int serviceCategory) {
   1909         try {
   1910             BitwiseInputStream inStream = new BitwiseInputStream(smsData);
   1911             BearerData bData = new BearerData();
   1912             int foundSubparamMask = 0;
   1913             while (inStream.available() > 0) {
   1914                 int subparamId = inStream.read(8);
   1915                 int subparamIdBit = 1 << subparamId;
   1916                 // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8)
   1917                 // as 32th bit is the max bit in int.
   1918                 // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers:
   1919                 // last defined subparam ID is 23 (00010111 = 0x17 = 23).
   1920                 // Only do duplicate subparam ID check if subparam is within defined value as
   1921                 // reserved subparams are just skipped.
   1922                 if ((foundSubparamMask & subparamIdBit) != 0 &&
   1923                         (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
   1924                         subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
   1925                     throw new CodingException("illegal duplicate subparameter (" +
   1926                                               subparamId + ")");
   1927                 }
   1928                 boolean decodeSuccess;
   1929                 switch (subparamId) {
   1930                 case SUBPARAM_MESSAGE_IDENTIFIER:
   1931                     decodeSuccess = decodeMessageId(bData, inStream);
   1932                     break;
   1933                 case SUBPARAM_USER_DATA:
   1934                     decodeSuccess = decodeUserData(bData, inStream);
   1935                     break;
   1936                 case SUBPARAM_USER_RESPONSE_CODE:
   1937                     decodeSuccess = decodeUserResponseCode(bData, inStream);
   1938                     break;
   1939                 case SUBPARAM_REPLY_OPTION:
   1940                     decodeSuccess = decodeReplyOption(bData, inStream);
   1941                     break;
   1942                 case SUBPARAM_NUMBER_OF_MESSAGES:
   1943                     decodeSuccess = decodeMsgCount(bData, inStream);
   1944                     break;
   1945                 case SUBPARAM_CALLBACK_NUMBER:
   1946                     decodeSuccess = decodeCallbackNumber(bData, inStream);
   1947                     break;
   1948                 case SUBPARAM_MESSAGE_STATUS:
   1949                     decodeSuccess = decodeMsgStatus(bData, inStream);
   1950                     break;
   1951                 case SUBPARAM_MESSAGE_CENTER_TIME_STAMP:
   1952                     decodeSuccess = decodeMsgCenterTimeStamp(bData, inStream);
   1953                     break;
   1954                 case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE:
   1955                     decodeSuccess = decodeValidityAbs(bData, inStream);
   1956                     break;
   1957                 case SUBPARAM_VALIDITY_PERIOD_RELATIVE:
   1958                     decodeSuccess = decodeValidityRel(bData, inStream);
   1959                     break;
   1960                 case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE:
   1961                     decodeSuccess = decodeDeferredDeliveryAbs(bData, inStream);
   1962                     break;
   1963                 case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE:
   1964                     decodeSuccess = decodeDeferredDeliveryRel(bData, inStream);
   1965                     break;
   1966                 case SUBPARAM_PRIVACY_INDICATOR:
   1967                     decodeSuccess = decodePrivacyIndicator(bData, inStream);
   1968                     break;
   1969                 case SUBPARAM_LANGUAGE_INDICATOR:
   1970                     decodeSuccess = decodeLanguageIndicator(bData, inStream);
   1971                     break;
   1972                 case SUBPARAM_MESSAGE_DISPLAY_MODE:
   1973                     decodeSuccess = decodeDisplayMode(bData, inStream);
   1974                     break;
   1975                 case SUBPARAM_PRIORITY_INDICATOR:
   1976                     decodeSuccess = decodePriorityIndicator(bData, inStream);
   1977                     break;
   1978                 case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY:
   1979                     decodeSuccess = decodeMsgDeliveryAlert(bData, inStream);
   1980                     break;
   1981                 case SUBPARAM_MESSAGE_DEPOSIT_INDEX:
   1982                     decodeSuccess = decodeDepositIndex(bData, inStream);
   1983                     break;
   1984                 case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA:
   1985                     decodeSuccess = decodeServiceCategoryProgramData(bData, inStream);
   1986                     break;
   1987                 default:
   1988                     decodeSuccess = decodeReserved(bData, inStream, subparamId);
   1989                 }
   1990                 if (decodeSuccess &&
   1991                         (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
   1992                         subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
   1993                     foundSubparamMask |= subparamIdBit;
   1994                 }
   1995             }
   1996             if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
   1997                 throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
   1998             }
   1999             if (bData.userData != null) {
   2000                 if (isCmasAlertCategory(serviceCategory)) {
   2001                     decodeCmasUserData(bData, serviceCategory);
   2002                 } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
   2003                     if ((foundSubparamMask ^
   2004                              (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
   2005                              (1 << SUBPARAM_USER_DATA))
   2006                             != 0) {
   2007                         Rlog.e(LOG_TAG, "IS-91 must occur without extra subparams (" +
   2008                               foundSubparamMask + ")");
   2009                     }
   2010                     decodeIs91(bData);
   2011                 } else {
   2012                     decodeUserDataPayload(bData.userData, bData.hasUserDataHeader);
   2013                 }
   2014             }
   2015             return bData;
   2016         } catch (BitwiseInputStream.AccessException ex) {
   2017             Rlog.e(LOG_TAG, "BearerData decode failed: " + ex);
   2018         } catch (CodingException ex) {
   2019             Rlog.e(LOG_TAG, "BearerData decode failed: " + ex);
   2020         }
   2021         return null;
   2022     }
   2023 }
   2024