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