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_ETWS) {
99            format = FORMAT_ETWS_PRIMARY;
100            geographicalScope = (pdu[0] & 0xc0) >> 6;
101            serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
102            messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
103            dataCodingScheme = -1;
104            pageIndex = -1;
105            nrOfPages = -1;
106            boolean emergencyUserAlert = (pdu[4] & 0x1) != 0;
107            boolean activatePopup = (pdu[5] & 0x80) != 0;
108            int warningType = (pdu[4] & 0xfe) >> 1;
109            byte[] warningSecurityInfo;
110            // copy the Warning-Security-Information, if present
111            if (pdu.length > PDU_HEADER_LENGTH) {
112                warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length);
113            } else {
114                warningSecurityInfo = null;
115            }
116            mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
117                    warningSecurityInfo);
118            mCmasInfo = null;
119            return;     // skip the ETWS/CMAS initialization code for regular notifications
120        } else if (pdu.length <= PDU_LENGTH_GSM) {
121            // GSM pdus are no more than 88 bytes
122            format = FORMAT_GSM;
123            geographicalScope = (pdu[0] & 0xc0) >> 6;
124            serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
125            messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
126            dataCodingScheme = pdu[4] & 0xff;
127
128            // Check for invalid page parameter
129            int pageIndex = (pdu[5] & 0xf0) >> 4;
130            int nrOfPages = pdu[5] & 0x0f;
131
132            if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
133                pageIndex = 1;
134                nrOfPages = 1;
135            }
136
137            this.pageIndex = pageIndex;
138            this.nrOfPages = nrOfPages;
139        } else {
140            // UMTS pdus are always at least 90 bytes since the payload includes
141            // a number-of-pages octet and also one length octet per page
142            format = FORMAT_UMTS;
143
144            int messageType = pdu[0];
145
146            if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
147                throw new IllegalArgumentException("Unsupported message type " + messageType);
148            }
149
150            messageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
151            geographicalScope = (pdu[3] & 0xc0) >> 6;
152            serialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
153            dataCodingScheme = pdu[5] & 0xff;
154
155            // We will always consider a UMTS message as having one single page
156            // since there's only one instance of the header, even though the
157            // actual payload may contain several pages.
158            pageIndex = 1;
159            nrOfPages = 1;
160        }
161
162        if (isEtwsMessage()) {
163            boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
164            boolean activatePopup = isEtwsPopupAlert();
165            int warningType = getEtwsWarningType();
166            mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, null);
167            mCmasInfo = null;
168        } else if (isCmasMessage()) {
169            int messageClass = getCmasMessageClass();
170            int severity = getCmasSeverity();
171            int urgency = getCmasUrgency();
172            int certainty = getCmasCertainty();
173            mEtwsInfo = null;
174            mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
175                    SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty);
176        } else {
177            mEtwsInfo = null;
178            mCmasInfo = null;
179        }
180    }
181
182    int getGeographicalScope() {
183        return geographicalScope;
184    }
185
186    int getSerialNumber() {
187        return serialNumber;
188    }
189
190    int getServiceCategory() {
191        return messageIdentifier;
192    }
193
194    int getDataCodingScheme() {
195        return dataCodingScheme;
196    }
197
198    int getPageIndex() {
199        return pageIndex;
200    }
201
202    int getNumberOfPages() {
203        return nrOfPages;
204    }
205
206    SmsCbEtwsInfo getEtwsInfo() {
207        return mEtwsInfo;
208    }
209
210    SmsCbCmasInfo getCmasInfo() {
211        return mCmasInfo;
212    }
213
214    /**
215     * Return whether this broadcast is an emergency (PWS) message type.
216     * @return true if this message is emergency type; false otherwise
217     */
218    boolean isEmergencyMessage() {
219        return messageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
220                && messageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
221    }
222
223    /**
224     * Return whether this broadcast is an ETWS emergency message type.
225     * @return true if this message is ETWS emergency type; false otherwise
226     */
227    private boolean isEtwsMessage() {
228        return (messageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
229                == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
230    }
231
232    /**
233     * Return whether this broadcast is an ETWS primary notification.
234     * @return true if this message is an ETWS primary notification; false otherwise
235     */
236    boolean isEtwsPrimaryNotification() {
237        return format == FORMAT_ETWS_PRIMARY;
238    }
239
240    /**
241     * Return whether this broadcast is in UMTS format.
242     * @return true if this message is in UMTS format; false otherwise
243     */
244    boolean isUmtsFormat() {
245        return format == FORMAT_UMTS;
246    }
247
248    /**
249     * Return whether this message is a CMAS emergency message type.
250     * @return true if this message is CMAS emergency type; false otherwise
251     */
252    private boolean isCmasMessage() {
253        return messageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
254                && messageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
255    }
256
257    /**
258     * Return whether the popup alert flag is set for an ETWS warning notification.
259     * This method assumes that the message ID has already been checked for ETWS type.
260     *
261     * @return true if the message code indicates a popup alert should be displayed
262     */
263    private boolean isEtwsPopupAlert() {
264        return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
265    }
266
267    /**
268     * Return whether the emergency user alert flag is set for an ETWS warning notification.
269     * This method assumes that the message ID has already been checked for ETWS type.
270     *
271     * @return true if the message code indicates an emergency user alert
272     */
273    private boolean isEtwsEmergencyUserAlert() {
274        return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0;
275    }
276
277    /**
278     * Returns the warning type for an ETWS warning notification.
279     * This method assumes that the message ID has already been checked for ETWS type.
280     *
281     * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24
282     */
283    private int getEtwsWarningType() {
284        return messageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING;
285    }
286
287    /**
288     * Returns the message class for a CMAS warning notification.
289     * This method assumes that the message ID has already been checked for CMAS type.
290     * @return the CMAS message class as defined in {@link SmsCbCmasInfo}
291     */
292    private int getCmasMessageClass() {
293        switch (messageIdentifier) {
294            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
295                return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
296
297            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
298            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
299            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
300            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
301                return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
302
303            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
304            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
305            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
306            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
307                return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
308
309            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
310                return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
311
312            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
313                return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
314
315            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
316                return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
317
318            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
319                return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
320
321            default:
322                return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
323        }
324    }
325
326    /**
327     * Returns the severity for a CMAS warning notification. This is only available for extreme
328     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
329     * This method assumes that the message ID has already been checked for CMAS type.
330     * @return the CMAS severity as defined in {@link SmsCbCmasInfo}
331     */
332    private int getCmasSeverity() {
333        switch (messageIdentifier) {
334            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
335            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
336            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
337            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
338                return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
339
340            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
341            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
342            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
343            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
344                return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
345
346            default:
347                return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
348        }
349    }
350
351    /**
352     * Returns the urgency for a CMAS warning notification. This is only available for extreme
353     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
354     * This method assumes that the message ID has already been checked for CMAS type.
355     * @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
356     */
357    private int getCmasUrgency() {
358        switch (messageIdentifier) {
359            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
360            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
361            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
362            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
363                return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
364
365            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
366            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
367            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
368            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
369                return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
370
371            default:
372                return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
373        }
374    }
375
376    /**
377     * Returns the certainty for a CMAS warning notification. This is only available for extreme
378     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
379     * This method assumes that the message ID has already been checked for CMAS type.
380     * @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
381     */
382    private int getCmasCertainty() {
383        switch (messageIdentifier) {
384            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
385            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
386            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
387            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
388                return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
389
390            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
391            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
392            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
393            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
394                return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
395
396            default:
397                return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
398        }
399    }
400
401    @Override
402    public String toString() {
403        return "SmsCbHeader{GS=" + geographicalScope + ", serialNumber=0x" +
404                Integer.toHexString(serialNumber) +
405                ", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) +
406                ", DCS=0x" + Integer.toHexString(dataCodingScheme) +
407                ", page " + pageIndex + " of " + nrOfPages + '}';
408    }
409}
410