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 */
16package com.android.nfc.cardemulation;
17
18import android.util.Log;
19import android.util.SparseArray;
20
21import com.android.nfc.NfcService;
22
23import java.io.FileDescriptor;
24import java.io.PrintWriter;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.Map;
28import java.util.Set;
29
30public 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