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