• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
doGetDefaultRouteDestination()73     private native int doGetDefaultRouteDestination();
doGetDefaultOffHostRouteDestination()74     private native int doGetDefaultOffHostRouteDestination();
doGetAidMatchingMode()75     private native int doGetAidMatchingMode();
76 
77     final class AidEntry {
78         boolean isOnHost;
79         int aidInfo;
80     }
81 
AidRoutingManager()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 
supportsAidPrefixRouting()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 
supportsAidSubsetRouting()97     public boolean supportsAidSubsetRouting() {
98         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
99     }
100 
clearNfcRoutingTableLocked()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 
configureRouting(HashMap<String, AidEntry> aidMap)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      */
onNfccRoutingTableCleared()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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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