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 
17 package com.android.internal.telephony.gsm;
18 
19 import android.app.Activity;
20 import android.content.res.Resources;
21 import android.content.res.Resources.NotFoundException;
22 import android.os.AsyncResult;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.provider.Telephony.Sms.Intents;
26 import android.telephony.PhoneNumberUtils;
27 import android.telephony.SmsManager;
28 import android.telephony.ims.stub.ImsSmsImplBase;
29 
30 import com.android.ims.ImsException;
31 import com.android.ims.ImsManager;
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.telephony.CommandsInterface;
34 import com.android.internal.telephony.InboundSmsHandler;
35 import com.android.internal.telephony.PhoneFactory;
36 import com.android.internal.telephony.cat.ComprehensionTlvTag;
37 import com.android.internal.telephony.metrics.TelephonyMetrics;
38 import com.android.internal.telephony.uicc.IccIoResult;
39 import com.android.internal.telephony.uicc.IccUtils;
40 import com.android.internal.telephony.uicc.UsimServiceTable;
41 import com.android.telephony.Rlog;
42 
43 /**
44  * Handler for SMS-PP data download messages.
45  * See 3GPP TS 31.111 section 7.1.1
46  */
47 public class UsimDataDownloadHandler extends Handler {
48     private static final String TAG = "UsimDataDownloadHandler";
49 
50     /** BER-TLV tag for SMS-PP download. TS 31.111 section 9.1. */
51     private static final int BER_SMS_PP_DOWNLOAD_TAG      = 0xd1;
52 
53     /** Device identity value for UICC (destination). */
54     private static final int DEV_ID_UICC        = 0x81;
55 
56     /** Device identity value for network (source). */
57     private static final int DEV_ID_NETWORK     = 0x83;
58 
59     /** Message containing new SMS-PP message to process. */
60     private static final int EVENT_START_DATA_DOWNLOAD = 1;
61 
62     /** Response to SMS-PP download envelope command. */
63     private static final int EVENT_SEND_ENVELOPE_RESPONSE = 2;
64 
65     /** Result of writing SM to UICC (when SMS-PP service is not available). */
66     private static final int EVENT_WRITE_SMS_COMPLETE = 3;
67 
68     private final CommandsInterface mCi;
69     private final int mPhoneId;
70     private ImsManager mImsManager;
71     Resources mResource;
72 
UsimDataDownloadHandler(CommandsInterface commandsInterface, int phoneId)73     public UsimDataDownloadHandler(CommandsInterface commandsInterface, int phoneId) {
74         mCi = commandsInterface;
75         mPhoneId = phoneId;
76         mImsManager = null; // will get initialized when ImsManager connection is ready
77         mResource = Resources.getSystem();
78     }
79 
80     /**
81      * Handle SMS-PP data download messages. Normally these are automatically handled by the
82      * radio, but we may have to deal with this type of SM arriving via the IMS stack. If the
83      * data download service is not enabled, try to write to the USIM as an SMS, and send the
84      * UICC response as the acknowledgment to the SMSC.
85      *
86      * @param ust the UsimServiceTable, to check if data download is enabled
87      * @param smsMessage the SMS message to process
88      * @param smsSource the source of the SMS message
89      * @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure
90      */
handleUsimDataDownload(UsimServiceTable ust, SmsMessage smsMessage, @InboundSmsHandler.SmsSource int smsSource, int token)91     int handleUsimDataDownload(UsimServiceTable ust, SmsMessage smsMessage,
92             @InboundSmsHandler.SmsSource int smsSource, int token) {
93         // If we receive an SMS-PP message before the UsimServiceTable has been loaded,
94         // assume that the data download service is not present. This is very unlikely to
95         // happen because the IMS connection will not be established until after the ISIM
96         // records have been loaded, after the USIM service table has been loaded.
97         if (ust != null && ust.isAvailable(
98                 UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) {
99             Rlog.d(TAG, "Received SMS-PP data download, sending to UICC.");
100             return startDataDownload(smsMessage, smsSource, token);
101         } else {
102             Rlog.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC.");
103             String smsc = IccUtils.bytesToHexString(
104                     PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
105                             smsMessage.getServiceCenterAddress()));
106             mCi.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc,
107                     IccUtils.bytesToHexString(smsMessage.getPdu()),
108                     obtainMessage(EVENT_WRITE_SMS_COMPLETE,
109                             new int[]{ smsSource, smsMessage.mMessageRef, token }));
110             addUsimDataDownloadToMetrics(false, smsSource);
111             return Activity.RESULT_OK;  // acknowledge after response from write to USIM
112         }
113 
114     }
115 
116     /**
117      * Start an SMS-PP data download for the specified message. Can be called from a different
118      * thread than this Handler is running on.
119      *
120      * @param smsMessage the message to process
121      * @param smsSource the source of the SMS message
122      * @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure
123      */
startDataDownload(SmsMessage smsMessage, @InboundSmsHandler.SmsSource int smsSource, int token)124     public int startDataDownload(SmsMessage smsMessage,
125             @InboundSmsHandler.SmsSource int smsSource, int token) {
126         if (sendMessage(obtainMessage(EVENT_START_DATA_DOWNLOAD,
127                 smsSource, token, smsMessage))) {
128             return Activity.RESULT_OK;  // we will send SMS ACK/ERROR based on UICC response
129         } else {
130             Rlog.e(TAG, "startDataDownload failed to send message to start data download.");
131             return Intents.RESULT_SMS_GENERIC_ERROR;
132         }
133     }
134 
handleDataDownload(SmsMessage smsMessage, @InboundSmsHandler.SmsSource int smsSource, int token)135     private void handleDataDownload(SmsMessage smsMessage,
136             @InboundSmsHandler.SmsSource int smsSource, int token) {
137         int dcs = smsMessage.getDataCodingScheme();
138         int pid = smsMessage.getProtocolIdentifier();
139         byte[] pdu = smsMessage.getPdu();           // includes SC address
140 
141         int scAddressLength = pdu[0] & 0xff;
142         int tpduIndex = scAddressLength + 1;        // start of TPDU
143         int tpduLength = pdu.length - tpduIndex;
144 
145         int bodyLength = getEnvelopeBodyLength(scAddressLength, tpduLength);
146 
147         // Add 1 byte for SMS-PP download tag and 1-2 bytes for BER-TLV length.
148         // See ETSI TS 102 223 Annex C for encoding of length and tags.
149         int totalLength = bodyLength + 1 + (bodyLength > 127 ? 2 : 1);
150 
151         byte[] envelope = new byte[totalLength];
152         int index = 0;
153         Rlog.d(TAG, "smsSource: " + smsSource + "Token: " + token);
154 
155         // SMS-PP download tag and length (assumed to be < 256 bytes).
156         envelope[index++] = (byte) BER_SMS_PP_DOWNLOAD_TAG;
157         if (bodyLength > 127) {
158             envelope[index++] = (byte) 0x81;    // length 128-255 encoded as 0x81 + length
159         }
160         envelope[index++] = (byte) bodyLength;
161 
162         // Device identities TLV
163         envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value());
164         envelope[index++] = (byte) 2;
165         envelope[index++] = (byte) DEV_ID_NETWORK;
166         envelope[index++] = (byte) DEV_ID_UICC;
167 
168         // Address TLV (if present). Encoded length is assumed to be < 127 bytes.
169         if (scAddressLength != 0) {
170             envelope[index++] = (byte) ComprehensionTlvTag.ADDRESS.value();
171             envelope[index++] = (byte) scAddressLength;
172             System.arraycopy(pdu, 1, envelope, index, scAddressLength);
173             index += scAddressLength;
174         }
175 
176         // SMS TPDU TLV. Length is assumed to be < 256 bytes.
177         envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.SMS_TPDU.value());
178         if (tpduLength > 127) {
179             envelope[index++] = (byte) 0x81;    // length 128-255 encoded as 0x81 + length
180         }
181         envelope[index++] = (byte) tpduLength;
182         System.arraycopy(pdu, tpduIndex, envelope, index, tpduLength);
183         index += tpduLength;
184 
185         // Verify that we calculated the payload size correctly.
186         if (index != envelope.length) {
187             Rlog.e(TAG, "startDataDownload() calculated incorrect envelope length, aborting.");
188             acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR,
189                     smsSource, token, smsMessage.mMessageRef);
190             addUsimDataDownloadToMetrics(false, smsSource);
191             return;
192         }
193 
194         String encodedEnvelope = IccUtils.bytesToHexString(envelope);
195         mCi.sendEnvelopeWithStatus(encodedEnvelope, obtainMessage(
196                 EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid, smsSource,
197                     smsMessage.mMessageRef, token }));
198 
199         addUsimDataDownloadToMetrics(true, smsSource);
200     }
201 
202     /**
203      * Return the size in bytes of the envelope to send to the UICC, excluding the
204      * SMS-PP download tag byte and length byte(s). If the size returned is <= 127,
205      * the BER-TLV length will be encoded in 1 byte, otherwise 2 bytes are required.
206      *
207      * @param scAddressLength the length of the SMSC address, or zero if not present
208      * @param tpduLength the length of the TPDU from the SMS-PP message
209      * @return the number of bytes to allocate for the envelope command
210      */
getEnvelopeBodyLength(int scAddressLength, int tpduLength)211     private static int getEnvelopeBodyLength(int scAddressLength, int tpduLength) {
212         // Add 4 bytes for device identities TLV + 1 byte for SMS TPDU tag byte
213         int length = tpduLength + 5;
214         // Add 1 byte for TPDU length, or 2 bytes if length > 127
215         length += (tpduLength > 127 ? 2 : 1);
216         // Add length of address tag, if present (+ 2 bytes for tag and length)
217         if (scAddressLength != 0) {
218             length = length + 2 + scAddressLength;
219         }
220         return length;
221     }
222 
223     /**
224      * Handle the response to the ENVELOPE command.
225      * @param response UICC response encoded as hexadecimal digits. First two bytes are the
226      *  UICC SW1 and SW2 status bytes.
227      */
sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid, int smsSource, int token, int messageRef)228     private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid,
229             int smsSource, int token, int messageRef) {
230         int sw1 = response.sw1;
231         int sw2 = response.sw2;
232 
233         boolean success;
234         if ((sw1 == 0x90 && sw2 == 0x00) || sw1 == 0x91) {
235             Rlog.d(TAG, "USIM data download succeeded: " + response.toString());
236             success = true;
237         } else if (sw1 == 0x93 && sw2 == 0x00) {
238             Rlog.e(TAG, "USIM data download failed: Toolkit busy");
239             acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY,
240                     smsSource, token, messageRef);
241             return;
242         } else if (sw1 == 0x62 || sw1 == 0x63) {
243             Rlog.e(TAG, "USIM data download failed: " + response.toString());
244             success = false;
245         } else {
246             Rlog.e(TAG, "Unexpected SW1/SW2 response from UICC: " + response.toString());
247             success = false;
248         }
249 
250         byte[] responseBytes = response.payload;
251         if (responseBytes == null || responseBytes.length == 0) {
252             if (success) {
253                 acknowledgeSmsWithSuccess(0, smsSource, token, messageRef);
254             } else {
255                 acknowledgeSmsWithError(
256                         CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR, smsSource,
257                         token, messageRef);
258             }
259             return;
260         }
261 
262         byte[] smsAckPdu;
263         int index = 0;
264         if (success) {
265             smsAckPdu = new byte[responseBytes.length + 5];
266             smsAckPdu[index++] = 0x00;      // TP-MTI, TP-UDHI
267             smsAckPdu[index++] = 0x07;      // TP-PI: TP-PID, TP-DCS, TP-UDL present
268         } else {
269             smsAckPdu = new byte[responseBytes.length + 6];
270             smsAckPdu[index++] = 0x00;      // TP-MTI, TP-UDHI
271             smsAckPdu[index++] = (byte)
272                     CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR;  // TP-FCS
273             smsAckPdu[index++] = 0x07;      // TP-PI: TP-PID, TP-DCS, TP-UDL present
274         }
275 
276         smsAckPdu[index++] = (byte) pid;
277         smsAckPdu[index++] = (byte) dcs;
278 
279         if (is7bitDcs(dcs)) {
280             int septetCount = responseBytes.length * 8 / 7;
281             smsAckPdu[index++] = (byte) septetCount;
282         } else {
283             smsAckPdu[index++] = (byte) responseBytes.length;
284         }
285 
286         System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length);
287 
288         if (smsSource == InboundSmsHandler.SOURCE_INJECTED_FROM_IMS && ackViaIms()) {
289             acknowledgeImsSms(token, messageRef, true, smsAckPdu);
290         } else {
291             mCi.acknowledgeIncomingGsmSmsWithPdu(success,
292                     IccUtils.bytesToHexString(smsAckPdu), null);
293         }
294     }
295 
acknowledgeSmsWithSuccess(int cause, int smsSource, int token, int messageRef)296     private void acknowledgeSmsWithSuccess(int cause, int smsSource, int token, int messageRef) {
297         Rlog.d(TAG, "acknowledgeSmsWithSuccess- cause: " + cause + " smsSource: " + smsSource
298                 + " token: " + token + " messageRef: " + messageRef);
299         if (smsSource == InboundSmsHandler.SOURCE_INJECTED_FROM_IMS && ackViaIms()) {
300             acknowledgeImsSms(token, messageRef, true, null);
301         } else {
302             mCi.acknowledgeLastIncomingGsmSms(true, cause, null);
303         }
304     }
305 
acknowledgeSmsWithError(int cause, int smsSource, int token, int messageRef)306     private void acknowledgeSmsWithError(int cause, int smsSource, int token, int messageRef) {
307         Rlog.d(TAG, "acknowledgeSmsWithError- cause: " + cause + " smsSource: " + smsSource
308                 + " token: " + token + " messageRef: " + messageRef);
309         if (smsSource == InboundSmsHandler.SOURCE_INJECTED_FROM_IMS && ackViaIms()) {
310             acknowledgeImsSms(token, messageRef, false, null);
311         } else {
312             mCi.acknowledgeLastIncomingGsmSms(false, cause, null);
313         }
314     }
315 
316     /**
317      * Returns whether the DCS is 7 bit. If so, set TP-UDL to the septet count of TP-UD;
318      * otherwise, set TP-UDL to the octet count of TP-UD.
319      * @param dcs the TP-Data-Coding-Scheme field from the original download SMS
320      * @return true if the DCS specifies 7 bit encoding; false otherwise
321      */
is7bitDcs(int dcs)322     private static boolean is7bitDcs(int dcs) {
323         // See 3GPP TS 23.038 section 4
324         return ((dcs & 0x8C) == 0x00) || ((dcs & 0xF4) == 0xF0);
325     }
326 
327     /**
328      * Add the SMS-PP data to the telephony metrics, indicating if the message was forwarded
329      * to the USIM. The metrics does not cover the case where the SMS-PP might be rejected
330      * by the USIM itself.
331      */
addUsimDataDownloadToMetrics(boolean result, @InboundSmsHandler.SmsSource int smsSource)332     private void addUsimDataDownloadToMetrics(boolean result,
333             @InboundSmsHandler.SmsSource int smsSource) {
334         TelephonyMetrics metrics = TelephonyMetrics.getInstance();
335         metrics.writeIncomingSMSPP(mPhoneId, android.telephony.SmsMessage.FORMAT_3GPP, result);
336         PhoneFactory.getPhone(mPhoneId).getSmsStats().onIncomingSmsPP(smsSource, result);
337     }
338 
339     /**
340      * Route resposes via ImsManager based on config
341      */
ackViaIms()342     private boolean ackViaIms() {
343         boolean isViaIms;
344 
345         try {
346             isViaIms = mResource.getBoolean(
347                     com.android.internal.R.bool.config_smppsim_response_via_ims);
348         } catch (NotFoundException e) {
349             isViaIms = false;
350         }
351 
352         Rlog.d(TAG, "ackViaIms : " + isViaIms);
353         return isViaIms;
354     }
355 
356     /**
357      * Acknowledges IMS SMS and delivers the result based on the envelope or SIM saving respose
358      * received from SIM for SMS-PP Data.
359      */
acknowledgeImsSms(int token, int messageRef, boolean success, byte[] pdu)360     private void acknowledgeImsSms(int token, int messageRef, boolean success, byte[] pdu) {
361         int result = success ? ImsSmsImplBase.DELIVER_STATUS_OK :
362                     ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC;
363         Rlog.d(TAG, "sending result via acknowledgeImsSms: " + result + " token: " + token);
364 
365         try {
366             if (mImsManager != null) {
367                 if (pdu != null && pdu.length > 0) {
368                     mImsManager.acknowledgeSms(token, messageRef, result, pdu);
369                 } else {
370                     mImsManager.acknowledgeSms(token, messageRef, result);
371                 }
372             }
373         } catch (ImsException e) {
374             Rlog.e(TAG, "Failed to acknowledgeSms(). Error: " + e.getMessage());
375         }
376     }
377 
378     /**
379      * Handle UICC envelope response and send SMS acknowledgement.
380      *
381      * @param msg the message to handle
382      */
383     @Override
handleMessage(Message msg)384     public void handleMessage(Message msg) {
385         AsyncResult ar;
386         int smsSource = InboundSmsHandler.SOURCE_INJECTED_FROM_UNKNOWN;
387         int token = 0;
388         int messageRef = 0;
389         int[] responseInfo;
390 
391         switch (msg.what) {
392             case EVENT_START_DATA_DOWNLOAD:
393                 Rlog.d(TAG, "EVENT_START_DATA_DOWNLOAD");
394                 handleDataDownload((SmsMessage) msg.obj, msg.arg1 /* smsSource */,
395                         msg.arg2 /* token */);
396                 break;
397 
398             case EVENT_SEND_ENVELOPE_RESPONSE:
399                 ar = (AsyncResult) msg.obj;
400 
401                 responseInfo = (int[]) ar.userObj;
402                 smsSource = responseInfo[2];
403                 messageRef = responseInfo[3];
404                 token = responseInfo[4];
405 
406                 Rlog.d(TAG, "Received EVENT_SEND_ENVELOPE_RESPONSE from source : " + smsSource);
407 
408                 if (ar.exception != null) {
409                     Rlog.e(TAG, "UICC Send Envelope failure, exception: " + ar.exception);
410 
411                     acknowledgeSmsWithError(
412                             CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR,
413                             smsSource, token, messageRef);
414                     return;
415                 }
416 
417                 Rlog.d(TAG, "Successful in sending envelope response");
418                 sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, responseInfo[0],
419                             responseInfo[1], smsSource, token, messageRef);
420                 break;
421 
422             case EVENT_WRITE_SMS_COMPLETE:
423                 ar = (AsyncResult) msg.obj;
424 
425                 responseInfo = (int[]) ar.userObj;
426                 smsSource = responseInfo[0];
427                 messageRef = responseInfo[1];
428                 token = responseInfo[2];
429 
430                 Rlog.d(TAG, "Received EVENT_WRITE_SMS_COMPLETE from source : " + smsSource);
431 
432                 if (ar.exception == null) {
433                     Rlog.d(TAG, "Successfully wrote SMS-PP message to UICC");
434                     acknowledgeSmsWithSuccess(0, smsSource, token, messageRef);
435                 } else {
436                     Rlog.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception);
437                     acknowledgeSmsWithError(
438                             CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR,
439                             smsSource, token, messageRef);
440                 }
441                 break;
442 
443             default:
444                 Rlog.e(TAG, "Ignoring unexpected message, what=" + msg.what);
445         }
446     }
447 
448     /**
449      * Called when ImsManager connection is ready. ImsManager object will be used to send ACK to IMS
450      * which doesn't use RIL interface.
451      * @param imsManager object
452      */
setImsManager(ImsManager imsManager)453     public void setImsManager(ImsManager imsManager) {
454         mImsManager = imsManager;
455     }
456 
457     /**
458      * Called to set mocked object of type Resources during unit testing of this file.
459      * @param resource object
460      */
461     @VisibleForTesting
setResourcesForTest(Resources resource)462     public void setResourcesForTest(Resources resource) {
463         mResource = resource;
464         Rlog.d(TAG, "setResourcesForTest");
465     }
466 }
467