1 /*
2  * Copyright (C) 2021 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 package com.android.server.uwb.secure.iso7816;
17 
18 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_APPLET_SELECT_FAILED;
19 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_CLA_NOT_SUPPORTED;
20 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_CONDITIONS_NOT_SATISFIED;
21 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_FILE_NOT_FOUND;
22 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_INCORRECT_P1P2;
23 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_INS_NOT_SUPPORTED;
24 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_NO_ERROR;
25 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_UNKNOWN_ERROR;
26 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_WRONG_DATA;
27 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_WRONG_LE;
28 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_WRONG_LENGTH;
29 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_WRONG_P1P2;
30 
31 import androidx.annotation.Nullable;
32 import androidx.annotation.VisibleForTesting;
33 
34 import com.android.server.uwb.util.Hex;
35 
36 import com.google.common.base.Objects;
37 import com.google.common.base.Preconditions;
38 import com.google.common.primitives.Bytes;
39 import com.google.common.primitives.Shorts;
40 
41 import java.nio.ByteBuffer;
42 import java.util.Arrays;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.concurrent.TimeUnit;
46 
47 
48 /** A class that represents the data contained in an ISO/IEC 7816-4 Response APDU. */
49 public class ResponseApdu {
50 
51     public static final ResponseApdu SW_CONDITIONS_NOT_SATISFIED_APDU =
52             ResponseApdu.fromStatusWord(SW_CONDITIONS_NOT_SATISFIED);
53 
54     public static final ResponseApdu SW_INCORRECT_P1P2_APDU =
55             ResponseApdu.fromStatusWord(SW_INCORRECT_P1P2);
56 
57     public static final ResponseApdu SW_FILE_NOT_FOUND_APDU =
58             ResponseApdu.fromStatusWord(SW_FILE_NOT_FOUND);
59 
60     public static final ResponseApdu SW_WRONG_P1P2_APDU =
61             ResponseApdu.fromStatusWord(SW_WRONG_P1P2);
62 
63     public static final ResponseApdu SW_WRONG_LE_APDU =
64             ResponseApdu.fromStatusWord(SW_WRONG_LE);
65 
66     public static final ResponseApdu SW_WRONG_DATA_APDU =
67             ResponseApdu.fromStatusWord(SW_WRONG_DATA);
68 
69     public static final ResponseApdu SW_WRONG_LENGTH_APDU =
70             ResponseApdu.fromStatusWord(SW_WRONG_LENGTH);
71 
72     public static final ResponseApdu SW_CLA_NOT_SUPPORTED_APDU =
73             ResponseApdu.fromStatusWord(SW_CLA_NOT_SUPPORTED);
74 
75     public static final ResponseApdu SW_INS_NOT_SUPPORTED_APDU =
76             ResponseApdu.fromStatusWord(SW_INS_NOT_SUPPORTED);
77 
78     public static final ResponseApdu SW_WRONG_FILE_APDU =
79             ResponseApdu.fromStatusWord(SW_FILE_NOT_FOUND);
80 
81     public static final ResponseApdu SW_UNKNOWN_APDU = ResponseApdu.fromStatusWord(
82             SW_UNKNOWN_ERROR);
83 
84     public static final ResponseApdu SW_SUCCESS_APDU = ResponseApdu.fromStatusWord(SW_NO_ERROR);
85 
86     public static final ResponseApdu SW_APPLET_SELECT_FAILED_APDU =
87             ResponseApdu.fromStatusWord(SW_APPLET_SELECT_FAILED);
88 
89     private static final long NO_TIME_RECORDED = -1L;
90 
91     private static final int SIZE_OF_SW = 2;
92 
93     private static final int MASK_OF_SW = 0xffff;
94 
95     private final byte[] mRdata;
96 
97     private final int mSw;
98 
99     private final long mCmdTimeMillis;
100 
101     @VisibleForTesting
ResponseApdu(byte[] rdata, int sw, long cmdTimeMillis)102     ResponseApdu(byte[] rdata, int sw, long cmdTimeMillis) {
103         this.mRdata = rdata;
104         this.mSw = sw;
105         this.mCmdTimeMillis = cmdTimeMillis;
106     }
107 
108     /**
109      * Parses a raw APDU response to set the response data and status word. A response consists of
110      * at
111      * least a two byte status word and any number of data bytes. A standard length APDU supports
112      * 256
113      * bytes of data and 2 bytes of status word while and extended length APDU supports 32KB of
114      * response data. A minimum response is simply a status word.
115      *
116      * @param response The raw response from the card to parse.
117      * @throws IllegalArgumentException if the response is less than 2 bytes long.
118      */
fromResponse(byte[] response)119     public static ResponseApdu fromResponse(byte[] response) {
120         return fromResponse(response, NO_TIME_RECORDED, TimeUnit.MILLISECONDS);
121     }
122 
123   /**
124    * Generate the ResponseApdu from the byte array(data) and status word.
125    */
fromDataAndStatusWord(byte[] data, int sw)126     public static ResponseApdu fromDataAndStatusWord(byte[] data, int sw) {
127         Preconditions.checkArgument((sw >> Short.SIZE) == 0);
128         return fromResponse(
129                 Bytes.concat(data == null ? new byte[]{} : data, Shorts.toByteArray((short) sw)));
130     }
131 
132   /**
133    * Generate the ResponseApdu form the list of TlvDatum and status word.
134    */
fromDataAndStatusWord(List<TlvDatum> data, int sw)135     public static ResponseApdu fromDataAndStatusWord(List<TlvDatum> data, int sw) {
136         byte[] dataBytes = new byte[]{};
137         for (TlvDatum tlvDatum : data) {
138             dataBytes = Bytes.concat(dataBytes, tlvDatum.toBytes());
139         }
140         return fromDataAndStatusWord(dataBytes, sw);
141     }
142 
143   /**
144    * Generate the ResponseApdu form the status word.
145    */
fromStatusWord(StatusWord sw)146     public static ResponseApdu fromStatusWord(StatusWord sw) {
147         return fromResponse(sw.toBytes());
148     }
149 
150     /**
151      * Parses a raw APDU response to set the response data and status word. A response consists of
152      * at
153      * least a two byte status word and any number of data bytes. A standard length APDU supports
154      * 256
155      * bytes of data and 2 bytes of status word while and extended length APDU supports 32KB of
156      * response data. A minimum response is simply a status word.
157      *
158      * @param response The raw response from the card to parse.
159      * @param time     the time for the command to execute.
160      * @param timeUnit the {@link TimeUnit} of the execution time.
161      * @throws IllegalArgumentException if the response is less than 2 bytes long.
162      */
fromResponse(byte[] response, long time, TimeUnit timeUnit)163     public static ResponseApdu fromResponse(byte[] response, long time, TimeUnit timeUnit) {
164         Preconditions.checkNotNull(response);
165         int len = response.length;
166         long cmdTimeMillis = timeUnit.toMillis(time);
167 
168         // A response must at least have a status word (2 bytes).
169         Preconditions.checkArgument(
170                 len >= SIZE_OF_SW,
171                 "Invalid response APDU after %sms. Must be at least 2 bytes long: [%s]",
172                 cmdTimeMillis,
173                 Hex.encode(response));
174 
175         ByteBuffer buffer = ByteBuffer.wrap(response);
176 
177         // Extract and store any response data.
178         int rdataLen = len - SIZE_OF_SW;
179         byte[] rdata = new byte[rdataLen];
180         buffer.get(rdata, 0, rdataLen);
181 
182         // Extract and set the status word.
183         int sw = buffer.getShort() & MASK_OF_SW;
184 
185         return new ResponseApdu(rdata, sw, cmdTimeMillis);
186     }
187 
188     /**
189      * Returns a copy of the response data for the APDU. Updates to this copy will not affect the
190      * internal copy in this instance.
191      */
getResponseData()192     public byte[] getResponseData() {
193         return mRdata.clone();
194     }
195 
196     /**
197      * Gets the status word.
198      */
getStatusWord()199     public int getStatusWord() {
200         return mSw;
201     }
202 
203     /**
204      * Convert the ResponseApdu to the byte array.
205      */
toByteArray()206     public byte[] toByteArray() {
207         return Bytes.concat(mRdata, Shorts.toByteArray((short) mSw));
208     }
209 
210     @Override
toString()211     public String toString() {
212         StringBuilder sb = new StringBuilder("Response: ");
213 
214         if (mRdata != null && mRdata.length > 0) {
215             sb.append(Hex.encode(mRdata)).append(", ");
216         }
217 
218         sb.append(String.format("SW=%04x", mSw));
219 
220         if (mCmdTimeMillis > NO_TIME_RECORDED) {
221             sb.append(String.format(Locale.US, ", elapsed: %dms", mCmdTimeMillis));
222         }
223 
224         return sb.toString();
225     }
226 
227     /**
228      * {@inheritDoc}
229      *
230      * <p>This ignores the time the APDU took to complete and only compares the response data and
231      * status word.
232      */
233     @Override
equals(@ullable Object obj)234     public boolean equals(@Nullable Object obj) {
235         if (obj == null) {
236             return false;
237         }
238 
239         if (this.getClass() == obj.getClass()) {
240             ResponseApdu other = (ResponseApdu) obj;
241             return Arrays.equals(this.mRdata, other.mRdata) && this.mSw == other.mSw;
242         }
243         return false;
244     }
245 
246     @Override
hashCode()247     public int hashCode() {
248         return Objects.hashCode(Arrays.hashCode(mRdata), mSw);
249     }
250 }
251