1/*
2 * Copyright (C) 2011 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.app.Activity;
20import android.os.AsyncResult;
21import android.os.Handler;
22import android.os.Message;
23import android.provider.Telephony.Sms.Intents;
24import android.telephony.PhoneNumberUtils;
25import android.telephony.Rlog;
26import android.telephony.SmsManager;
27
28import com.android.internal.telephony.CommandsInterface;
29import com.android.internal.telephony.cat.ComprehensionTlvTag;
30import com.android.internal.telephony.uicc.IccIoResult;
31import com.android.internal.telephony.uicc.IccUtils;
32import com.android.internal.telephony.uicc.UsimServiceTable;
33
34/**
35 * Handler for SMS-PP data download messages.
36 * See 3GPP TS 31.111 section 7.1.1
37 */
38public class UsimDataDownloadHandler extends Handler {
39    private static final String TAG = "UsimDataDownloadHandler";
40
41    /** BER-TLV tag for SMS-PP download. TS 31.111 section 9.1. */
42    private static final int BER_SMS_PP_DOWNLOAD_TAG      = 0xd1;
43
44    /** Device identity value for UICC (destination). */
45    private static final int DEV_ID_UICC        = 0x81;
46
47    /** Device identity value for network (source). */
48    private static final int DEV_ID_NETWORK     = 0x83;
49
50    /** Message containing new SMS-PP message to process. */
51    private static final int EVENT_START_DATA_DOWNLOAD = 1;
52
53    /** Response to SMS-PP download envelope command. */
54    private static final int EVENT_SEND_ENVELOPE_RESPONSE = 2;
55
56    /** Result of writing SM to UICC (when SMS-PP service is not available). */
57    private static final int EVENT_WRITE_SMS_COMPLETE = 3;
58
59    private final CommandsInterface mCi;
60
61    public UsimDataDownloadHandler(CommandsInterface commandsInterface) {
62        mCi = commandsInterface;
63    }
64
65    /**
66     * Handle SMS-PP data download messages. Normally these are automatically handled by the
67     * radio, but we may have to deal with this type of SM arriving via the IMS stack. If the
68     * data download service is not enabled, try to write to the USIM as an SMS, and send the
69     * UICC response as the acknowledgment to the SMSC.
70     *
71     * @param ust the UsimServiceTable, to check if data download is enabled
72     * @param smsMessage the SMS message to process
73     * @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure
74     */
75    int handleUsimDataDownload(UsimServiceTable ust, SmsMessage smsMessage) {
76        // If we receive an SMS-PP message before the UsimServiceTable has been loaded,
77        // assume that the data download service is not present. This is very unlikely to
78        // happen because the IMS connection will not be established until after the ISIM
79        // records have been loaded, after the USIM service table has been loaded.
80        if (ust != null && ust.isAvailable(
81                UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) {
82            Rlog.d(TAG, "Received SMS-PP data download, sending to UICC.");
83            return startDataDownload(smsMessage);
84        } else {
85            Rlog.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC.");
86            String smsc = IccUtils.bytesToHexString(
87                    PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
88                            smsMessage.getServiceCenterAddress()));
89            mCi.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc,
90                    IccUtils.bytesToHexString(smsMessage.getPdu()),
91                    obtainMessage(EVENT_WRITE_SMS_COMPLETE));
92            return Activity.RESULT_OK;  // acknowledge after response from write to USIM
93        }
94
95    }
96
97    /**
98     * Start an SMS-PP data download for the specified message. Can be called from a different
99     * thread than this Handler is running on.
100     *
101     * @param smsMessage the message to process
102     * @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure
103     */
104    public int startDataDownload(SmsMessage smsMessage) {
105        if (sendMessage(obtainMessage(EVENT_START_DATA_DOWNLOAD, smsMessage))) {
106            return Activity.RESULT_OK;  // we will send SMS ACK/ERROR based on UICC response
107        } else {
108            Rlog.e(TAG, "startDataDownload failed to send message to start data download.");
109            return Intents.RESULT_SMS_GENERIC_ERROR;
110        }
111    }
112
113    private void handleDataDownload(SmsMessage smsMessage) {
114        int dcs = smsMessage.getDataCodingScheme();
115        int pid = smsMessage.getProtocolIdentifier();
116        byte[] pdu = smsMessage.getPdu();           // includes SC address
117
118        int scAddressLength = pdu[0] & 0xff;
119        int tpduIndex = scAddressLength + 1;        // start of TPDU
120        int tpduLength = pdu.length - tpduIndex;
121
122        int bodyLength = getEnvelopeBodyLength(scAddressLength, tpduLength);
123
124        // Add 1 byte for SMS-PP download tag and 1-2 bytes for BER-TLV length.
125        // See ETSI TS 102 223 Annex C for encoding of length and tags.
126        int totalLength = bodyLength + 1 + (bodyLength > 127 ? 2 : 1);
127
128        byte[] envelope = new byte[totalLength];
129        int index = 0;
130
131        // SMS-PP download tag and length (assumed to be < 256 bytes).
132        envelope[index++] = (byte) BER_SMS_PP_DOWNLOAD_TAG;
133        if (bodyLength > 127) {
134            envelope[index++] = (byte) 0x81;    // length 128-255 encoded as 0x81 + length
135        }
136        envelope[index++] = (byte) bodyLength;
137
138        // Device identities TLV
139        envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value());
140        envelope[index++] = (byte) 2;
141        envelope[index++] = (byte) DEV_ID_NETWORK;
142        envelope[index++] = (byte) DEV_ID_UICC;
143
144        // Address TLV (if present). Encoded length is assumed to be < 127 bytes.
145        if (scAddressLength != 0) {
146            envelope[index++] = (byte) ComprehensionTlvTag.ADDRESS.value();
147            envelope[index++] = (byte) scAddressLength;
148            System.arraycopy(pdu, 1, envelope, index, scAddressLength);
149            index += scAddressLength;
150        }
151
152        // SMS TPDU TLV. Length is assumed to be < 256 bytes.
153        envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.SMS_TPDU.value());
154        if (tpduLength > 127) {
155            envelope[index++] = (byte) 0x81;    // length 128-255 encoded as 0x81 + length
156        }
157        envelope[index++] = (byte) tpduLength;
158        System.arraycopy(pdu, tpduIndex, envelope, index, tpduLength);
159        index += tpduLength;
160
161        // Verify that we calculated the payload size correctly.
162        if (index != envelope.length) {
163            Rlog.e(TAG, "startDataDownload() calculated incorrect envelope length, aborting.");
164            acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR);
165            return;
166        }
167
168        String encodedEnvelope = IccUtils.bytesToHexString(envelope);
169        mCi.sendEnvelopeWithStatus(encodedEnvelope, obtainMessage(
170                EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid }));
171    }
172
173    /**
174     * Return the size in bytes of the envelope to send to the UICC, excluding the
175     * SMS-PP download tag byte and length byte(s). If the size returned is <= 127,
176     * the BER-TLV length will be encoded in 1 byte, otherwise 2 bytes are required.
177     *
178     * @param scAddressLength the length of the SMSC address, or zero if not present
179     * @param tpduLength the length of the TPDU from the SMS-PP message
180     * @return the number of bytes to allocate for the envelope command
181     */
182    private static int getEnvelopeBodyLength(int scAddressLength, int tpduLength) {
183        // Add 4 bytes for device identities TLV + 1 byte for SMS TPDU tag byte
184        int length = tpduLength + 5;
185        // Add 1 byte for TPDU length, or 2 bytes if length > 127
186        length += (tpduLength > 127 ? 2 : 1);
187        // Add length of address tag, if present (+ 2 bytes for tag and length)
188        if (scAddressLength != 0) {
189            length = length + 2 + scAddressLength;
190        }
191        return length;
192    }
193
194    /**
195     * Handle the response to the ENVELOPE command.
196     * @param response UICC response encoded as hexadecimal digits. First two bytes are the
197     *  UICC SW1 and SW2 status bytes.
198     */
199    private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid) {
200        int sw1 = response.sw1;
201        int sw2 = response.sw2;
202
203        boolean success;
204        if ((sw1 == 0x90 && sw2 == 0x00) || sw1 == 0x91) {
205            Rlog.d(TAG, "USIM data download succeeded: " + response.toString());
206            success = true;
207        } else if (sw1 == 0x93 && sw2 == 0x00) {
208            Rlog.e(TAG, "USIM data download failed: Toolkit busy");
209            acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY);
210            return;
211        } else if (sw1 == 0x62 || sw1 == 0x63) {
212            Rlog.e(TAG, "USIM data download failed: " + response.toString());
213            success = false;
214        } else {
215            Rlog.e(TAG, "Unexpected SW1/SW2 response from UICC: " + response.toString());
216            success = false;
217        }
218
219        byte[] responseBytes = response.payload;
220        if (responseBytes == null || responseBytes.length == 0) {
221            if (success) {
222                mCi.acknowledgeLastIncomingGsmSms(true, 0, null);
223            } else {
224                acknowledgeSmsWithError(
225                        CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
226            }
227            return;
228        }
229
230        byte[] smsAckPdu;
231        int index = 0;
232        if (success) {
233            smsAckPdu = new byte[responseBytes.length + 5];
234            smsAckPdu[index++] = 0x00;      // TP-MTI, TP-UDHI
235            smsAckPdu[index++] = 0x07;      // TP-PI: TP-PID, TP-DCS, TP-UDL present
236        } else {
237            smsAckPdu = new byte[responseBytes.length + 6];
238            smsAckPdu[index++] = 0x00;      // TP-MTI, TP-UDHI
239            smsAckPdu[index++] = (byte)
240                    CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR;  // TP-FCS
241            smsAckPdu[index++] = 0x07;      // TP-PI: TP-PID, TP-DCS, TP-UDL present
242        }
243
244        smsAckPdu[index++] = (byte) pid;
245        smsAckPdu[index++] = (byte) dcs;
246
247        if (is7bitDcs(dcs)) {
248            int septetCount = responseBytes.length * 8 / 7;
249            smsAckPdu[index++] = (byte) septetCount;
250        } else {
251            smsAckPdu[index++] = (byte) responseBytes.length;
252        }
253
254        System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length);
255
256        mCi.acknowledgeIncomingGsmSmsWithPdu(success,
257                IccUtils.bytesToHexString(smsAckPdu), null);
258    }
259
260    private void acknowledgeSmsWithError(int cause) {
261        mCi.acknowledgeLastIncomingGsmSms(false, cause, null);
262    }
263
264    /**
265     * Returns whether the DCS is 7 bit. If so, set TP-UDL to the septet count of TP-UD;
266     * otherwise, set TP-UDL to the octet count of TP-UD.
267     * @param dcs the TP-Data-Coding-Scheme field from the original download SMS
268     * @return true if the DCS specifies 7 bit encoding; false otherwise
269     */
270    private static boolean is7bitDcs(int dcs) {
271        // See 3GPP TS 23.038 section 4
272        return ((dcs & 0x8C) == 0x00) || ((dcs & 0xF4) == 0xF0);
273    }
274
275    /**
276     * Handle UICC envelope response and send SMS acknowledgement.
277     *
278     * @param msg the message to handle
279     */
280    @Override
281    public void handleMessage(Message msg) {
282        AsyncResult ar;
283
284        switch (msg.what) {
285            case EVENT_START_DATA_DOWNLOAD:
286                handleDataDownload((SmsMessage) msg.obj);
287                break;
288
289            case EVENT_SEND_ENVELOPE_RESPONSE:
290                ar = (AsyncResult) msg.obj;
291
292                if (ar.exception != null) {
293                    Rlog.e(TAG, "UICC Send Envelope failure, exception: " + ar.exception);
294                    acknowledgeSmsWithError(
295                            CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
296                    return;
297                }
298
299                int[] dcsPid = (int[]) ar.userObj;
300                sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, dcsPid[0], dcsPid[1]);
301                break;
302
303            case EVENT_WRITE_SMS_COMPLETE:
304                ar = (AsyncResult) msg.obj;
305                if (ar.exception == null) {
306                    Rlog.d(TAG, "Successfully wrote SMS-PP message to UICC");
307                    mCi.acknowledgeLastIncomingGsmSms(true, 0, null);
308                } else {
309                    Rlog.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception);
310                    mCi.acknowledgeLastIncomingGsmSms(false,
311                            CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null);
312                }
313                break;
314
315            default:
316                Rlog.e(TAG, "Ignoring unexpected message, what=" + msg.what);
317        }
318    }
319}
320