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