• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.nfc.cardemulation;
17 
18 import android.sysprop.NfcProperties;
19 import android.util.Log;
20 import android.util.SparseArray;
21 import android.util.proto.ProtoOutputStream;
22 
23 import com.android.nfc.NfcService;
24 import com.android.nfc.NfcStatsLog;
25 
26 import java.io.FileDescriptor;
27 import java.io.PrintWriter;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Map;
33 import java.util.Set;
34 
35 public class AidRoutingManager {
36 
37     static final String TAG = "AidRoutingManager";
38 
39     static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
40 
41     static final int ROUTE_HOST = 0x00;
42 
43     // Every routing table entry is matched exact
44     static final int AID_MATCHING_EXACT_ONLY = 0x00;
45     // Every routing table entry can be matched either exact or prefix
46     static final int AID_MATCHING_EXACT_OR_PREFIX = 0x01;
47     // Every routing table entry is matched as a prefix
48     static final int AID_MATCHING_PREFIX_ONLY = 0x02;
49     // Every routing table entry can be matched either exact or prefix or subset only
50     static final int AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX = 0x03;
51 
52     int mDefaultIsoDepRoute;
53     //Let mDefaultRoute as default aid route
54     int mDefaultRoute;
55 
56     int mMaxAidRoutingTableSize;
57 
58     final byte[] mOffHostRouteUicc;
59     final byte[] mOffHostRouteEse;
60     // Used for backward compatibility in case application doesn't specify the
61     // SE
62     final int mDefaultOffHostRoute;
63 
64     // How the NFC controller can match AIDs in the routing table;
65     // see AID_MATCHING constants
66     final int mAidMatchingSupport;
67 
68     final Object mLock = new Object();
69 
70     // mAidRoutingTable contains the current routing table. The index is the route ID.
71     // The route can include routes to a eSE/UICC.
72     SparseArray<Set<String>> mAidRoutingTable =
73             new SparseArray<Set<String>>();
74 
75     // Easy look-up what the route is for a certain AID
76     HashMap<String, Integer> mRouteForAid = new HashMap<String, Integer>();
77 
doGetDefaultRouteDestination()78     private native int doGetDefaultRouteDestination();
doGetDefaultOffHostRouteDestination()79     private native int doGetDefaultOffHostRouteDestination();
doGetOffHostUiccDestination()80     private native byte[] doGetOffHostUiccDestination();
doGetOffHostEseDestination()81     private native byte[] doGetOffHostEseDestination();
doGetAidMatchingMode()82     private native int doGetAidMatchingMode();
doGetDefaultIsoDepRouteDestination()83     private native int doGetDefaultIsoDepRouteDestination();
84 
85     final class AidEntry {
86         boolean isOnHost;
87         String offHostSE;
88         int route;
89         int aidInfo;
90         int power;
91     }
92 
AidRoutingManager()93     public AidRoutingManager() {
94         mDefaultRoute = doGetDefaultRouteDestination();
95         if (DBG)
96             Log.d(TAG, "mDefaultRoute=0x" + Integer.toHexString(mDefaultRoute));
97         mDefaultOffHostRoute = doGetDefaultOffHostRouteDestination();
98         if (DBG)
99             Log.d(TAG, "mDefaultOffHostRoute=0x" + Integer.toHexString(mDefaultOffHostRoute));
100         mOffHostRouteUicc = doGetOffHostUiccDestination();
101         if (DBG)
102             Log.d(TAG, "mOffHostRouteUicc=" + Arrays.toString(mOffHostRouteUicc));
103         mOffHostRouteEse = doGetOffHostEseDestination();
104         if (DBG)
105           Log.d(TAG, "mOffHostRouteEse=" + Arrays.toString(mOffHostRouteEse));
106         mAidMatchingSupport = doGetAidMatchingMode();
107         if (DBG) Log.d(TAG, "mAidMatchingSupport=0x" + Integer.toHexString(mAidMatchingSupport));
108 
109         mDefaultIsoDepRoute = doGetDefaultIsoDepRouteDestination();
110         if (DBG) Log.d(TAG, "mDefaultIsoDepRoute=0x" + Integer.toHexString(mDefaultIsoDepRoute));
111     }
112 
supportsAidPrefixRouting()113     public boolean supportsAidPrefixRouting() {
114         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
115                 mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
116                  mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
117     }
118 
supportsAidSubsetRouting()119     public boolean supportsAidSubsetRouting() {
120         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
121     }
122 
calculateAidRouteSize(HashMap<String, AidEntry> routeCache)123     public int calculateAidRouteSize(HashMap<String, AidEntry> routeCache) {
124         // TAG + ROUTE + LENGTH_BYTE + POWER
125         int AID_HDR_LENGTH = 0x04;
126         int routeTableSize = 0x00;
127         for(Map.Entry<String, AidEntry> aidEntry : routeCache.entrySet()) {
128             String aid = aidEntry.getKey();
129             // removing prefix length
130             if(aid.endsWith("*")) {
131                 routeTableSize += ((aid.length() - 0x01) / 0x02) + AID_HDR_LENGTH;
132             } else {
133                 routeTableSize += (aid.length() / 0x02)+ AID_HDR_LENGTH;
134             }
135         }
136         if (DBG) Log.d(TAG, "calculateAidRouteSize: " + routeTableSize);
137         return routeTableSize;
138     }
139 
clearNfcRoutingTableLocked()140     private void clearNfcRoutingTableLocked() {
141         for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet())  {
142             String aid = aidEntry.getKey();
143             if (aid.endsWith("*")) {
144                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
145                     Log.e(TAG, "Device does not support prefix AIDs but AID [" + aid
146                             + "] is registered");
147                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
148                     if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid);
149                     // Cut off '*' since controller anyway treats all AIDs as a prefix
150                     aid = aid.substring(0, aid.length() - 1);
151                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
152                     mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
153                     aid = aid.substring(0, aid.length() - 1);
154                     if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid);
155                 }
156             }  else if (aid.endsWith("#")) {
157                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
158                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
159                             + "] is registered");
160                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
161                     mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) {
162                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
163                             + "] is registered");
164                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
165                     if (DBG) Log.d(TAG, "Unrouting subset AID " + aid);
166                     aid = aid.substring(0, aid.length() - 1);
167                 }
168             } else {
169                 if (DBG) Log.d(TAG, "Unrouting exact AID " + aid);
170             }
171 
172             NfcService.getInstance().unrouteAids(aid);
173         }
174         if (NfcService.getInstance().getNciVersion() >= NfcService.getInstance().NCI_VERSION_2_0) {
175             // unRoute EmptyAid
176             NfcService.getInstance().unrouteAids("");
177         }
178     }
179 
getRouteForSecureElement(String se)180     private int getRouteForSecureElement(String se) {
181         if (se == null || se.length() <= 3) {
182             return 0;
183         }
184         try {
185             if (se.startsWith("eSE") && mOffHostRouteEse != null) {
186                 int index = Integer.parseInt(se.substring(3));
187                 if (mOffHostRouteEse.length >= index && index > 0) {
188                     return mOffHostRouteEse[index - 1] & 0xFF;
189                 }
190             } else if (se.startsWith("SIM") && mOffHostRouteUicc != null) {
191                 int index = Integer.parseInt(se.substring(3));
192                 if (mOffHostRouteUicc.length >= index && index > 0) {
193                     return mOffHostRouteUicc[index - 1] & 0xFF;
194                 }
195             }
196             if (mOffHostRouteEse == null && mOffHostRouteUicc == null)
197               return mDefaultOffHostRoute;
198         } catch (NumberFormatException e) { }
199         return 0;
200     }
201 
configureRouting(HashMap<String, AidEntry> aidMap, boolean force)202     public boolean configureRouting(HashMap<String, AidEntry> aidMap, boolean force) {
203         boolean aidRouteResolved = false;
204         HashMap<String, AidEntry> aidRoutingTableCache = new HashMap<String, AidEntry>(aidMap.size());
205         ArrayList<Integer> seList = new ArrayList<Integer>();
206         mDefaultRoute = doGetDefaultRouteDestination();
207         seList.add(mDefaultRoute);
208         if (mDefaultRoute != ROUTE_HOST) {
209             seList.add(ROUTE_HOST);
210         }
211 
212         SparseArray<Set<String>> aidRoutingTable = new SparseArray<Set<String>>(aidMap.size());
213         HashMap<String, Integer> routeForAid = new HashMap<String, Integer>(aidMap.size());
214         HashMap<String, Integer> infoForAid = new HashMap<String, Integer>(aidMap.size());
215         // Then, populate internal data structures first
216         for (Map.Entry<String, AidEntry> aidEntry : aidMap.entrySet())  {
217             int route = ROUTE_HOST;
218             if (!aidEntry.getValue().isOnHost) {
219                 String offHostSE = aidEntry.getValue().offHostSE;
220                 if (offHostSE == null) {
221                     route = mDefaultOffHostRoute;
222                 } else {
223                     route = getRouteForSecureElement(offHostSE);
224                     if (route == 0) {
225                         Log.e(TAG, "Invalid Off host Aid Entry " + offHostSE);
226                         continue;
227                     }
228                 }
229             }
230             if (!seList.contains(route))
231                 seList.add(route);
232             aidEntry.getValue().route = route;
233             int aidType = aidEntry.getValue().aidInfo;
234             String aid = aidEntry.getKey();
235             Set<String> entries =
236                     aidRoutingTable.get(route, new HashSet<String>());
237             entries.add(aid);
238             aidRoutingTable.put(route, entries);
239             routeForAid.put(aid, route);
240             infoForAid.put(aid, aidType);
241         }
242 
243         synchronized (mLock) {
244             if (routeForAid.equals(mRouteForAid) && !force) {
245                 if (DBG) Log.d(TAG, "Routing table unchanged, not updating");
246                 return false;
247             }
248 
249             // Otherwise, update internal structures and commit new routing
250             clearNfcRoutingTableLocked();
251             mRouteForAid = routeForAid;
252             mAidRoutingTable = aidRoutingTable;
253 
254             mMaxAidRoutingTableSize = NfcService.getInstance().getAidRoutingTableSize();
255             if (DBG) Log.d(TAG, "mMaxAidRoutingTableSize: " + mMaxAidRoutingTableSize);
256 
257             //calculate AidRoutingTableSize for existing route destination
258             for(int index = 0; index < seList.size(); index ++) {
259                 mDefaultRoute = seList.get(index);
260                 if(index != 0) {
261                     if (DBG) {
262                         Log.d(TAG, "AidRoutingTable is full, try to switch mDefaultRoute to 0x" + Integer.toHexString(mDefaultRoute));
263                     }
264                 }
265 
266                 aidRoutingTableCache.clear();
267 
268                 if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
269                     /* If a non-default route registers an exact AID which is shorter
270                      * than this exact AID, this will create a problem with controllers
271                      * that treat every AID in the routing table as a prefix.
272                      * For example, if App A registers F0000000041010 as an exact AID,
273                      * and App B registers F000000004 as an exact AID, and App B is not
274                      * the default route, the following would be added to the routing table:
275                      * F000000004 -> non-default destination
276                      * However, because in this mode, the controller treats every routing table
277                      * entry as a prefix, it means F0000000041010 would suddenly go to the non-default
278                      * destination too, whereas it should have gone to the default.
279                      *
280                      * The only way to prevent this is to add the longer AIDs of the
281                      * default route at the top of the table, so they will be matched first.
282                      */
283                     Set<String> defaultRouteAids = mAidRoutingTable.get(mDefaultRoute);
284                     if (defaultRouteAids != null) {
285                         for (String defaultRouteAid : defaultRouteAids) {
286                             // Check whether there are any shorted AIDs routed to non-default
287                             // TODO this is O(N^2) run-time complexity...
288                             for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet()) {
289                                 String aid = aidEntry.getKey();
290                                 int route = aidEntry.getValue();
291                                 if (defaultRouteAid.startsWith(aid) && route != mDefaultRoute) {
292                                     if (DBG) Log.d(TAG, "Adding AID " + defaultRouteAid + " for default " +
293                                             "route, because a conflicting shorter AID will be " +
294                                             "added to the routing table");
295                                     aidRoutingTableCache.put(defaultRouteAid, aidMap.get(defaultRouteAid));
296                                 }
297                             }
298                         }
299                     }
300                 }
301 
302                 // Add AID entries for all non-default routes
303                 for (int i = 0; i < mAidRoutingTable.size(); i++) {
304                     int route = mAidRoutingTable.keyAt(i);
305                     if (route != mDefaultRoute) {
306                         Set<String> aidsForRoute = mAidRoutingTable.get(route);
307                         for (String aid : aidsForRoute) {
308                             if (aid.endsWith("*")) {
309                                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
310                                     Log.e(TAG, "This device does not support prefix AIDs.");
311                                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
312                                     if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route "
313                                             + Integer.toString(route));
314                                     // Cut off '*' since controller anyway treats all AIDs as a prefix
315                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
316                                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
317                                   mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
318                                     if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route "
319                                             + Integer.toString(route));
320                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
321                                 }
322                             } else if (aid.endsWith("#")) {
323                                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
324                                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
325                                             + "] is registered");
326                                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
327                                     mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) {
328                                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
329                                             + "] is registered");
330                                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
331                                     if (DBG) Log.d(TAG, "Routing subset AID " + aid + " to route "
332                                             + Integer.toString(route));
333                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
334                                 }
335                             } else {
336                                 if (DBG) Log.d(TAG, "Routing exact AID " + aid + " to route "
337                                         + Integer.toString(route));
338                                 aidRoutingTableCache.put(aid, aidMap.get(aid));
339                             }
340                         }
341                     }
342                 }
343 
344                 // register default route in below cases:
345                 // 1. mDefaultRoute is different with mDefaultIsoDepRoute
346                 // 2. mDefaultRoute and mDefaultIsoDepRoute all equal to ROUTE_HOST
347                 //    , which is used for screen off HCE scenarios
348                 if (mDefaultRoute != mDefaultIsoDepRoute || mDefaultIsoDepRoute == ROUTE_HOST) {
349                     if (NfcService.getInstance().getNciVersion()
350                             >= NfcService.getInstance().NCI_VERSION_2_0) {
351                         String emptyAid = "";
352                         AidEntry entry = new AidEntry();
353                         int default_route_power_state;
354                         entry.route = mDefaultRoute;
355                         if (mDefaultRoute == ROUTE_HOST) {
356                             entry.isOnHost = true;
357                             default_route_power_state = RegisteredAidCache.POWER_STATE_SWITCH_ON
358                                     | RegisteredAidCache.POWER_STATE_SCREEN_ON_LOCKED;
359                             Set<String> aidsForDefaultRoute = mAidRoutingTable.get(mDefaultRoute);
360                             if (aidsForDefaultRoute != null) {
361                                 for (String aid : aidsForDefaultRoute) {
362                                     default_route_power_state |= aidMap.get(aid).power;
363                                 }
364                             }
365                         } else {
366                             entry.isOnHost = false;
367                             default_route_power_state = RegisteredAidCache.POWER_STATE_ALL;
368                         }
369                         entry.aidInfo = RegisteredAidCache.AID_ROUTE_QUAL_PREFIX;
370                         entry.power = default_route_power_state;
371 
372                         aidRoutingTableCache.put(emptyAid, entry);
373                         if (DBG) Log.d(TAG, "Add emptyAid into AidRoutingTable");
374                     }
375                 }
376 
377                 // Register additional offhost AIDs when their support power states are
378                 // differernt from the default route entry
379                 if (mDefaultRoute != ROUTE_HOST) {
380                     int default_route_power_state = RegisteredAidCache.POWER_STATE_ALL;
381                     if (NfcService.getInstance().getNciVersion()
382                             < NfcService.getInstance().NCI_VERSION_2_0) {
383                         default_route_power_state =
384                                 RegisteredAidCache.POWER_STATE_ALL_NCI_VERSION_1_0;
385                     }
386 
387                     Set<String> aidsForDefaultRoute = mAidRoutingTable.get(mDefaultRoute);
388                     if (aidsForDefaultRoute != null) {
389                         for (String aid : aidsForDefaultRoute) {
390                             if (aidMap.get(aid).power != default_route_power_state) {
391                                 aidRoutingTableCache.put(aid, aidMap.get(aid));
392                             }
393                         }
394                     }
395                 }
396 
397                 if (calculateAidRouteSize(aidRoutingTableCache) <= mMaxAidRoutingTableSize) {
398                     aidRouteResolved = true;
399                     break;
400                 }
401             }
402 
403             if(aidRouteResolved == true) {
404                 commit(aidRoutingTableCache);
405             } else {
406                 NfcStatsLog.write(NfcStatsLog.NFC_ERROR_OCCURRED,
407                         NfcStatsLog.NFC_ERROR_OCCURRED__TYPE__AID_OVERFLOW, 0, 0);
408                 Log.e(TAG, "RoutingTable unchanged because it's full, not updating");
409             }
410         }
411         return true;
412     }
413 
commit(HashMap<String, AidEntry> routeCache )414     private void commit(HashMap<String, AidEntry> routeCache ) {
415 
416         if(routeCache != null) {
417 
418             for (Map.Entry<String, AidEntry> aidEntry : routeCache.entrySet())  {
419                 int route = aidEntry.getValue().route;
420                 int aidType = aidEntry.getValue().aidInfo;
421                 String aid = aidEntry.getKey();
422                 int power = aidEntry.getValue().power;
423                 if (DBG) {
424                     Log.d(TAG, "commit aid:" + aid + ",route:" + route
425                         + ",aidtype:" + aidType + ", power state:" + power);
426                 }
427 
428                 NfcService.getInstance().routeAids(aid, route, aidType, power);
429             }
430         }
431 
432         // And finally commit the routing
433         NfcService.getInstance().commitRouting();
434     }
435 
436     /**
437      * This notifies that the AID routing table in the controller
438      * has been cleared (usually due to NFC being turned off).
439      */
onNfccRoutingTableCleared()440     public void onNfccRoutingTableCleared() {
441         // The routing table in the controller was cleared
442         // To stay in sync, clear our own tables.
443         synchronized (mLock) {
444             mAidRoutingTable.clear();
445             mRouteForAid.clear();
446         }
447     }
448 
dump(FileDescriptor fd, PrintWriter pw, String[] args)449     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
450         pw.println("Routing table:");
451         pw.println("    Default route: " + ((mDefaultRoute == 0x00) ? "host" : "secure element"));
452         synchronized (mLock) {
453             for (int i = 0; i < mAidRoutingTable.size(); i++) {
454                 Set<String> aids = mAidRoutingTable.valueAt(i);
455                 pw.println("    Routed to 0x" + Integer.toHexString(mAidRoutingTable.keyAt(i)) + ":");
456                 for (String aid : aids) {
457                     pw.println("        \"" + aid + "\"");
458                 }
459             }
460         }
461     }
462 
463     /**
464      * Dump debugging information as a AidRoutingManagerProto
465      *
466      * Note:
467      * See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto
468      * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
469      * {@link ProtoOutputStream#end(long)} after.
470      * Never reuse a proto field number. When removing a field, mark it as reserved.
471      */
dumpDebug(ProtoOutputStream proto)472     void dumpDebug(ProtoOutputStream proto) {
473         proto.write(AidRoutingManagerProto.DEFAULT_ROUTE, mDefaultRoute);
474         synchronized (mLock) {
475             for (int i = 0; i < mAidRoutingTable.size(); i++) {
476                 long token = proto.start(AidRoutingManagerProto.ROUTES);
477                 proto.write(AidRoutingManagerProto.Route.ID, mAidRoutingTable.keyAt(i));
478                 mAidRoutingTable.valueAt(i).forEach(aid -> {
479                     proto.write(AidRoutingManagerProto.Route.AIDS, aid);
480                 });
481                 proto.end(token);
482             }
483         }
484     }
485 }
486