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 71 private native int doGetDefaultRouteDestination(); 72 private native int doGetDefaultOffHostRouteDestination(); 73 private native int doGetAidMatchingMode(); 74 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 84 public boolean supportsAidPrefixRouting() { 85 return mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX || 86 mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY; 87 } 88 89 void clearNfcRoutingTableLocked() { 90 for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet()) { 91 String aid = aidEntry.getKey(); 92 if (aid.endsWith("*")) { 93 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) { 94 Log.e(TAG, "Device does not support prefix AIDs but AID [" + aid 95 + "] is registered"); 96 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) { 97 if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid); 98 // Cut off '*' since controller anyway treats all AIDs as a prefix 99 aid = aid.substring(0, aid.length() - 1); 100 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) { 101 if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid); 102 } 103 } else { 104 if (DBG) Log.d(TAG, "Unrouting exact AID " + aid); 105 } 106 107 NfcService.getInstance().unrouteAids(aid); 108 } 109 } 110 111 public boolean configureRouting(HashMap<String, Boolean> aidMap) { 112 SparseArray<Set<String>> aidRoutingTable = new SparseArray<Set<String>>(aidMap.size()); 113 HashMap<String, Integer> routeForAid = new HashMap<String, Integer>(aidMap.size()); 114 // Then, populate internal data structures first 115 for (Map.Entry<String, Boolean> aidEntry : aidMap.entrySet()) { 116 int route = aidEntry.getValue() ? ROUTE_HOST : mDefaultOffHostRoute; 117 String aid = aidEntry.getKey(); 118 Set<String> entries = aidRoutingTable.get(route, new HashSet<String>()); 119 entries.add(aid); 120 aidRoutingTable.put(route, entries); 121 routeForAid.put(aid, route); 122 } 123 124 synchronized (mLock) { 125 if (routeForAid.equals(mRouteForAid)) { 126 if (DBG) Log.d(TAG, "Routing table unchanged, not updating"); 127 return false; 128 } 129 130 // Otherwise, update internal structures and commit new routing 131 clearNfcRoutingTableLocked(); 132 mRouteForAid = routeForAid; 133 mAidRoutingTable = aidRoutingTable; 134 if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) { 135 /* If a non-default route registers an exact AID which is shorter 136 * than this exact AID, this will create a problem with controllers 137 * that treat every AID in the routing table as a prefix. 138 * For example, if App A registers F0000000041010 as an exact AID, 139 * and App B registers F000000004 as an exact AID, and App B is not 140 * the default route, the following would be added to the routing table: 141 * F000000004 -> non-default destination 142 * However, because in this mode, the controller treats every routing table 143 * entry as a prefix, it means F0000000041010 would suddenly go to the non-default 144 * destination too, whereas it should have gone to the default. 145 * 146 * The only way to prevent this is to add the longer AIDs of the 147 * default route at the top of the table, so they will be matched first. 148 */ 149 Set<String> defaultRouteAids = mAidRoutingTable.get(mDefaultRoute); 150 if (defaultRouteAids != null) { 151 for (String defaultRouteAid : defaultRouteAids) { 152 // Check whether there are any shorted AIDs routed to non-default 153 // TODO this is O(N^2) run-time complexity... 154 for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet()) { 155 String aid = aidEntry.getKey(); 156 int route = aidEntry.getValue(); 157 if (defaultRouteAid.startsWith(aid) && route != mDefaultRoute) { 158 if (DBG) 159 Log.d(TAG, "Adding AID " + defaultRouteAid + " for default " + 160 "route, because a conflicting shorter AID will be " + 161 "added to the routing table"); 162 NfcService.getInstance().routeAids(defaultRouteAid, mDefaultRoute); 163 } 164 } 165 } 166 } 167 } 168 169 // Add AID entries for all non-default routes 170 for (int i = 0; i < mAidRoutingTable.size(); i++) { 171 int route = mAidRoutingTable.keyAt(i); 172 if (route != mDefaultRoute) { 173 Set<String> aidsForRoute = mAidRoutingTable.get(route); 174 for (String aid : aidsForRoute) { 175 if (aid.endsWith("*")) { 176 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) { 177 Log.e(TAG, "This device does not support prefix AIDs."); 178 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) { 179 if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route " 180 + Integer.toString(route)); 181 // Cut off '*' since controller anyway treats all AIDs as a prefix 182 NfcService.getInstance().routeAids(aid.substring(0, 183 aid.length() - 1), route); 184 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) { 185 if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route " 186 + Integer.toString(route)); 187 NfcService.getInstance().routeAids(aid, route); 188 } 189 } else { 190 if (DBG) Log.d(TAG, "Routing exact AID " + aid + " to route " 191 + Integer.toString(route)); 192 NfcService.getInstance().routeAids(aid, route); 193 } 194 } 195 } 196 } 197 } 198 199 // And finally commit the routing 200 NfcService.getInstance().commitRouting(); 201 202 return true; 203 } 204 205 /** 206 * This notifies that the AID routing table in the controller 207 * has been cleared (usually due to NFC being turned off). 208 */ 209 public void onNfccRoutingTableCleared() { 210 // The routing table in the controller was cleared 211 // To stay in sync, clear our own tables. 212 synchronized (mLock) { 213 mAidRoutingTable.clear(); 214 mRouteForAid.clear(); 215 } 216 } 217 218 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 219 pw.println("Routing table:"); 220 pw.println(" Default route: " + ((mDefaultRoute == 0x00) ? "host" : "secure element")); 221 synchronized (mLock) { 222 for (int i = 0; i < mAidRoutingTable.size(); i++) { 223 Set<String> aids = mAidRoutingTable.valueAt(i); 224 pw.println(" Routed to 0x" + Integer.toHexString(mAidRoutingTable.keyAt(i)) + ":"); 225 for (String aid : aids) { 226 pw.println(" \"" + aid + "\""); 227 } 228 } 229 } 230 } 231 } 232