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 21 import com.android.nfc.NfcService; 22 23 import java.io.FileDescriptor; 24 import java.io.PrintWriter; 25 import java.util.HashMap; 26 import java.util.HashSet; 27 import java.util.Map; 28 import java.util.Set; 29 30 public class AidRoutingManager { 31 static final String TAG = "AidRoutingManager"; 32 33 static final boolean DBG = false; 34 35 static final int ROUTE_HOST = 0x00; 36 37 // Every routing table entry is matched exact 38 static final int AID_MATCHING_EXACT_ONLY = 0x00; 39 // Every routing table entry can be matched either exact or prefix 40 static final int AID_MATCHING_EXACT_OR_PREFIX = 0x01; 41 // Every routing table entry is matched as a prefix 42 static final int AID_MATCHING_PREFIX_ONLY = 0x02; 43 44 // This is the default IsoDep protocol route; it means 45 // that for any AID that needs to be routed to this 46 // destination, we won't need to add a rule to the routing 47 // table, because this destination is already the default route. 48 // 49 // For Nexus devices, the default route is always 0x00. 50 final int mDefaultRoute; 51 52 // For Nexus devices, just a static route to the eSE 53 // OEMs/Carriers could manually map off-host AIDs 54 // to the correct eSE/UICC based on state they keep. 55 final int mDefaultOffHostRoute; 56 57 // How the NFC controller can match AIDs in the routing table; 58 // see AID_MATCHING constants 59 final int mAidMatchingSupport; 60 61 final Object mLock = new Object(); 62 63 // mAidRoutingTable contains the current routing table. The index is the route ID. 64 // The route can include routes to a eSE/UICC. 65 SparseArray<Set<String>> mAidRoutingTable = 66 new SparseArray<Set<String>>(); 67 68 // Easy look-up what the route is for a certain AID 69 HashMap<String, Integer> mRouteForAid = new HashMap<String, Integer>(); 70 doGetDefaultRouteDestination()71 private native int doGetDefaultRouteDestination(); doGetDefaultOffHostRouteDestination()72 private native int doGetDefaultOffHostRouteDestination(); doGetAidMatchingMode()73 private native int doGetAidMatchingMode(); 74 AidRoutingManager()75 public AidRoutingManager() { 76 mDefaultRoute = doGetDefaultRouteDestination(); 77 if (DBG) Log.d(TAG, "mDefaultRoute=0x" + Integer.toHexString(mDefaultRoute)); 78 mDefaultOffHostRoute = doGetDefaultOffHostRouteDestination(); 79 if (DBG) Log.d(TAG, "mDefaultOffHostRoute=0x" + Integer.toHexString(mDefaultOffHostRoute)); 80 mAidMatchingSupport = doGetAidMatchingMode(); 81 if (DBG) Log.d(TAG, "mAidMatchingSupport=0x" + Integer.toHexString(mAidMatchingSupport)); 82 } 83 supportsAidPrefixRouting()84 public boolean supportsAidPrefixRouting() { 85 return mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX || 86 mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY; 87 } 88 clearNfcRoutingTableLocked()89 void clearNfcRoutingTableLocked() { 90 for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet()) { 91 NfcService.getInstance().unrouteAids(aidEntry.getKey()); 92 } 93 } 94 configureRouting(HashMap<String, Boolean> aidMap)95 public boolean configureRouting(HashMap<String, Boolean> aidMap) { 96 SparseArray<Set<String>> aidRoutingTable = new SparseArray<Set<String>>(aidMap.size()); 97 HashMap<String, Integer> routeForAid = new HashMap<String, Integer>(aidMap.size()); 98 // Then, populate internal data structures first 99 for (Map.Entry<String, Boolean> aidEntry : aidMap.entrySet()) { 100 int route = aidEntry.getValue() ? ROUTE_HOST : mDefaultOffHostRoute; 101 String aid = aidEntry.getKey(); 102 Set<String> entries = aidRoutingTable.get(route, new HashSet<String>()); 103 entries.add(aid); 104 aidRoutingTable.put(route, entries); 105 routeForAid.put(aid, route); 106 } 107 108 synchronized (mLock) { 109 if (routeForAid.equals(mRouteForAid)) { 110 if (DBG) Log.d(TAG, "Routing table unchanged, not updating"); 111 return false; 112 } 113 114 // Otherwise, update internal structures and commit new routing 115 clearNfcRoutingTableLocked(); 116 mRouteForAid = routeForAid; 117 mAidRoutingTable = aidRoutingTable; 118 if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) { 119 /* If a non-default route registers an exact AID which is shorter 120 * than this exact AID, this will create a problem with controllers 121 * that treat every AID in the routing table as a prefix. 122 * For example, if App A registers F0000000041010 as an exact AID, 123 * and App B registers F000000004 as an exact AID, and App B is not 124 * the default route, the following would be added to the routing table: 125 * F000000004 -> non-default destination 126 * However, because in this mode, the controller treats every routing table 127 * entry as a prefix, it means F0000000041010 would suddenly go to the non-default 128 * destination too, whereas it should have gone to the default. 129 * 130 * The only way to prevent this is to add the longer AIDs of the 131 * default route at the top of the table, so they will be matched first. 132 */ 133 Set<String> defaultRouteAids = mAidRoutingTable.get(mDefaultRoute); 134 if (defaultRouteAids != null) { 135 for (String defaultRouteAid : defaultRouteAids) { 136 // Check whether there are any shorted AIDs routed to non-default 137 // TODO this is O(N^2) run-time complexity... 138 for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet()) { 139 String aid = aidEntry.getKey(); 140 int route = aidEntry.getValue(); 141 if (defaultRouteAid.startsWith(aid) && route != mDefaultRoute) { 142 if (DBG) 143 Log.d(TAG, "Adding AID " + defaultRouteAid + " for default " + 144 "route, because a conflicting shorter AID will be " + 145 "added to the routing table"); 146 NfcService.getInstance().routeAids(defaultRouteAid, mDefaultRoute); 147 } 148 } 149 } 150 } 151 } 152 153 // Add AID entries for all non-default routes 154 for (int i = 0; i < mAidRoutingTable.size(); i++) { 155 int route = mAidRoutingTable.keyAt(i); 156 if (route != mDefaultRoute) { 157 Set<String> aidsForRoute = mAidRoutingTable.get(route); 158 for (String aid : aidsForRoute) { 159 if (aid.endsWith("*")) { 160 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) { 161 Log.e(TAG, "This device does not support prefix AIDs."); 162 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) { 163 if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route " 164 + Integer.toString(route)); 165 // Cut off '*' since controller anyway treats all AIDs as a prefix 166 NfcService.getInstance().routeAids(aid.substring(0, 167 aid.length() - 1), route); 168 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) { 169 if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route " 170 + Integer.toString(route)); 171 NfcService.getInstance().routeAids(aid, route); 172 } 173 } else { 174 if (DBG) Log.d(TAG, "Routing exact AID " + aid + " to route " 175 + Integer.toString(route)); 176 NfcService.getInstance().routeAids(aid, route); 177 } 178 } 179 } 180 } 181 } 182 183 // And finally commit the routing 184 NfcService.getInstance().commitRouting(); 185 186 return true; 187 } 188 189 /** 190 * This notifies that the AID routing table in the controller 191 * has been cleared (usually due to NFC being turned off). 192 */ onNfccRoutingTableCleared()193 public void onNfccRoutingTableCleared() { 194 // The routing table in the controller was cleared 195 // To stay in sync, clear our own tables. 196 synchronized (mLock) { 197 mAidRoutingTable.clear(); 198 mRouteForAid.clear(); 199 } 200 } 201 dump(FileDescriptor fd, PrintWriter pw, String[] args)202 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 203 pw.println("Routing table:"); 204 pw.println(" Default route: " + ((mDefaultRoute == 0x00) ? "host" : "secure element")); 205 synchronized (mLock) { 206 for (int i = 0; i < mAidRoutingTable.size(); i++) { 207 Set<String> aids = mAidRoutingTable.valueAt(i); 208 pw.println(" Routed to 0x" + Integer.toHexString(mAidRoutingTable.keyAt(i)) + ":"); 209 for (String aid : aids) { 210 pw.println(" \"" + aid + "\""); 211 } 212 } 213 } 214 } 215 } 216