blob: c63452753733b6d8fbab428c5167156185cf08c4 [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;
Jordan Liue532d232019-12-16 15:35:27 -080022import android.util.Log;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070023
Jordan Liudfcbfaf2019-10-11 11:42:03 -070024/**
25 * An object to decode CDMA SMS bearer data.
26 */
27public 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 */
Jordan Liu5e174552019-11-07 11:54:10 -080035 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;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070039
40 // All other values after this are reserved.
Jordan Liu5e174552019-11-07 11:54:10 -080041 private static final byte SUBPARAM_ID_LAST_DEFINED = 0x17;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070042
43 /**
44 * Supported priority modes for CDMA SMS messages
45 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
46 */
Jordan Liu5e174552019-11-07 11:54:10 -080047 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;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070051
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 */
Jordan Liu5e174552019-11-07 11:54:10 -080059 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;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070063 public static final int LANGUAGE_JAPANESE = 0x04;
Jordan Liu5e174552019-11-07 11:54:10 -080064 public static final int LANGUAGE_KOREAN = 0x05;
65 public static final int LANGUAGE_CHINESE = 0x06;
66 public static final int LANGUAGE_HEBREW = 0x07;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070067
68 /**
Jordan Liuc8e12392019-11-07 11:34:49 -080069 * 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 /**
Jordan Liudfcbfaf2019-10-11 11:42:03 -070076 * 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.
Jordan Liu5e174552019-11-07 11:54:10 -0800111 *
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700112 * @see #decodeCmasUserData(BearerData, int)
113 */
114 public SmsCbCmasInfo cmasWarningInfo;
115
116 /**
117 * Construct an empty BearerData.
118 */
Jordan Liu5e174552019-11-07 11:54:10 -0800119 private BearerData() {
120 }
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700121
122 private static class CodingException extends Exception {
123 public CodingException(String s) {
124 super(s);
125 }
126 }
127
128 /**
129 * Returns the language indicator as a two-character ISO 639 string.
Jordan Liu5e174552019-11-07 11:54:10 -0800130 *
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700131 * @return a two character ISO 639 language code
132 */
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.
Jordan Liu5e174552019-11-07 11:54:10 -0800139 *
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700140 * @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 */
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
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
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 }
Jordan Liu5e174552019-11-07 11:54:10 -0800196 if ((!decodeSuccess) || (paramBits > 0)) {
Jordan Liue532d232019-12-16 15:35:27 -0800197 Log.d(LOG_TAG, "MESSAGE_IDENTIFIER decode "
Jack Yu66ad02d2019-11-18 19:55:39 -0800198 + (decodeSuccess ? "succeeded" : "failed")
199 + " (extra bits = " + paramBits + ")");
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700200 }
201 inStream.skip(paramBits);
202 return decodeSuccess;
203 }
204
205 private static boolean decodeReserved(BitwiseInputStream inStream, int subparamId)
Jordan Liu5e174552019-11-07 11:54:10 -0800206 throws BitwiseInputStream.AccessException, CodingException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700207 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 }
Jordan Liue532d232019-12-16 15:35:27 -0800214 Log.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode "
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700215 + (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
224 private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
Jordan Liu5e174552019-11-07 11:54:10 -0800225 throws BitwiseInputStream.AccessException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700226 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
244 private static String decodeUtf8(byte[] data, int offset, int numFields)
Jordan Liu5e174552019-11-07 11:54:10 -0800245 throws CodingException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700246 return decodeCharset(data, offset, numFields, 1, "UTF-8");
247 }
248
249 private static String decodeUtf16(byte[] data, int offset, int numFields)
Jordan Liu5e174552019-11-07 11:54:10 -0800250 throws CodingException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700251 // 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
257 private static String decodeCharset(byte[] data, int offset, int numFields, int width,
Jordan Liu5e174552019-11-07 11:54:10 -0800258 String charset) throws CodingException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700259 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 }
Jordan Liue532d232019-12-16 15:35:27 -0800266 Log.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700267 + 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
278 private static String decode7bitAscii(byte[] data, int offset, int numFields)
Jordan Liu5e174552019-11-07 11:54:10 -0800279 throws CodingException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700280 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
313 private static String decode7bitGsm(byte[] data, int offset, int numFields)
Jordan Liu5e174552019-11-07 11:54:10 -0800314 throws CodingException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700315 // 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;
Jordan Liu5e174552019-11-07 11:54:10 -0800320 String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields,
321 paddingBits, 0, 0);
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700322 if (result == null) {
323 throw new CodingException("7bit GSM decoding failed");
324 }
325 return result;
326 }
327
328 private static String decodeLatin(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, "ISO-8859-1");
331 }
332
333 private static String decodeShiftJis(byte[] data, int offset, int numFields)
Jordan Liu5e174552019-11-07 11:54:10 -0800334 throws CodingException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700335 return decodeCharset(data, offset, numFields, 1, "Shift_JIS");
336 }
337
Jordan Liu5e174552019-11-07 11:54:10 -0800338 private static String decodeGsmDcs(byte[] data, int offset, int numFields,
339 int msgType)
340 throws CodingException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700341 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
Jordan Liu5e174552019-11-07 11:54:10 -0800359 private static void decodeUserDataPayload(Context context, UserData userData,
360 boolean hasUserDataHeader) throws CodingException {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700361 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 */
Jordan Liu5e174552019-11-07 11:54:10 -0800374 boolean decodingtypeUTF8 = context.getResources()
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700375 .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) {
Jordan Liu5e174552019-11-07 11:54:10 -0800388 // There are many devices in the market that send 8bit text sms (latin
389 // encoded) as
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700390 // 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:
Jordan Liu5e174552019-11-07 11:54:10 -0800405 userData.payloadStr = decode7bitGsm(userData.payload, offset,
406 userData.numFields);
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700407 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 }
Jordan Liu5e174552019-11-07 11:54:10 -0800434 if ((!decodeSuccess) || (paramBits > 0)) {
Jordan Liue532d232019-12-16 15:35:27 -0800435 Log.d(LOG_TAG, "LANGUAGE_INDICATOR decode "
Jack Yu66ad02d2019-11-18 19:55:39 -0800436 + (decodeSuccess ? "succeeded" : "failed")
437 + " (extra bits = " + paramBits + ")");
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700438 }
439 inStream.skip(paramBits);
440 return decodeSuccess;
441 }
442
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 }
Jordan Liu5e174552019-11-07 11:54:10 -0800454 if ((!decodeSuccess) || (paramBits > 0)) {
Jordan Liue532d232019-12-16 15:35:27 -0800455 Log.d(LOG_TAG, "PRIORITY_INDICATOR decode "
Jack Yu66ad02d2019-11-18 19:55:39 -0800456 + (decodeSuccess ? "succeeded" : "failed")
457 + " (extra bits = " + paramBits + ")");
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700458 }
459 inStream.skip(paramBits);
460 return decodeSuccess;
461 }
462
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 */
Jordan Liu5e174552019-11-07 11:54:10 -0800491 private static void decodeCmasUserData(Context context, BearerData bData, int serviceCategory)
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700492 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);
Jordan Liu5e174552019-11-07 11:54:10 -0800542 decodeUserDataPayload(context, alertUserData, false);
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700543 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:
Jordan Liue532d232019-12-16 15:35:27 -0800556 Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700557 inStream.skip(recordLen * 8);
558 break;
559 }
560 }
561
562 bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
563 urgency, certainty);
564 }
565
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 *
Jordan Liu5e174552019-11-07 11:54:10 -0800575 * @param smsData byte array of raw encoded SMS bearer data.
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700576 * @param serviceCategory the envelope service category (for CMAS alert handling)
577 * @return an instance of BearerData.
578 */
Jordan Liu62e183f2019-12-20 15:09:44 -0800579 public static BearerData decode(Context context, byte[] smsData, int serviceCategory)
580 throws CodingException, BitwiseInputStream.AccessException {
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 (" + subparamId + ")");
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700597 }
Jordan Liu62e183f2019-12-20 15:09:44 -0800598 boolean decodeSuccess;
599 switch (subparamId) {
600 case SUBPARAM_MESSAGE_IDENTIFIER:
601 decodeSuccess = decodeMessageId(bData, inStream);
602 break;
603 case SUBPARAM_USER_DATA:
604 decodeSuccess = decodeUserData(bData, inStream);
605 break;
606 case SUBPARAM_LANGUAGE_INDICATOR:
607 decodeSuccess = decodeLanguageIndicator(bData, inStream);
608 break;
609 case SUBPARAM_PRIORITY_INDICATOR:
610 decodeSuccess = decodePriorityIndicator(bData, inStream);
611 break;
612 default:
613 decodeSuccess = decodeReserved(inStream, subparamId);
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700614 }
Jordan Liu62e183f2019-12-20 15:09:44 -0800615 if (decodeSuccess && (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER
616 && subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
617 foundSubparamMask |= subparamIdBit;
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700618 }
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700619 }
Jordan Liu62e183f2019-12-20 15:09:44 -0800620 if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
621 throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
622 }
623 if (bData.userData != null) {
624 if (isCmasAlertCategory(serviceCategory)) {
625 decodeCmasUserData(context, bData, serviceCategory);
626 } else {
627 decodeUserDataPayload(context, bData.userData, bData.hasUserDataHeader);
628 }
629 }
630 return bData;
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700631 }
632}