1 /*
2  * Copyright (C) 2006-2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.android.internal.telephony.cat;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.os.Build;
21 import android.os.SystemProperties;
22 import android.text.TextUtils;
23 
24 import com.android.internal.telephony.EncodeException;
25 import com.android.internal.telephony.GsmAlphabet;
26 import com.android.internal.telephony.cat.AppInterface.CommandType;
27 
28 import java.io.ByteArrayOutputStream;
29 import java.io.UnsupportedEncodingException;
30 import java.util.Calendar;
31 import java.util.TimeZone;
32 
33 abstract class ResponseData {
34 
35     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
ResponseData()36     ResponseData() {
37     }
38 
39     /**
40      * Format the data appropriate for TERMINAL RESPONSE and write it into
41      * the ByteArrayOutputStream object.
42      */
43     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
format(ByteArrayOutputStream buf)44     public abstract void format(ByteArrayOutputStream buf);
45 
writeLength(ByteArrayOutputStream buf, int length)46     public static void writeLength(ByteArrayOutputStream buf, int length) {
47         // As per ETSI 102.220 Sec7.1.2, if the total length is greater
48         // than 0x7F, it should be coded in two bytes and the first byte
49         // should be 0x81.
50         if (length > 0x7F) {
51             buf.write(0x81);
52         }
53         buf.write(length);
54     }
55 }
56 
57 class SelectItemResponseData extends ResponseData {
58     // members
59     private int mId;
60 
SelectItemResponseData(int id)61     public SelectItemResponseData(int id) {
62         super();
63         mId = id;
64     }
65 
66     @Override
format(ByteArrayOutputStream buf)67     public void format(ByteArrayOutputStream buf) {
68         // Item identifier object
69         int tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
70         buf.write(tag); // tag
71         buf.write(1); // length
72         buf.write(mId); // identifier of item chosen
73     }
74 }
75 
76 class GetInkeyInputResponseData extends ResponseData {
77     // members
78     private boolean mIsUcs2;
79     private boolean mIsPacked;
80     private boolean mIsYesNo;
81     private boolean mYesNoResponse;
82     public String mInData;
83 
84     // GetInKey Yes/No response characters constants.
85     protected static final byte GET_INKEY_YES = 0x01;
86     protected static final byte GET_INKEY_NO = 0x00;
87 
GetInkeyInputResponseData(String inData, boolean ucs2, boolean packed)88     public GetInkeyInputResponseData(String inData, boolean ucs2, boolean packed) {
89         super();
90         mIsUcs2 = ucs2;
91         mIsPacked = packed;
92         mInData = inData;
93         mIsYesNo = false;
94     }
95 
GetInkeyInputResponseData(boolean yesNoResponse)96     public GetInkeyInputResponseData(boolean yesNoResponse) {
97         super();
98         mIsUcs2 = false;
99         mIsPacked = false;
100         mInData = "";
101         mIsYesNo = true;
102         mYesNoResponse = yesNoResponse;
103     }
104 
105     @Override
format(ByteArrayOutputStream buf)106     public void format(ByteArrayOutputStream buf) {
107         if (buf == null) {
108             return;
109         }
110 
111         // Text string object
112         int tag = 0x80 | ComprehensionTlvTag.TEXT_STRING.value();
113         buf.write(tag); // tag
114 
115         byte[] data;
116 
117         if (mIsYesNo) {
118             data = new byte[1];
119             data[0] = mYesNoResponse ? GET_INKEY_YES : GET_INKEY_NO;
120         } else if (mInData != null && mInData.length() > 0) {
121             try {
122                 // ETSI TS 102 223 8.15, should use the same format as in SMS messages
123                 // on the network.
124                 if (mIsUcs2) {
125                     // ucs2 is by definition big endian.
126                     data = mInData.getBytes("UTF-16BE");
127                 } else if (mIsPacked) {
128                     byte[] tempData = GsmAlphabet
129                             .stringToGsm7BitPacked(mInData, 0, 0);
130                     // The size of the new buffer will be smaller than the original buffer
131                     // since 7-bit GSM packed only requires ((mInData.length * 7) + 7) / 8 bytes.
132                     // And we don't need to copy/store the first byte from the returned array
133                     // because it is used to store the count of septets used.
134                     data = new byte[tempData.length - 1];
135                     System.arraycopy(tempData, 1, data, 0, tempData.length - 1);
136                 } else {
137                     data = GsmAlphabet.stringToGsm8BitPacked(mInData);
138                 }
139             } catch (UnsupportedEncodingException e) {
140                 data = new byte[0];
141             } catch (EncodeException e) {
142                 data = new byte[0];
143             }
144         } else {
145             data = new byte[0];
146         }
147 
148         // length - one more for data coding scheme.
149 
150         // ETSI TS 102 223 Annex C (normative): Structure of CAT communications
151         // Any length within the APDU limits (up to 255 bytes) can thus be encoded on two bytes.
152         // This coding is chosen to remain compatible with TS 101.220.
153         // Note that we need to reserve one more byte for coding scheme thus the maximum APDU
154         // size would be 254 bytes.
155         if (data.length + 1 <= 255) {
156             writeLength(buf, data.length + 1);
157         }
158         else {
159             data = new byte[0];
160         }
161 
162 
163         // data coding scheme
164         if (mIsUcs2) {
165             buf.write(0x08); // UCS2
166         } else if (mIsPacked) {
167             buf.write(0x00); // 7 bit packed
168         } else {
169             buf.write(0x04); // 8 bit unpacked
170         }
171 
172         for (byte b : data) {
173             buf.write(b);
174         }
175     }
176 }
177 
178 // For "PROVIDE LOCAL INFORMATION" command.
179 // See TS 31.111 section 6.4.15/ETSI TS 102 223
180 // TS 31.124 section 27.22.4.15 for test spec
181 class LanguageResponseData extends ResponseData {
182     private String mLang;
183 
LanguageResponseData(String lang)184     public LanguageResponseData(String lang) {
185         super();
186         mLang = lang;
187     }
188 
189     @Override
format(ByteArrayOutputStream buf)190     public void format(ByteArrayOutputStream buf) {
191         if (buf == null) {
192             return;
193         }
194 
195         // Text string object
196         int tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value();
197         buf.write(tag); // tag
198 
199         byte[] data;
200 
201         if (mLang != null && mLang.length() > 0) {
202             data = GsmAlphabet.stringToGsm8BitPacked(mLang);
203         }
204         else {
205             data = new byte[0];
206         }
207 
208         buf.write(data.length);
209 
210         for (byte b : data) {
211             buf.write(b);
212         }
213     }
214 }
215 
216 // For "PROVIDE LOCAL INFORMATION" command.
217 // See TS 31.111 section 6.4.15/ETSI TS 102 223
218 // TS 31.124 section 27.22.4.15 for test spec
219 class DTTZResponseData extends ResponseData {
220     private Calendar mCalendar;
221 
DTTZResponseData(Calendar cal)222     public DTTZResponseData(Calendar cal) {
223         super();
224         mCalendar = cal;
225     }
226 
227     @Override
format(ByteArrayOutputStream buf)228     public void format(ByteArrayOutputStream buf) {
229         if (buf == null) {
230             return;
231         }
232 
233         // DTTZ object
234         int tag = 0x80 | CommandType.PROVIDE_LOCAL_INFORMATION.value();
235         buf.write(tag); // tag
236 
237         byte[] data = new byte[8];
238 
239         data[0] = 0x07; // Write length of DTTZ data
240 
241         if (mCalendar == null) {
242             mCalendar = Calendar.getInstance();
243         }
244         // Fill year byte
245         data[1] = byteToBCD(mCalendar.get(java.util.Calendar.YEAR) % 100);
246 
247         // Fill month byte
248         data[2] = byteToBCD(mCalendar.get(java.util.Calendar.MONTH) + 1);
249 
250         // Fill day byte
251         data[3] = byteToBCD(mCalendar.get(java.util.Calendar.DATE));
252 
253         // Fill hour byte
254         data[4] = byteToBCD(mCalendar.get(java.util.Calendar.HOUR_OF_DAY));
255 
256         // Fill minute byte
257         data[5] = byteToBCD(mCalendar.get(java.util.Calendar.MINUTE));
258 
259         // Fill second byte
260         data[6] = byteToBCD(mCalendar.get(java.util.Calendar.SECOND));
261 
262         String tz = SystemProperties.get("persist.sys.timezone", "");
263         if (TextUtils.isEmpty(tz)) {
264             data[7] = (byte) 0xFF;    // set FF in terminal response
265         } else {
266             TimeZone zone = TimeZone.getTimeZone(tz);
267             int zoneOffset = zone.getOffset(mCalendar.getTimeInMillis());
268             data[7] = getTZOffSetByte(zoneOffset);
269         }
270 
271         for (byte b : data) {
272             buf.write(b);
273         }
274     }
275 
byteToBCD(int value)276     private byte byteToBCD(int value) {
277         if (value < 0 && value > 99) {
278             CatLog.d(this, "Err: byteToBCD conversion Value is " + value +
279                            " Value has to be between 0 and 99");
280             return 0;
281         }
282 
283         return (byte) ((value / 10) | ((value % 10) << 4));
284     }
285 
getTZOffSetByte(long offSetVal)286     private byte getTZOffSetByte(long offSetVal) {
287         boolean isNegative = (offSetVal < 0);
288 
289         /*
290          * The 'offSetVal' is in milliseconds. Convert it to hours and compute
291          * offset While sending T.R to UICC, offset is expressed is 'quarters of
292          * hours'
293          */
294 
295          long tzOffset = offSetVal / (15 * 60 * 1000);
296          tzOffset = (isNegative ? -1 : 1) * tzOffset;
297          byte bcdVal = byteToBCD((int) tzOffset);
298          // For negative offsets, put '1' in the msb
299          return isNegative ?  (bcdVal |= 0x08) : bcdVal;
300     }
301 
302 }
303 
304