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.util.Log;
25
26import com.android.internal.telephony.CommandsInterface;
27import com.android.internal.telephony.IccIoResult;
28import com.android.internal.telephony.IccUtils;
29import com.android.internal.telephony.cat.ComprehensionTlvTag;
30
31/**
32 * Handler for SMS-PP data download messages.
33 * See 3GPP TS 31.111 section 7.1.1
34 */
35public class UsimDataDownloadHandler extends Handler {
36    private static final String TAG = "UsimDataDownloadHandler";
37
38    /** BER-TLV tag for SMS-PP download. TS 31.111 section 9.1. */
39    private static final int BER_SMS_PP_DOWNLOAD_TAG      = 0xd1;
40
41    /** Device identity value for UICC (destination). */
42    private static final int DEV_ID_UICC        = 0x81;
43
44    /** Device identity value for network (source). */
45    private static final int DEV_ID_NETWORK     = 0x83;
46
47    /** Message containing new SMS-PP message to process. */
48    private static final int EVENT_START_DATA_DOWNLOAD = 1;
49
50    /** Response to SMS-PP download envelope command. */
51    private static final int EVENT_SEND_ENVELOPE_RESPONSE = 2;
52
53    private final CommandsInterface mCI;
54
55    public UsimDataDownloadHandler(CommandsInterface commandsInterface) {
56        mCI = commandsInterface;
57    }
58
59    /**
60     * Start an SMS-PP data download for the specified message. Can be called from a different
61     * thread than this Handler is running on.
62     *
63     * @param smsMessage the message to process
64     * @return Activity.RESULT_OK on success; Intents.RESULT_SMS_GENERIC_ERROR on failure
65     */
66    public int startDataDownload(SmsMessage smsMessage) {
67        if (sendMessage(obtainMessage(EVENT_START_DATA_DOWNLOAD, smsMessage))) {
68            return Activity.RESULT_OK;  // we will send SMS ACK/ERROR based on UICC response
69        } else {
70            Log.e(TAG, "startDataDownload failed to send message to start data download.");
71            return Intents.RESULT_SMS_GENERIC_ERROR;
72        }
73    }
74
75    private void handleDataDownload(SmsMessage smsMessage) {
76        int dcs = smsMessage.getDataCodingScheme();
77        int pid = smsMessage.getProtocolIdentifier();
78        byte[] pdu = smsMessage.getPdu();           // includes SC address
79
80        int scAddressLength = pdu[0] & 0xff;
81        int tpduIndex = scAddressLength + 1;        // start of TPDU
82        int tpduLength = pdu.length - tpduIndex;
83
84        int bodyLength = getEnvelopeBodyLength(scAddressLength, tpduLength);
85
86        // Add 1 byte for SMS-PP download tag and 1-2 bytes for BER-TLV length.
87        // See ETSI TS 102 223 Annex C for encoding of length and tags.
88        int totalLength = bodyLength + 1 + (bodyLength > 127 ? 2 : 1);
89
90        byte[] envelope = new byte[totalLength];
91        int index = 0;
92
93        // SMS-PP download tag and length (assumed to be < 256 bytes).
94        envelope[index++] = (byte) BER_SMS_PP_DOWNLOAD_TAG;
95        if (bodyLength > 127) {
96            envelope[index++] = (byte) 0x81;    // length 128-255 encoded as 0x81 + length
97        }
98        envelope[index++] = (byte) bodyLength;
99
100        // Device identities TLV
101        envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value());
102        envelope[index++] = (byte) 2;
103        envelope[index++] = (byte) DEV_ID_NETWORK;
104        envelope[index++] = (byte) DEV_ID_UICC;
105
106        // Address TLV (if present). Encoded length is assumed to be < 127 bytes.
107        if (scAddressLength != 0) {
108            envelope[index++] = (byte) ComprehensionTlvTag.ADDRESS.value();
109            envelope[index++] = (byte) scAddressLength;
110            System.arraycopy(pdu, 1, envelope, index, scAddressLength);
111            index += scAddressLength;
112        }
113
114        // SMS TPDU TLV. Length is assumed to be < 256 bytes.
115        envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.SMS_TPDU.value());
116        if (tpduLength > 127) {
117            envelope[index++] = (byte) 0x81;    // length 128-255 encoded as 0x81 + length
118        }
119        envelope[index++] = (byte) tpduLength;
120        System.arraycopy(pdu, tpduIndex, envelope, index, tpduLength);
121        index += tpduLength;
122
123        // Verify that we calculated the payload size correctly.
124        if (index != envelope.length) {
125            Log.e(TAG, "startDataDownload() calculated incorrect envelope length, aborting.");
126            acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR);
127            return;
128        }
129
130        String encodedEnvelope = IccUtils.bytesToHexString(envelope);
131        mCI.sendEnvelopeWithStatus(encodedEnvelope, obtainMessage(
132                EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid }));
133    }
134
135    /**
136     * Return the size in bytes of the envelope to send to the UICC, excluding the
137     * SMS-PP download tag byte and length byte(s). If the size returned is <= 127,
138     * the BER-TLV length will be encoded in 1 byte, otherwise 2 bytes are required.
139     *
140     * @param scAddressLength the length of the SMSC address, or zero if not present
141     * @param tpduLength the length of the TPDU from the SMS-PP message
142     * @return the number of bytes to allocate for the envelope command
143     */
144    private static int getEnvelopeBodyLength(int scAddressLength, int tpduLength) {
145        // Add 4 bytes for device identities TLV + 1 byte for SMS TPDU tag byte
146        int length = tpduLength + 5;
147        // Add 1 byte for TPDU length, or 2 bytes if length > 127
148        length += (tpduLength > 127 ? 2 : 1);
149        // Add length of address tag, if present (+ 2 bytes for tag and length)
150        if (scAddressLength != 0) {
151            length = length + 2 + scAddressLength;
152        }
153        return length;
154    }
155
156    /**
157     * Handle the response to the ENVELOPE command.
158     * @param response UICC response encoded as hexadecimal digits. First two bytes are the
159     *  UICC SW1 and SW2 status bytes.
160     */
161    private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid) {
162        int sw1 = response.sw1;
163        int sw2 = response.sw2;
164
165        boolean success;
166        if ((sw1 == 0x90 && sw2 == 0x00) || sw1 == 0x91) {
167            Log.d(TAG, "USIM data download succeeded: " + response.toString());
168            success = true;
169        } else if (sw1 == 0x93 && sw2 == 0x00) {
170            Log.e(TAG, "USIM data download failed: Toolkit busy");
171            acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY);
172            return;
173        } else if (sw1 == 0x62 || sw1 == 0x63) {
174            Log.e(TAG, "USIM data download failed: " + response.toString());
175            success = false;
176        } else {
177            Log.e(TAG, "Unexpected SW1/SW2 response from UICC: " + response.toString());
178            success = false;
179        }
180
181        byte[] responseBytes = response.payload;
182        if (responseBytes == null || responseBytes.length == 0) {
183            if (success) {
184                mCI.acknowledgeLastIncomingGsmSms(true, 0, null);
185            } else {
186                acknowledgeSmsWithError(
187                        CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
188            }
189            return;
190        }
191
192        byte[] smsAckPdu;
193        int index = 0;
194        if (success) {
195            smsAckPdu = new byte[responseBytes.length + 5];
196            smsAckPdu[index++] = 0x00;      // TP-MTI, TP-UDHI
197            smsAckPdu[index++] = 0x07;      // TP-PI: TP-PID, TP-DCS, TP-UDL present
198        } else {
199            smsAckPdu = new byte[responseBytes.length + 6];
200            smsAckPdu[index++] = 0x00;      // TP-MTI, TP-UDHI
201            smsAckPdu[index++] = (byte)
202                    CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR;  // TP-FCS
203            smsAckPdu[index++] = 0x07;      // TP-PI: TP-PID, TP-DCS, TP-UDL present
204        }
205
206        smsAckPdu[index++] = (byte) pid;
207        smsAckPdu[index++] = (byte) dcs;
208
209        if (is7bitDcs(dcs)) {
210            int septetCount = responseBytes.length * 8 / 7;
211            smsAckPdu[index++] = (byte) septetCount;
212        } else {
213            smsAckPdu[index++] = (byte) responseBytes.length;
214        }
215
216        System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length);
217
218        mCI.acknowledgeIncomingGsmSmsWithPdu(success,
219                IccUtils.bytesToHexString(smsAckPdu), null);
220    }
221
222    private void acknowledgeSmsWithError(int cause) {
223        mCI.acknowledgeLastIncomingGsmSms(false, cause, null);
224    }
225
226    /**
227     * Returns whether the DCS is 7 bit. If so, set TP-UDL to the septet count of TP-UD;
228     * otherwise, set TP-UDL to the octet count of TP-UD.
229     * @param dcs the TP-Data-Coding-Scheme field from the original download SMS
230     * @return true if the DCS specifies 7 bit encoding; false otherwise
231     */
232    private static boolean is7bitDcs(int dcs) {
233        // See 3GPP TS 23.038 section 4
234        return ((dcs & 0x8C) == 0x00) || ((dcs & 0xF4) == 0xF0);
235    }
236
237    /**
238     * Handle UICC envelope response and send SMS acknowledgement.
239     *
240     * @param msg the message to handle
241     */
242    @Override
243    public void handleMessage(Message msg) {
244        switch (msg.what) {
245            case EVENT_START_DATA_DOWNLOAD:
246                handleDataDownload((SmsMessage) msg.obj);
247                break;
248
249            case EVENT_SEND_ENVELOPE_RESPONSE:
250                AsyncResult ar = (AsyncResult) msg.obj;
251
252                if (ar.exception != null) {
253                    Log.e(TAG, "UICC Send Envelope failure, exception: " + ar.exception);
254                    acknowledgeSmsWithError(
255                            CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
256                    return;
257                }
258
259                int[] dcsPid = (int[]) ar.userObj;
260                sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, dcsPid[0], dcsPid[1]);
261                break;
262
263            default:
264                Log.e(TAG, "Ignoring unexpected message, what=" + msg.what);
265        }
266    }
267}
268