1 /*
2  * Copyright (C) 2019 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.cellbroadcastservice;
18 
19 import android.content.Context;
20 import android.telephony.SmsCbCmasInfo;
21 import android.telephony.cdma.CdmaSmsCbProgramData;
22 import android.util.Log;
23 
24 /**
25  * An object to decode CDMA SMS bearer data.
26  */
27 public final class BearerData {
28     private final static String LOG_TAG = "BearerData";
29 
30     /**
31      * Bearer Data Subparameter Identifiers
32      * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
33      * NOTE: Unneeded subparameter types are not included
34      */
35     private static final byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00;
36     private static final byte SUBPARAM_USER_DATA = 0x01;
37     private static final byte SUBPARAM_PRIORITY_INDICATOR = 0x08;
38     private static final byte SUBPARAM_LANGUAGE_INDICATOR = 0x0D;
39 
40     // All other values after this are reserved.
41     private static final byte SUBPARAM_ID_LAST_DEFINED = 0x17;
42 
43     /**
44      * Supported priority modes for CDMA SMS messages
45      * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
46      */
47     public static final int PRIORITY_NORMAL = 0x0;
48     public static final int PRIORITY_INTERACTIVE = 0x1;
49     public static final int PRIORITY_URGENT = 0x2;
50     public static final int PRIORITY_EMERGENCY = 0x3;
51 
52     /**
53      * Language Indicator values.  NOTE: the spec (3GPP2 C.S0015-B,
54      * v2, 4.5.14) is ambiguous as to the meaning of this field, as it
55      * refers to C.R1001-D but that reference has been crossed out.
56      * It would seem reasonable to assume the values from C.R1001-F
57      * (table 9.2-1) are to be used instead.
58      */
59     public static final int LANGUAGE_UNKNOWN = 0x00;
60     public static final int LANGUAGE_ENGLISH = 0x01;
61     public static final int LANGUAGE_FRENCH = 0x02;
62     public static final int LANGUAGE_SPANISH = 0x03;
63     public static final int LANGUAGE_JAPANESE = 0x04;
64     public static final int LANGUAGE_KOREAN = 0x05;
65     public static final int LANGUAGE_CHINESE = 0x06;
66     public static final int LANGUAGE_HEBREW = 0x07;
67 
68     /**
69      * Supported message types for CDMA SMS messages
70      * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
71      * Used for CdmaSmsCbTest.
72      */
73     public static final int MESSAGE_TYPE_DELIVER        = 0x01;
74 
75     /**
76      * 16-bit value indicating the message ID, which increments modulo 65536.
77      * (Special rules apply for WAP-messages.)
78      * (See 3GPP2 C.S0015-B, v2, 4.5.1)
79      */
80     public int messageId;
81 
82     /**
83      * Priority modes for CDMA SMS message (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
84      */
85     public int priority = PRIORITY_NORMAL;
86 
87     /**
88      * Language indicator for CDMA SMS message.
89      */
90     public int language = LANGUAGE_UNKNOWN;
91 
92     /**
93      * 1-bit value that indicates whether a User Data Header (UDH) is present.
94      * (See 3GPP2 C.S0015-B, v2, 4.5.1)
95      *
96      * NOTE: during encoding, this value will be set based on the
97      * presence of a UDH in the structured data, any existing setting
98      * will be overwritten.
99      */
100     public boolean hasUserDataHeader;
101 
102     /**
103      * Information on the user data
104      * (e.g. padding bits, user data, user data header, etc)
105      * (See 3GPP2 C.S.0015-B, v2, 4.5.2)
106      */
107     public UserData userData;
108 
109     /**
110      * CMAS warning notification information.
111      *
112      * @see #decodeCmasUserData(BearerData, int)
113      */
114     public SmsCbCmasInfo cmasWarningInfo;
115 
116     /**
117      * Construct an empty BearerData.
118      */
BearerData()119     private BearerData() {
120     }
121 
122     private static class CodingException extends Exception {
CodingException(String s)123         public CodingException(String s) {
124             super(s);
125         }
126     }
127 
128     /**
129      * Returns the language indicator as a two-character ISO 639 string.
130      *
131      * @return a two character ISO 639 language code
132      */
getLanguage()133     public String getLanguage() {
134         return getLanguageCodeForValue(language);
135     }
136 
137     /**
138      * Converts a CDMA language indicator value to an ISO 639 two character language code.
139      *
140      * @param languageValue the CDMA language value to convert
141      * @return the two character ISO 639 language code for the specified value, or null if unknown
142      */
getLanguageCodeForValue(int languageValue)143     private static String getLanguageCodeForValue(int languageValue) {
144         switch (languageValue) {
145             case LANGUAGE_ENGLISH:
146                 return "en";
147 
148             case LANGUAGE_FRENCH:
149                 return "fr";
150 
151             case LANGUAGE_SPANISH:
152                 return "es";
153 
154             case LANGUAGE_JAPANESE:
155                 return "ja";
156 
157             case LANGUAGE_KOREAN:
158                 return "ko";
159 
160             case LANGUAGE_CHINESE:
161                 return "zh";
162 
163             case LANGUAGE_HEBREW:
164                 return "he";
165 
166             default:
167                 return null;
168         }
169     }
170 
171     @Override
toString()172     public String toString() {
173         StringBuilder builder = new StringBuilder();
174         builder.append("BearerData ");
175         builder.append(", messageId=" + messageId);
176         builder.append(", hasUserDataHeader=" + hasUserDataHeader);
177         builder.append(", userData=" + userData);
178         builder.append(" }");
179         return builder.toString();
180     }
181 
decodeMessageId(BearerData bData, BitwiseInputStream inStream)182     private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
183             throws BitwiseInputStream.AccessException {
184         final int EXPECTED_PARAM_SIZE = 3 * 8;
185         boolean decodeSuccess = false;
186         int paramBits = inStream.read(8) * 8;
187         if (paramBits >= EXPECTED_PARAM_SIZE) {
188             paramBits -= EXPECTED_PARAM_SIZE;
189             decodeSuccess = true;
190             inStream.skip(4); // skip messageType
191             bData.messageId = inStream.read(8) << 8;
192             bData.messageId |= inStream.read(8);
193             bData.hasUserDataHeader = (inStream.read(1) == 1);
194             inStream.skip(3);
195         }
196         if ((!decodeSuccess) || (paramBits > 0)) {
197             Log.d(LOG_TAG, "MESSAGE_IDENTIFIER decode "
198                     + (decodeSuccess ? "succeeded" : "failed")
199                     + " (extra bits = " + paramBits + ")");
200         }
201         inStream.skip(paramBits);
202         return decodeSuccess;
203     }
204 
decodeReserved(BitwiseInputStream inStream, int subparamId)205     private static boolean decodeReserved(BitwiseInputStream inStream, int subparamId)
206             throws BitwiseInputStream.AccessException, CodingException {
207         boolean decodeSuccess = false;
208         int subparamLen = inStream.read(8); // SUBPARAM_LEN
209         int paramBits = subparamLen * 8;
210         if (paramBits <= inStream.available()) {
211             decodeSuccess = true;
212             inStream.skip(paramBits);
213         }
214         Log.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode "
215                 + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")");
216         if (!decodeSuccess) {
217             throw new CodingException("RESERVED bearer data subparameter " + subparamId
218                     + " had invalid SUBPARAM_LEN " + subparamLen);
219         }
220 
221         return decodeSuccess;
222     }
223 
decodeUserData(BearerData bData, BitwiseInputStream inStream)224     private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
225             throws BitwiseInputStream.AccessException {
226         int paramBits = inStream.read(8) * 8;
227         bData.userData = new UserData();
228         bData.userData.msgEncoding = inStream.read(5);
229         bData.userData.msgEncodingSet = true;
230         bData.userData.msgType = 0;
231         int consumedBits = 5;
232         if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
233                 (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
234             bData.userData.msgType = inStream.read(8);
235             consumedBits += 8;
236         }
237         bData.userData.numFields = inStream.read(8);
238         consumedBits += 8;
239         int dataBits = paramBits - consumedBits;
240         bData.userData.payload = inStream.readByteArray(dataBits);
241         return true;
242     }
243 
decodeUtf8(byte[] data, int offset, int numFields)244     private static String decodeUtf8(byte[] data, int offset, int numFields)
245             throws CodingException {
246         return decodeCharset(data, offset, numFields, 1, "UTF-8");
247     }
248 
decodeUtf16(byte[] data, int offset, int numFields)249     private static String decodeUtf16(byte[] data, int offset, int numFields)
250             throws CodingException {
251         // Subtract header and possible padding byte (at end) from num fields.
252         int padding = offset % 2;
253         numFields -= (offset + padding) / 2;
254         return decodeCharset(data, offset, numFields, 2, "utf-16be");
255     }
256 
decodeCharset(byte[] data, int offset, int numFields, int width, String charset)257     private static String decodeCharset(byte[] data, int offset, int numFields, int width,
258             String charset) throws CodingException {
259         if (numFields < 0 || (numFields * width + offset) > data.length) {
260             // Try to decode the max number of characters in payload
261             int padding = offset % width;
262             int maxNumFields = (data.length - offset - padding) / width;
263             if (maxNumFields < 0) {
264                 throw new CodingException(charset + " decode failed: offset out of range");
265             }
266             Log.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
267                     + numFields + " data.length = " + data.length + " maxNumFields = "
268                     + maxNumFields);
269             numFields = maxNumFields;
270         }
271         try {
272             return new String(data, offset, numFields * width, charset);
273         } catch (java.io.UnsupportedEncodingException ex) {
274             throw new CodingException(charset + " decode failed: " + ex);
275         }
276     }
277 
decode7bitAscii(byte[] data, int offset, int numFields)278     private static String decode7bitAscii(byte[] data, int offset, int numFields)
279             throws CodingException {
280         try {
281             int offsetBits = offset * 8;
282             int offsetSeptets = (offsetBits + 6) / 7;
283             numFields -= offsetSeptets;
284 
285             StringBuffer strBuf = new StringBuffer(numFields);
286             BitwiseInputStream inStream = new BitwiseInputStream(data);
287             int wantedBits = (offsetSeptets * 7) + (numFields * 7);
288             if (inStream.available() < wantedBits) {
289                 throw new CodingException("insufficient data (wanted " + wantedBits +
290                         " bits, but only have " + inStream.available() + ")");
291             }
292             inStream.skip(offsetSeptets * 7);
293             for (int i = 0; i < numFields; i++) {
294                 int charCode = inStream.read(7);
295                 if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) &&
296                         (charCode <= UserData.ASCII_MAP_MAX_INDEX)) {
297                     strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]);
298                 } else if (charCode == UserData.ASCII_NL_INDEX) {
299                     strBuf.append('\n');
300                 } else if (charCode == UserData.ASCII_CR_INDEX) {
301                     strBuf.append('\r');
302                 } else {
303                     /* For other charCodes, they are unprintable, and so simply use SPACE. */
304                     strBuf.append(' ');
305                 }
306             }
307             return strBuf.toString();
308         } catch (BitwiseInputStream.AccessException ex) {
309             throw new CodingException("7bit ASCII decode failed: " + ex);
310         }
311     }
312 
decode7bitGsm(byte[] data, int offset, int numFields)313     private static String decode7bitGsm(byte[] data, int offset, int numFields)
314             throws CodingException {
315         // Start reading from the next 7-bit aligned boundary after offset.
316         int offsetBits = offset * 8;
317         int offsetSeptets = (offsetBits + 6) / 7;
318         numFields -= offsetSeptets;
319         int paddingBits = (offsetSeptets * 7) - offsetBits;
320         String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields,
321                 paddingBits, 0, 0);
322         if (result == null) {
323             throw new CodingException("7bit GSM decoding failed");
324         }
325         return result;
326     }
327 
decodeLatin(byte[] data, int offset, int numFields)328     private static String decodeLatin(byte[] data, int offset, int numFields)
329             throws CodingException {
330         return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
331     }
332 
decodeShiftJis(byte[] data, int offset, int numFields)333     private static String decodeShiftJis(byte[] data, int offset, int numFields)
334             throws CodingException {
335         return decodeCharset(data, offset, numFields, 1, "Shift_JIS");
336     }
337 
decodeGsmDcs(byte[] data, int offset, int numFields, int msgType)338     private static String decodeGsmDcs(byte[] data, int offset, int numFields,
339             int msgType)
340             throws CodingException {
341         if ((msgType & 0xC0) != 0) {
342             throw new CodingException("unsupported coding group ("
343                     + msgType + ")");
344         }
345 
346         switch ((msgType >> 2) & 0x3) {
347             case UserData.ENCODING_GSM_DCS_7BIT:
348                 return decode7bitGsm(data, offset, numFields);
349             case UserData.ENCODING_GSM_DCS_8BIT:
350                 return decodeUtf8(data, offset, numFields);
351             case UserData.ENCODING_GSM_DCS_16BIT:
352                 return decodeUtf16(data, offset, numFields);
353             default:
354                 throw new CodingException("unsupported user msgType encoding ("
355                         + msgType + ")");
356         }
357     }
358 
decodeUserDataPayload(Context context, UserData userData, boolean hasUserDataHeader)359     private static void decodeUserDataPayload(Context context, UserData userData,
360             boolean hasUserDataHeader) throws CodingException {
361         int offset = 0;
362         if (hasUserDataHeader) {
363             int udhLen = userData.payload[0] & 0x00FF;
364             offset += udhLen + 1;
365             byte[] headerData = new byte[udhLen];
366             System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
367             userData.userDataHeader = SmsHeader.fromByteArray(headerData);
368         }
369         switch (userData.msgEncoding) {
370             case UserData.ENCODING_OCTET:
371                 /*
372                  *  Octet decoding depends on the carrier service.
373                  */
374                 boolean decodingtypeUTF8 = context.getResources()
375                         .getBoolean(R.bool.config_sms_utf8_support);
376 
377                 // Strip off any padding bytes, meaning any differences between the length of the
378                 // array and the target length specified by numFields.  This is to avoid any
379                 // confusion by code elsewhere that only considers the payload array length.
380                 byte[] payload = new byte[userData.numFields];
381                 int copyLen = userData.numFields < userData.payload.length
382                         ? userData.numFields : userData.payload.length;
383 
384                 System.arraycopy(userData.payload, 0, payload, 0, copyLen);
385                 userData.payload = payload;
386 
387                 if (!decodingtypeUTF8) {
388                     // There are many devices in the market that send 8bit text sms (latin
389                     // encoded) as
390                     // octet encoded.
391                     userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
392                 } else {
393                     userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
394                 }
395                 break;
396 
397             case UserData.ENCODING_IA5:
398             case UserData.ENCODING_7BIT_ASCII:
399                 userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
400                 break;
401             case UserData.ENCODING_UNICODE_16:
402                 userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields);
403                 break;
404             case UserData.ENCODING_GSM_7BIT_ALPHABET:
405                 userData.payloadStr = decode7bitGsm(userData.payload, offset,
406                         userData.numFields);
407                 break;
408             case UserData.ENCODING_LATIN:
409                 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
410                 break;
411             case UserData.ENCODING_SHIFT_JIS:
412                 userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields);
413                 break;
414             case UserData.ENCODING_GSM_DCS:
415                 userData.payloadStr = decodeGsmDcs(userData.payload, offset,
416                         userData.numFields, userData.msgType);
417                 break;
418             default:
419                 throw new CodingException("unsupported user data encoding ("
420                         + userData.msgEncoding + ")");
421         }
422     }
423 
424     private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)
425             throws BitwiseInputStream.AccessException {
426         final int EXPECTED_PARAM_SIZE = 1 * 8;
427         boolean decodeSuccess = false;
428         int paramBits = inStream.read(8) * 8;
429         if (paramBits >= EXPECTED_PARAM_SIZE) {
430             paramBits -= EXPECTED_PARAM_SIZE;
431             decodeSuccess = true;
432             bData.language = inStream.read(8);
433         }
434         if ((!decodeSuccess) || (paramBits > 0)) {
435             Log.d(LOG_TAG, "LANGUAGE_INDICATOR decode "
436                     + (decodeSuccess ? "succeeded" : "failed")
437                     + " (extra bits = " + paramBits + ")");
438         }
439         inStream.skip(paramBits);
440         return decodeSuccess;
441     }
442 
decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)443     private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)
444             throws BitwiseInputStream.AccessException {
445         final int EXPECTED_PARAM_SIZE = 1 * 8;
446         boolean decodeSuccess = false;
447         int paramBits = inStream.read(8) * 8;
448         if (paramBits >= EXPECTED_PARAM_SIZE) {
449             paramBits -= EXPECTED_PARAM_SIZE;
450             decodeSuccess = true;
451             bData.priority = inStream.read(2);
452             inStream.skip(6);
453         }
454         if ((!decodeSuccess) || (paramBits > 0)) {
455             Log.d(LOG_TAG, "PRIORITY_INDICATOR decode "
456                     + (decodeSuccess ? "succeeded" : "failed")
457                     + " (extra bits = " + paramBits + ")");
458         }
459         inStream.skip(paramBits);
460         return decodeSuccess;
461     }
462 
serviceCategoryToCmasMessageClass(int serviceCategory)463     private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
464         switch (serviceCategory) {
465             case CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
466                 return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
467 
468             case CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT:
469                 return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
470 
471             case CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT:
472                 return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
473 
474             case CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
475                 return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
476 
477             case CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE:
478                 return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
479 
480             default:
481                 return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
482         }
483     }
484 
485     /**
486      * CMAS message decoding.
487      * (See TIA-1149-0-1, CMAS over CDMA)
488      *
489      * @param serviceCategory is the service category from the SMS envelope
490      */
decodeCmasUserData(Context context, BearerData bData, int serviceCategory)491     private static void decodeCmasUserData(Context context, BearerData bData, int serviceCategory)
492             throws BitwiseInputStream.AccessException, CodingException {
493         BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
494         if (inStream.available() < 8) {
495             throw new CodingException("emergency CB with no CMAE_protocol_version");
496         }
497         int protocolVersion = inStream.read(8);
498         if (protocolVersion != 0) {
499             throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
500         }
501 
502         int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
503         int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
504         int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
505         int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
506         int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
507         int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
508 
509         while (inStream.available() >= 16) {
510             int recordType = inStream.read(8);
511             int recordLen = inStream.read(8);
512             switch (recordType) {
513                 case 0:     // Type 0 elements (Alert text)
514                     UserData alertUserData = new UserData();
515                     alertUserData.msgEncoding = inStream.read(5);
516                     alertUserData.msgEncodingSet = true;
517                     alertUserData.msgType = 0;
518 
519                     int numFields;                          // number of chars to decode
520                     switch (alertUserData.msgEncoding) {
521                         case UserData.ENCODING_OCTET:
522                         case UserData.ENCODING_LATIN:
523                             numFields = recordLen - 1;      // subtract 1 byte for encoding
524                             break;
525 
526                         case UserData.ENCODING_IA5:
527                         case UserData.ENCODING_7BIT_ASCII:
528                         case UserData.ENCODING_GSM_7BIT_ALPHABET:
529                             numFields = ((recordLen * 8) - 5) / 7;  // subtract 5 bits for encoding
530                             break;
531 
532                         case UserData.ENCODING_UNICODE_16:
533                             numFields = (recordLen - 1) / 2;
534                             break;
535 
536                         default:
537                             numFields = 0;      // unsupported encoding
538                     }
539 
540                     alertUserData.numFields = numFields;
541                     alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
542                     decodeUserDataPayload(context, alertUserData, false);
543                     bData.userData = alertUserData;
544                     break;
545 
546                 case 1:     // Type 1 elements
547                     category = inStream.read(8);
548                     responseType = inStream.read(8);
549                     severity = inStream.read(4);
550                     urgency = inStream.read(4);
551                     certainty = inStream.read(4);
552                     inStream.skip(recordLen * 8 - 28);
553                     break;
554 
555                 default:
556                     Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
557                     inStream.skip(recordLen * 8);
558                     break;
559             }
560         }
561 
562         bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
563                 urgency, certainty);
564     }
565 
isCmasAlertCategory(int category)566     private static boolean isCmasAlertCategory(int category) {
567         return category >= CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
568                 && category <= CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE;
569     }
570 
571     /**
572      * Create BearerData object from serialized representation.
573      * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
574      *
575      * @param smsData         byte array of raw encoded SMS bearer data.
576      * @param serviceCategory the envelope service category (for CMAS alert handling)
577      * @return an instance of BearerData.
578      */
decode(Context context, byte[] smsData, int serviceCategory)579     public static BearerData decode(Context context, byte[] smsData, int serviceCategory) {
580         try {
581             BitwiseInputStream inStream = new BitwiseInputStream(smsData);
582             BearerData bData = new BearerData();
583             int foundSubparamMask = 0;
584             while (inStream.available() > 0) {
585                 int subparamId = inStream.read(8);
586                 int subparamIdBit = 1 << subparamId;
587                 // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8)
588                 // as 32th bit is the max bit in int.
589                 // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers:
590                 // last defined subparam ID is 23 (00010111 = 0x17 = 23).
591                 // Only do duplicate subparam ID check if subparam is within defined value as
592                 // reserved subparams are just skipped.
593                 if ((foundSubparamMask & subparamIdBit) != 0 &&
594                         (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
595                                 subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
596                     throw new CodingException("illegal duplicate subparameter (" +
597                             subparamId + ")");
598                 }
599                 boolean decodeSuccess;
600                 switch (subparamId) {
601                     case SUBPARAM_MESSAGE_IDENTIFIER:
602                         decodeSuccess = decodeMessageId(bData, inStream);
603                         break;
604                     case SUBPARAM_USER_DATA:
605                         decodeSuccess = decodeUserData(bData, inStream);
606                         break;
607                     case SUBPARAM_LANGUAGE_INDICATOR:
608                         decodeSuccess = decodeLanguageIndicator(bData, inStream);
609                         break;
610                     case SUBPARAM_PRIORITY_INDICATOR:
611                         decodeSuccess = decodePriorityIndicator(bData, inStream);
612                         break;
613                     default:
614                         decodeSuccess = decodeReserved(inStream, subparamId);
615                 }
616                 if (decodeSuccess &&
617                         (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
618                                 subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
619                     foundSubparamMask |= subparamIdBit;
620                 }
621             }
622             if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
623                 throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
624             }
625             if (bData.userData != null) {
626                 if (isCmasAlertCategory(serviceCategory)) {
627                     decodeCmasUserData(context, bData, serviceCategory);
628                 } else {
629                     decodeUserDataPayload(context, bData.userData, bData.hasUserDataHeader);
630                 }
631             }
632             return bData;
633         } catch (BitwiseInputStream.AccessException ex) {
634             Log.e(LOG_TAG, "BearerData decode failed: " + ex);
635         } catch (CodingException ex) {
636             Log.e(LOG_TAG, "BearerData decode failed: " + ex);
637         }
638         return null;
639     }
640 }
641