1/*
2 * Copyright (C) 2010 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.internal.telephony.gsm;
18
19import android.telephony.SmsCbCmasInfo;
20import android.telephony.SmsCbEtwsInfo;
21
22import java.util.Arrays;
23
24/**
25 * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by
26 * CellBroadcastReceiver test cases, but should not be used by applications.
27 *
28 * All relevant header information is now sent as a Parcelable
29 * {@link android.telephony.SmsCbMessage} object in the "message" extra of the
30 * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or
31 * {@link android.provider.Telephony.Sms.Intents#SMS_EMERGENCY_CB_RECEIVED_ACTION} intent.
32 * The raw PDU is no longer sent to SMS CB applications.
33 */
34class SmsCbHeader {
35
36    /**
37     * Length of SMS-CB header
38     */
39    static final int PDU_HEADER_LENGTH = 6;
40
41    /**
42     * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
43     */
44    static final int FORMAT_GSM = 1;
45
46    /**
47     * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
48     */
49    static final int FORMAT_UMTS = 2;
50
51    /**
52     * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
53     */
54    static final int FORMAT_ETWS_PRIMARY = 3;
55
56    /**
57     * Message type value as defined in 3gpp TS 25.324, section 11.1.
58     */
59    private static final int MESSAGE_TYPE_CBS_MESSAGE = 1;
60
61    /**
62     * Length of GSM pdus
63     */
64    private static final int PDU_LENGTH_GSM = 88;
65
66    /**
67     * Maximum length of ETWS primary message GSM pdus
68     */
69    private static final int PDU_LENGTH_ETWS = 56;
70
71    private final int geographicalScope;
72
73    /** The serial number combines geographical scope, message code, and update number. */
74    private final int serialNumber;
75
76    /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
77    private final int messageIdentifier;
78
79    private final int dataCodingScheme;
80
81    private final int pageIndex;
82
83    private final int nrOfPages;
84
85    private final int format;
86
87    /** ETWS warning notification info. */
88    private final SmsCbEtwsInfo mEtwsInfo;
89
90    /** CMAS warning notification info. */
91    private final SmsCbCmasInfo mCmasInfo;
92
93    public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
94        if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
95            throw new IllegalArgumentException("Illegal PDU");
96        }
97
98        if (pdu.length <= PDU_LENGTH_GSM) {
99            // can be ETWS or GSM format.
100            // Per TS23.041 9.4.1.2 and 9.4.1.3.2, GSM and ETWS format both
101            // contain serial number which contains GS, Message Code, and Update Number
102            // per 9.4.1.2.1, and message identifier in same octets
103            geographicalScope = (pdu[0] & 0xc0) >>> 6;
104            serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
105            messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
106            if (isEtwsMessage() && pdu.length <= PDU_LENGTH_ETWS) {
107                format = FORMAT_ETWS_PRIMARY;
108                dataCodingScheme = -1;
109                pageIndex = -1;
110                nrOfPages = -1;
111                boolean emergencyUserAlert = (pdu[4] & 0x1) != 0;
112                boolean activatePopup = (pdu[5] & 0x80) != 0;
113                int warningType = (pdu[4] & 0xfe) >>> 1;
114                byte[] warningSecurityInfo;
115                // copy the Warning-Security-Information, if present
116                if (pdu.length > PDU_HEADER_LENGTH) {
117                    warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length);
118                } else {
119                    warningSecurityInfo = null;
120                }
121                mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
122                        warningSecurityInfo);
123                mCmasInfo = null;
124                return;     // skip the ETWS/CMAS initialization code for regular notifications
125            } else {
126                // GSM pdus are no more than 88 bytes
127                format = FORMAT_GSM;
128                dataCodingScheme = pdu[4] & 0xff;
129
130                // Check for invalid page parameter
131                int pageIndex = (pdu[5] & 0xf0) >>> 4;
132                int nrOfPages = pdu[5] & 0x0f;
133
134                if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
135                    pageIndex = 1;
136                    nrOfPages = 1;
137                }
138
139                this.pageIndex = pageIndex;
140                this.nrOfPages = nrOfPages;
141            }
142        } else {
143            // UMTS pdus are always at least 90 bytes since the payload includes
144            // a number-of-pages octet and also one length octet per page
145            format = FORMAT_UMTS;
146
147            int messageType = pdu[0];
148
149            if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
150                throw new IllegalArgumentException("Unsupported message type " + messageType);
151            }
152
153            messageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
154            geographicalScope = (pdu[3] & 0xc0) >>> 6;
155            serialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
156            dataCodingScheme = pdu[5] & 0xff;
157
158            // We will always consider a UMTS message as having one single page
159            // since there's only one instance of the header, even though the
160            // actual payload may contain several pages.
161            pageIndex = 1;
162            nrOfPages = 1;
163        }
164
165        if (isEtwsMessage()) {
166            boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
167            boolean activatePopup = isEtwsPopupAlert();
168            int warningType = getEtwsWarningType();
169            mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, null);
170            mCmasInfo = null;
171        } else if (isCmasMessage()) {
172            int messageClass = getCmasMessageClass();
173            int severity = getCmasSeverity();
174            int urgency = getCmasUrgency();
175            int certainty = getCmasCertainty();
176            mEtwsInfo = null;
177            mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
178                    SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty);
179        } else {
180            mEtwsInfo = null;
181            mCmasInfo = null;
182        }
183    }
184
185    int getGeographicalScope() {
186        return geographicalScope;
187    }
188
189    int getSerialNumber() {
190        return serialNumber;
191    }
192
193    int getServiceCategory() {
194        return messageIdentifier;
195    }
196
197    int getDataCodingScheme() {
198        return dataCodingScheme;
199    }
200
201    int getPageIndex() {
202        return pageIndex;
203    }
204
205    int getNumberOfPages() {
206        return nrOfPages;
207    }
208
209    SmsCbEtwsInfo getEtwsInfo() {
210        return mEtwsInfo;
211    }
212
213    SmsCbCmasInfo getCmasInfo() {
214        return mCmasInfo;
215    }
216
217    /**
218     * Return whether this broadcast is an emergency (PWS) message type.
219     * @return true if this message is emergency type; false otherwise
220     */
221    boolean isEmergencyMessage() {
222        return messageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
223                && messageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
224    }
225
226    /**
227     * Return whether this broadcast is an ETWS emergency message type.
228     * @return true if this message is ETWS emergency type; false otherwise
229     */
230    private boolean isEtwsMessage() {
231        return (messageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
232                == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
233    }
234
235    /**
236     * Return whether this broadcast is an ETWS primary notification.
237     * @return true if this message is an ETWS primary notification; false otherwise
238     */
239    boolean isEtwsPrimaryNotification() {
240        return format == FORMAT_ETWS_PRIMARY;
241    }
242
243    /**
244     * Return whether this broadcast is in UMTS format.
245     * @return true if this message is in UMTS format; false otherwise
246     */
247    boolean isUmtsFormat() {
248        return format == FORMAT_UMTS;
249    }
250
251    /**
252     * Return whether this message is a CMAS emergency message type.
253     * @return true if this message is CMAS emergency type; false otherwise
254     */
255    private boolean isCmasMessage() {
256        return messageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
257                && messageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
258    }
259
260    /**
261     * Return whether the popup alert flag is set for an ETWS warning notification.
262     * This method assumes that the message ID has already been checked for ETWS type.
263     *
264     * @return true if the message code indicates a popup alert should be displayed
265     */
266    private boolean isEtwsPopupAlert() {
267        return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
268    }
269
270    /**
271     * Return whether the emergency user alert flag is set for an ETWS warning notification.
272     * This method assumes that the message ID has already been checked for ETWS type.
273     *
274     * @return true if the message code indicates an emergency user alert
275     */
276    private boolean isEtwsEmergencyUserAlert() {
277        return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0;
278    }
279
280    /**
281     * Returns the warning type for an ETWS warning notification.
282     * This method assumes that the message ID has already been checked for ETWS type.
283     *
284     * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24
285     */
286    private int getEtwsWarningType() {
287        return messageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING;
288    }
289
290    /**
291     * Returns the message class for a CMAS warning notification.
292     * This method assumes that the message ID has already been checked for CMAS type.
293     * @return the CMAS message class as defined in {@link SmsCbCmasInfo}
294     */
295    private int getCmasMessageClass() {
296        switch (messageIdentifier) {
297            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
298                return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
299
300            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
301            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
302            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
303            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
304                return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
305
306            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
307            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
308            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
309            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
310                return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
311
312            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
313                return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
314
315            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
316                return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
317
318            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
319                return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
320
321            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
322                return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
323
324            default:
325                return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
326        }
327    }
328
329    /**
330     * Returns the severity for a CMAS warning notification. This is only available for extreme
331     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
332     * This method assumes that the message ID has already been checked for CMAS type.
333     * @return the CMAS severity as defined in {@link SmsCbCmasInfo}
334     */
335    private int getCmasSeverity() {
336        switch (messageIdentifier) {
337            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
338            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
339            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
340            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
341                return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
342
343            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
344            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
345            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
346            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
347                return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
348
349            default:
350                return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
351        }
352    }
353
354    /**
355     * Returns the urgency for a CMAS warning notification. This is only available for extreme
356     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
357     * This method assumes that the message ID has already been checked for CMAS type.
358     * @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
359     */
360    private int getCmasUrgency() {
361        switch (messageIdentifier) {
362            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
363            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
364            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
365            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
366                return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
367
368            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
369            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
370            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
371            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
372                return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
373
374            default:
375                return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
376        }
377    }
378
379    /**
380     * Returns the certainty for a CMAS warning notification. This is only available for extreme
381     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
382     * This method assumes that the message ID has already been checked for CMAS type.
383     * @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
384     */
385    private int getCmasCertainty() {
386        switch (messageIdentifier) {
387            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
388            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
389            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
390            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
391                return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
392
393            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
394            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
395            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
396            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
397                return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
398
399            default:
400                return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
401        }
402    }
403
404    @Override
405    public String toString() {
406        return "SmsCbHeader{GS=" + geographicalScope + ", serialNumber=0x" +
407                Integer.toHexString(serialNumber) +
408                ", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) +
409                ", DCS=0x" + Integer.toHexString(dataCodingScheme) +
410                ", page " + pageIndex + " of " + nrOfPages + '}';
411    }
412}
413