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