Home | History | Annotate | Download | only in cardemulation
      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