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             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 
configureRouting(HashMap<String, Boolean> aidMap)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      */
onNfccRoutingTableCleared()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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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