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 
     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 
     73     private native int doGetDefaultRouteDestination();
     74     private native int doGetDefaultOffHostRouteDestination();
     75     private native int doGetAidMatchingMode();
     76 
     77     final class AidEntry {
     78         boolean isOnHost;
     79         int aidInfo;
     80     }
     81 
     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 
     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 
     97     public boolean supportsAidSubsetRouting() {
     98         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
     99     }
    100 
    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 
    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      */
    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 
    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