• 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.annotation.IntDef;
19 import android.nfc.NfcOemExtension;
20 import android.sysprop.NfcProperties;
21 import android.util.Log;
22 import android.util.SparseArray;
23 import android.util.proto.ProtoOutputStream;
24 
25 import androidx.annotation.VisibleForTesting;
26 
27 import com.android.nfc.NfcService;
28 import com.android.nfc.NfcStatsLog;
29 
30 import java.io.FileDescriptor;
31 import java.io.PrintWriter;
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Objects;
42 import java.util.Set;
43 
44 public class AidRoutingManager {
45 
46     static final String TAG = "AidRoutingManager";
47 
48     static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
49 
50     static final int ROUTE_HOST = 0x00;
51 
52     // Every routing table entry is matched exact
53     static final int AID_MATCHING_EXACT_ONLY = 0x00;
54     // Every routing table entry can be matched either exact or prefix
55     static final int AID_MATCHING_EXACT_OR_PREFIX = 0x01;
56     // Every routing table entry is matched as a prefix
57     static final int AID_MATCHING_PREFIX_ONLY = 0x02;
58     // Every routing table entry can be matched either exact or prefix or subset only
59     static final int AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX = 0x03;
60 
61     int mDefaultIsoDepRoute;
62     //Let mDefaultRoute as default aid route
63     int mDefaultRoute;
64     int mPowerEmptyAid = 0x00;
65 
66     int mMaxAidRoutingTableSize;
67 
68     final byte[] mOffHostRouteUicc;
69     final byte[] mOffHostRouteEse;
70     // Used for backward compatibility in case application doesn't specify the
71     // SE
72     int mDefaultOffHostRoute;
73 
74     int mDefaultFelicaRoute;
75 
76     // How the NFC controller can match AIDs in the routing table;
77     // see AID_MATCHING constants
78     final int mAidMatchingSupport;
79 
80     final Object mLock = new Object();
81 
82     // mAidRoutingTable contains the current routing table. The index is the route ID.
83     // The route can include routes to a eSE/UICC.
84     SparseArray<Set<String>> mAidRoutingTable =
85             new SparseArray<Set<String>>();
86 
87     // Easy look-up what the route is for a certain AID
88     HashMap<String, Integer> mRouteForAid = new HashMap<String, Integer>();
89     // Easy look-up what the power is for a certain AID
90     HashMap<String, Integer> mPowerForAid = new HashMap<String, Integer>();
91 
92     RoutingOptionManager mRoutingOptionManager = RoutingOptionManager.getInstance();
93     @VisibleForTesting
94     public final class AidEntry {
95         boolean isOnHost;
96         String offHostSE;
97         int route;
98         int aidInfo;
99         int power;
100         List<String> unCheckedOffHostSE = new ArrayList<>();
101     }
102 
AidRoutingManager()103     public AidRoutingManager() {
104         mDefaultRoute = mRoutingOptionManager.getDefaultRoute();
105         if (DBG) Log.d(TAG, "mDefaultRoute=0x" + Integer.toHexString(mDefaultRoute));
106         mDefaultOffHostRoute = mRoutingOptionManager.getDefaultOffHostRoute();
107         if (DBG) Log.d(TAG, "mDefaultOffHostRoute=0x" + Integer.toHexString(mDefaultOffHostRoute));
108         mDefaultFelicaRoute = mRoutingOptionManager.getDefaultFelicaRoute();
109         if (DBG) Log.d(TAG, "mDefaultFelicaRoute=0x" + Integer.toHexString(mDefaultFelicaRoute));
110         mOffHostRouteUicc = mRoutingOptionManager.getOffHostRouteUicc();
111         if (DBG) Log.d(TAG, "mOffHostRouteUicc=" + Arrays.toString(mOffHostRouteUicc));
112         mOffHostRouteEse = mRoutingOptionManager.getOffHostRouteEse();
113         if (DBG) Log.d(TAG, "mOffHostRouteEse=" + Arrays.toString(mOffHostRouteEse));
114         mAidMatchingSupport = mRoutingOptionManager.getAidMatchingSupport();
115         if (DBG) Log.d(TAG, "mAidMatchingSupport=0x" + Integer.toHexString(mAidMatchingSupport));
116         mDefaultIsoDepRoute = mRoutingOptionManager.getDefaultIsoDepRoute();
117         if (DBG) Log.d(TAG, "mDefaultIsoDepRoute=0x" + Integer.toHexString(mDefaultIsoDepRoute));
118     }
119 
supportsAidPrefixRouting()120     public boolean supportsAidPrefixRouting() {
121         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX
122                 || mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY
123                 || mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
124     }
125 
supportsAidSubsetRouting()126     public boolean supportsAidSubsetRouting() {
127         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
128     }
129 
calculateAidRouteSize(HashMap<String, AidEntry> routeCache)130     public int calculateAidRouteSize(HashMap<String, AidEntry> routeCache) {
131         // TAG + ROUTE + LENGTH_BYTE + POWER
132         int AID_HDR_LENGTH = 0x04;
133         int routeTableSize = 0x00;
134         for (Map.Entry<String, AidEntry> aidEntry : routeCache.entrySet()) {
135             String aid = aidEntry.getKey();
136             // removing prefix length
137             if (aid.endsWith("*")) {
138                 routeTableSize += ((aid.length() - 0x01) / 0x02) + AID_HDR_LENGTH;
139             } else {
140                 routeTableSize += (aid.length() / 0x02)+ AID_HDR_LENGTH;
141             }
142         }
143         if (DBG) Log.d(TAG, "calculateAidRouteSize: " + routeTableSize);
144         return routeTableSize;
145     }
146 
clearNfcRoutingTableLocked()147     private void clearNfcRoutingTableLocked() {
148         if (DBG) Log.d(TAG, "clearNfcRoutingTableLocked");
149         NfcService.getInstance().clearRoutingTable(0x01);
150     }
151 
152     //Checking in case of power/route update of any AID after conflict
153     //resolution, is routing required or not?
isAidEntryUpdated(HashMap<String, Integer> currRouteForAid, Map.Entry<String, Integer> aidEntry, HashMap<String, Integer> prevPowerForAid)154     private boolean isAidEntryUpdated(HashMap<String, Integer> currRouteForAid,
155                                                 Map.Entry<String, Integer> aidEntry,
156                                                 HashMap<String, Integer> prevPowerForAid) {
157         if (!Objects.equals(currRouteForAid.get(aidEntry.getKey()), aidEntry.getValue())
158                 || !Objects.equals(
159                 mPowerForAid.get(aidEntry.getKey()),
160                 prevPowerForAid.get(aidEntry.getKey()))) {
161             return true;
162         }
163         return false;
164     }
165 
166     //Check if Any AID entry needs to be removed from previously registered
167     //entries in the Routing table. Current AID entries & power state are part of
168     //mRouteForAid & mPowerForAid respectively. previously registered AID entries &
169     //power states are part of input argument prevRouteForAid & prevPowerForAid respectively.
checkUnrouteAid(HashMap<String, Integer> prevRouteForAid, HashMap<String, Integer> prevPowerForAid)170     private boolean checkUnrouteAid(HashMap<String, Integer> prevRouteForAid,
171                                      HashMap<String, Integer> prevPowerForAid) {
172         for (Map.Entry<String, Integer> aidEntry : prevRouteForAid.entrySet())  {
173             if ((aidEntry.getValue() != mDefaultRoute)
174                     && (!mRouteForAid.containsKey(aidEntry.getKey())
175                     || isAidEntryUpdated(mRouteForAid, aidEntry, prevPowerForAid))) {
176                 return true;
177             }
178         }
179         return false;
180     }
181 
182     //Check if Any AID entry needs to be added to previously registered
183     //entries in the Routing table. Current AID entries & power state are part of
184     //mRouteForAid & mPowerForAid respectively. previously registered AID entries &
185     //power states are part of input argument prevRouteForAid & prevPowerForAid respectively.
checkRouteAid(HashMap<String, Integer> prevRouteForAid, HashMap<String, Integer> prevPowerForAid)186     private boolean checkRouteAid(HashMap<String, Integer> prevRouteForAid,
187                                    HashMap<String, Integer> prevPowerForAid) {
188         for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet())  {
189             if ((aidEntry.getValue() != mDefaultRoute)
190                     && (!prevRouteForAid.containsKey(aidEntry.getKey())
191                     || isAidEntryUpdated(prevRouteForAid, aidEntry, prevPowerForAid))) {
192                 return true;
193             }
194         }
195         return false;
196     }
197 
checkRoutingOptionChanged(int prevDefaultRoute, int prevDefaultIsoDepRoute, int prevDefaultOffHostRoute)198     private boolean checkRoutingOptionChanged(int prevDefaultRoute, int prevDefaultIsoDepRoute,
199                                               int prevDefaultOffHostRoute) {
200         return (prevDefaultRoute != mDefaultRoute)
201                 || (prevDefaultIsoDepRoute != mDefaultIsoDepRoute)
202                 || (prevDefaultOffHostRoute != mDefaultOffHostRoute);
203     }
204 
checkOffHostRouteToHost(HashMap<String, AidEntry> routeCache)205     private void checkOffHostRouteToHost(HashMap<String, AidEntry> routeCache) {
206         Iterator<Map.Entry<String, AidEntry> > it = routeCache.entrySet().iterator();
207         while (it.hasNext()) {
208             Map.Entry<String, AidEntry> entry = it.next();
209             String aid = entry.getKey();
210             AidEntry aidEntry = entry.getValue();
211 
212             if (!aidEntry.isOnHost || aidEntry.unCheckedOffHostSE.size() == 0) {
213                 continue;
214             }
215             boolean mustHostRoute = aidEntry.unCheckedOffHostSE.stream()
216                     .anyMatch(offHost ->mRoutingOptionManager.getRouteForSecureElement(offHost)
217                             == mDefaultRoute);
218             if (mustHostRoute) {
219                 if (DBG) {
220                     Log.d(TAG,
221                             "checkOffHostRouteToHost: " + aid
222                                     + " is route to host due to unchecked off host and "
223                                     + "default route(0x" + Integer.toHexString(mDefaultRoute)
224                                     + ") is same");
225                 }
226             } else {
227                 if (DBG) {
228                     Log.d(TAG, "checkOffHostRouteToHost: " + aid + " remove in host route list");
229                 }
230                 it.remove();
231             }
232         }
233     }
234 
235     public static final int CONFIGURE_ROUTING_SUCCESS = 0;
236     public static final int CONFIGURE_ROUTING_FAILURE_TABLE_FULL = 1;
237     public static final int CONFIGURE_ROUTING_FAILURE_UNKNOWN = 2;
238     @IntDef(flag = true, value = {
239         CONFIGURE_ROUTING_SUCCESS,
240         CONFIGURE_ROUTING_FAILURE_TABLE_FULL,
241         CONFIGURE_ROUTING_FAILURE_UNKNOWN,
242     })
243     @Retention(RetentionPolicy.SOURCE)
244     public @interface ConfigureRoutingResult {}
245 
246     /**
247      * Configures the routing table with the given {@code aidMap}.
248      *
249      * @param aidMap The map of AIDs to their corresponding {@link AidEntry}.
250      * @param force Whether to force the configuration even if the routing table is unchanged.
251      * @param isOverrideOrRecover Whether the configuration is requested when override/recover
252      *                            routing table.
253      * @return The failure reason if the configuration failed.
254      */
255     @ConfigureRoutingResult
configureRouting(HashMap<String, AidEntry> aidMap, boolean force, boolean isOverrideOrRecover)256     public int configureRouting(HashMap<String, AidEntry> aidMap, boolean force,
257             boolean isOverrideOrRecover) {
258         boolean aidRouteResolved = false;
259         HashMap<String, AidEntry> aidRoutingTableCache = new HashMap<String, AidEntry>(aidMap.size());
260         ArrayList<Integer> seList = new ArrayList<Integer>();
261 
262         int prevDefaultRoute = mDefaultRoute;
263         int prevDefaultIsoDepRoute = mDefaultIsoDepRoute;
264         int prevDefaultOffHostRoute = mDefaultOffHostRoute;
265 
266         if (mRoutingOptionManager.isRoutingTableOverrided()) {
267             mDefaultRoute = mRoutingOptionManager.getOverrideDefaultRoute();
268             mDefaultIsoDepRoute = mRoutingOptionManager.getOverrideDefaultIsoDepRoute();
269             mDefaultOffHostRoute = mRoutingOptionManager.getOverrideDefaultOffHostRoute();
270             mDefaultFelicaRoute = mRoutingOptionManager.getOverrideDefaultFelicaRoute();
271         } else {
272             mDefaultRoute = mRoutingOptionManager.getDefaultRoute();
273             mDefaultIsoDepRoute = mRoutingOptionManager.getDefaultIsoDepRoute();
274             mDefaultOffHostRoute = mRoutingOptionManager.getDefaultOffHostRoute();
275             mDefaultFelicaRoute = mRoutingOptionManager.getDefaultFelicaRoute();
276         }
277 
278         boolean isPowerStateUpdated = false;
279         seList.add(mDefaultRoute);
280         if (mDefaultRoute != ROUTE_HOST) {
281             seList.add(ROUTE_HOST);
282         }
283 
284         SparseArray<Set<String>> aidRoutingTable = new SparseArray<Set<String>>(aidMap.size());
285         HashMap<String, Integer> routeForAid = new HashMap<String, Integer>(aidMap.size());
286         HashMap<String, Integer> powerForAid = new HashMap<String, Integer>(aidMap.size());
287         HashMap<String, Integer> infoForAid = new HashMap<String, Integer>(aidMap.size());
288         HashMap<String, Integer> prevRouteForAid = new HashMap<String, Integer>();
289         HashMap<String, Integer> prevPowerForAid = new HashMap<String, Integer>();
290         // Then, populate internal data structures first
291         for (Map.Entry<String, AidEntry> aidEntry : aidMap.entrySet())  {
292             int route = ROUTE_HOST;
293             if (!aidEntry.getValue().isOnHost) {
294                 String offHostSE = aidEntry.getValue().offHostSE;
295                 if (offHostSE == null) {
296                     route = mDefaultOffHostRoute;
297                 } else {
298                     route = mRoutingOptionManager.getRouteForSecureElement(offHostSE);
299                     if (route == 0) {
300                         Log.e(TAG, "configureRouting: Invalid Off host Aid Entry " + offHostSE);
301                         continue;
302                     }
303                 }
304             }
305             if (!seList.contains(route))
306                 seList.add(route);
307             aidEntry.getValue().route = route;
308             int aidType = aidEntry.getValue().aidInfo;
309             int power = aidEntry.getValue().power;
310             String aid = aidEntry.getKey();
311             Set<String> entries =
312                     aidRoutingTable.get(route, new HashSet<String>());
313             entries.add(aid);
314             aidRoutingTable.put(route, entries);
315             routeForAid.put(aid, route);
316             powerForAid.put(aid, power);
317             infoForAid.put(aid, aidType);
318         }
319 
320         if (!mRoutingOptionManager.isAutoChangeEnabled() && seList.size() >= 2) {
321             Log.d(TAG, "configureRouting: AutoRouting is not enabled, make only one item in list");
322             int firstRoute = seList.get(0);
323             seList.clear();
324             seList.add(firstRoute);
325         }
326 
327         synchronized (mLock) {
328             if (routeForAid.equals(mRouteForAid) && powerForAid.equals(mPowerForAid) && !force) {
329                 if (DBG) Log.d(TAG, "configureRouting: Routing table unchanged, not updating");
330                 return CONFIGURE_ROUTING_SUCCESS;
331             }
332 
333             // Otherwise, update internal structures and commit new routing
334             prevRouteForAid = mRouteForAid;
335             mRouteForAid = routeForAid;
336             prevPowerForAid = mPowerForAid;
337             mPowerForAid = powerForAid;
338             mAidRoutingTable = aidRoutingTable;
339 
340             mMaxAidRoutingTableSize = NfcService.getInstance().getAidRoutingTableSize();
341             if (DBG) {
342                 Log.d(TAG, "configureRouting: mMaxAidRoutingTableSize: " + mMaxAidRoutingTableSize);
343             }
344 
345             //calculate AidRoutingTableSize for existing route destination
346             for (int index = 0; index < seList.size(); index++) {
347                 mDefaultRoute = seList.get(index);
348                 if (index != 0) {
349                     if (DBG) {
350                         Log.d(TAG, "configureRouting: AidRoutingTable is full, try to switch "
351                                 + "mDefaultRoute to 0x" + Integer.toHexString(mDefaultRoute));
352                     }
353                 }
354 
355                 aidRoutingTableCache.clear();
356 
357                 if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
358                     /* If a non-default route registers an exact AID which is shorter
359                      * than this exact AID, this will create a problem with controllers
360                      * that treat every AID in the routing table as a prefix.
361                      * For example, if App A registers F0000000041010 as an exact AID,
362                      * and App B registers F000000004 as an exact AID, and App B is not
363                      * the default route, the following would be added to the routing table:
364                      * F000000004 -> non-default destination
365                      * However, because in this mode, the controller treats every routing table
366                      * entry as a prefix, it means F0000000041010 would suddenly go to the non-default
367                      * destination too, whereas it should have gone to the default.
368                      *
369                      * The only way to prevent this is to add the longer AIDs of the
370                      * default route at the top of the table, so they will be matched first.
371                      */
372                     Set<String> defaultRouteAids = mAidRoutingTable.get(mDefaultRoute);
373                     if (defaultRouteAids != null) {
374                         for (String defaultRouteAid : defaultRouteAids) {
375                             // Check whether there are any shorted AIDs routed to non-default
376                             // TODO this is O(N^2) run-time complexity...
377                             for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet()) {
378                                 String aid = aidEntry.getKey();
379                                 int route = aidEntry.getValue();
380                                 if (defaultRouteAid.startsWith(aid) && route != mDefaultRoute) {
381                                     if (DBG) {
382                                         Log.d(TAG, "configureRouting: Adding AID " + defaultRouteAid
383                                                 + " for default "
384                                                 + "route, because a conflicting shorter "
385                                                 + "AID will be added to the routing table");
386                                     }
387                                     aidRoutingTableCache.put(defaultRouteAid, aidMap.get(defaultRouteAid));
388                                 }
389                             }
390                         }
391                     }
392                 }
393 
394                 // Add AID entries for all non-default routes
395                 for (int i = 0; i < mAidRoutingTable.size(); i++) {
396                     int route = mAidRoutingTable.keyAt(i);
397                     if (route != mDefaultRoute) {
398                         Set<String> aidsForRoute = mAidRoutingTable.get(route);
399                         for (String aid : aidsForRoute) {
400                             if (aid.endsWith("*")) {
401                                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
402                                     Log.e(TAG, "configureRouting: This device does not support "
403                                             + "prefix AIDs.");
404                                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
405                                     if (DBG) {
406                                         Log.d(TAG, "configureRouting: Routing prefix AID " + aid
407                                                 + " to route " + Integer.toString(route));
408                                     }
409                                     // Cut off '*' since controller anyway treats all AIDs as a prefix
410                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
411                                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
412                                   mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
413                                     if (DBG) {
414                                         Log.d(TAG, "configureRouting: Routing prefix AID " + aid
415                                                 + " to route " + Integer.toString(route));
416                                     }
417                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
418                                 }
419                             } else if (aid.endsWith("#")) {
420                                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
421                                     Log.e(TAG,
422                                             "configureRouting: Device does not support subset "
423                                                     + "AIDs but AID [" + aid + "] is registered");
424                                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
425                                     mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) {
426                                     Log.e(TAG, "configureRouting: Device does not support subset "
427                                             + "AIDs but AID [" + aid + "] is registered");
428                                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
429                                     if (DBG) {
430                                         Log.d(TAG, "configureRouting: Routing subset AID " + aid
431                                                 + " to route " + Integer.toString(route));
432                                     }
433                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
434                                 }
435                             } else {
436                                 if (DBG) {
437                                     Log.d(TAG, "configureRouting: Routing exact AID " + aid
438                                             + " to route " + Integer.toString(route));
439                                 }
440                                 aidRoutingTableCache.put(aid, aidMap.get(aid));
441                             }
442                         }
443                     }
444                 }
445 
446                 if (NfcService.getInstance().getNciVersion()
447                         >= NfcService.getInstance().NCI_VERSION_2_0) {
448                     String emptyAid = "";
449                     AidEntry entry = new AidEntry();
450                     int default_route_power_state;
451                     entry.route = mDefaultRoute;
452                     if (mDefaultRoute == ROUTE_HOST) {
453                         entry.isOnHost = true;
454                         default_route_power_state = RegisteredAidCache.POWER_STATE_SWITCH_ON
455                                 | RegisteredAidCache.POWER_STATE_SCREEN_ON_LOCKED;
456                         Set<String> aidsForDefaultRoute = mAidRoutingTable.get(mDefaultRoute);
457                         if (aidsForDefaultRoute != null) {
458                             for (String aid : aidsForDefaultRoute) {
459                                 default_route_power_state |= aidMap.get(aid).power;
460                             }
461                         }
462                     } else {
463                         entry.isOnHost = false;
464                         default_route_power_state = RegisteredAidCache.POWER_STATE_ALL;
465                     }
466                     if (mPowerEmptyAid != default_route_power_state) {
467                         isPowerStateUpdated = true;
468                     }
469                     mPowerEmptyAid = default_route_power_state;
470                     entry.aidInfo = RegisteredAidCache.AID_ROUTE_QUAL_PREFIX;
471                     entry.power = default_route_power_state;
472                     aidRoutingTableCache.put(emptyAid, entry);
473                     if (DBG) Log.d(TAG, "configureRouting: Add emptyAid into AidRoutingTable");
474                 }
475 
476                 // Register additional offhost AIDs when their support power states are
477                 // different from the default route entry
478                 if (mDefaultRoute != ROUTE_HOST) {
479                     int default_route_power_state = RegisteredAidCache.POWER_STATE_ALL;
480                     if (NfcService.getInstance().getNciVersion()
481                             < NfcService.getInstance().NCI_VERSION_2_0) {
482                         default_route_power_state =
483                                 RegisteredAidCache.POWER_STATE_ALL_NCI_VERSION_1_0;
484                     }
485 
486                     Set<String> aidsForDefaultRoute = mAidRoutingTable.get(mDefaultRoute);
487                     if (aidsForDefaultRoute != null) {
488                         for (String aid : aidsForDefaultRoute) {
489                             if (aidMap.get(aid).power != default_route_power_state) {
490                                 aidRoutingTableCache.put(aid, aidMap.get(aid));
491                                 isPowerStateUpdated = true;
492                             }
493                         }
494                     }
495                 }
496 
497                 // Unchecked Offhosts rout to host
498                 if (mDefaultRoute != ROUTE_HOST) {
499                     Log.d(TAG, "configureRouting: check offHost route to host");
500                     checkOffHostRouteToHost(aidRoutingTableCache);
501                 }
502 
503               if (calculateAidRouteSize(aidRoutingTableCache) <= mMaxAidRoutingTableSize ||
504                     mRoutingOptionManager.isRoutingTableOverrided()) {
505                   aidRouteResolved = true;
506                   break;
507               }
508           }
509 
510             boolean mIsUnrouteRequired = checkUnrouteAid(prevRouteForAid, prevPowerForAid);
511             boolean isRouteTableUpdated = checkRouteAid(prevRouteForAid, prevPowerForAid);
512             boolean isRoutingOptionUpdated = checkRoutingOptionChanged(prevDefaultRoute,
513                     prevDefaultIsoDepRoute, prevDefaultOffHostRoute);
514 
515             if (isPowerStateUpdated || isRouteTableUpdated || mIsUnrouteRequired
516                     || isRoutingOptionUpdated || force) {
517                 if (aidRouteResolved) {
518                     clearNfcRoutingTableLocked();
519                     sendRoutingTable(isRoutingOptionUpdated, force);
520                     int result = commit(aidRoutingTableCache, isOverrideOrRecover);
521                     if (result != NfcOemExtension.COMMIT_ROUTING_STATUS_OK) {
522                         NfcStatsLog.write(NfcStatsLog.NFC_ERROR_OCCURRED,
523                                 NfcStatsLog.NFC_ERROR_OCCURRED__TYPE__UNKNOWN, 0, 0);
524                         return CONFIGURE_ROUTING_FAILURE_UNKNOWN;
525                     }
526                 } else {
527                     NfcStatsLog.write(NfcStatsLog.NFC_ERROR_OCCURRED,
528                             NfcStatsLog.NFC_ERROR_OCCURRED__TYPE__AID_OVERFLOW, 0, 0);
529                     Log.e(TAG, "configureRouting: RoutingTable unchanged because it's full, "
530                             + "not updating");
531                     return CONFIGURE_ROUTING_FAILURE_TABLE_FULL;
532                 }
533             } else {
534                 Log.e(TAG, "configureRouting: All AIDs routing to mDefaultRoute, RoutingTable"
535                         + " update is not required");
536             }
537         }
538         return CONFIGURE_ROUTING_SUCCESS;
539     }
540 
commit(HashMap<String, AidEntry> routeCache, boolean isOverrideOrRecover)541     private int commit(HashMap<String, AidEntry> routeCache, boolean isOverrideOrRecover) {
542         if (routeCache != null) {
543             for (Map.Entry<String, AidEntry> aidEntry : routeCache.entrySet())  {
544                 int route = aidEntry.getValue().route;
545                 int aidType = aidEntry.getValue().aidInfo;
546                 String aid = aidEntry.getKey();
547                 int power = aidEntry.getValue().power;
548                 if (DBG)  {
549                     Log.d(TAG, "commit: aid:" + aid + ",route:" + route
550                         + ",aidtype:" + aidType + ", power state:" + power);
551                 }
552                 NfcService.getInstance().routeAids(aid, route, aidType, power);
553             }
554         }
555 
556         // And finally commit the routing
557         return NfcService.getInstance().commitRouting(isOverrideOrRecover);
558     }
559 
sendRoutingTable(boolean optionChanged, boolean force)560     private void sendRoutingTable(boolean optionChanged, boolean force) {
561         if (!mRoutingOptionManager.isRoutingTableOverrided()) {
562             if (force || optionChanged) {
563                 Log.d(TAG, "sendRoutingTable");
564                 NfcService.getInstance().setIsoDepProtocolRoute(mDefaultIsoDepRoute);
565                 NfcService.getInstance().setTechnologyABFRoute(mDefaultOffHostRoute,
566                         mDefaultFelicaRoute);
567             }
568         } else {
569             Log.d(TAG, "sendRoutingTable: Routing table is override, "
570                     + "Do not send the protocol, tech");
571         }
572     }
573 
574     /**
575      * This notifies that the AID routing table in the controller
576      * has been cleared (usually due to NFC being turned off).
577      */
onNfccRoutingTableCleared()578     public void onNfccRoutingTableCleared() {
579         // The routing table in the controller was cleared
580         // To stay in sync, clear our own tables.
581         synchronized (mLock) {
582             mAidRoutingTable.clear();
583             mRouteForAid.clear();
584             mPowerForAid.clear();
585         }
586     }
587 
dump(FileDescriptor fd, PrintWriter pw, String[] args)588     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
589         pw.println("Routing table:");
590         pw.println("    Default route: " + ((mDefaultRoute == 0x00) ? "host" : "secure element"));
591         synchronized (mLock) {
592             for (int i = 0; i < mAidRoutingTable.size(); i++) {
593                 Set<String> aids = mAidRoutingTable.valueAt(i);
594                 pw.println("    Routed to 0x" + Integer.toHexString(mAidRoutingTable.keyAt(i)) + ":");
595                 for (String aid : aids) {
596                     pw.println("        \"" + aid + "\"");
597                 }
598             }
599         }
600     }
601 
602     /**
603      * Dump debugging information as a AidRoutingManagerProto
604      *
605      * Note:
606      * See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto
607      * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
608      * {@link ProtoOutputStream#end(long)} after.
609      * Never reuse a proto field number. When removing a field, mark it as reserved.
610      */
dumpDebug(ProtoOutputStream proto)611     void dumpDebug(ProtoOutputStream proto) {
612         proto.write(AidRoutingManagerProto.DEFAULT_ROUTE, mDefaultRoute);
613         synchronized (mLock) {
614             for (int i = 0; i < mAidRoutingTable.size(); i++) {
615                 long token = proto.start(AidRoutingManagerProto.ROUTES);
616                 proto.write(AidRoutingManagerProto.Route.ID, mAidRoutingTable.keyAt(i));
617                 mAidRoutingTable.valueAt(i).forEach(aid -> {
618                     proto.write(AidRoutingManagerProto.Route.AIDS, aid);
619                 });
620                 proto.end(token);
621             }
622         }
623     }
624 
625     @VisibleForTesting
isRoutingTableCleared()626     public boolean isRoutingTableCleared() {
627         return mAidRoutingTable.size() == 0 && mRouteForAid.isEmpty() && mPowerForAid.isEmpty();
628     }
629 }
630