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