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 import android.util.proto.ProtoOutputStream;
21 
22 import com.android.nfc.NfcService;
23 import com.android.nfc.NfcStatsLog;
24 import java.io.FileDescriptor;
25 import java.io.PrintWriter;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Map;
31 import java.util.Set;
32 
33 public class AidRoutingManager {
34 
35     static final String TAG = "AidRoutingManager";
36 
37     static final boolean DBG = false;
38 
39     static final int ROUTE_HOST = 0x00;
40 
41     // Every routing table entry is matched exact
42     static final int AID_MATCHING_EXACT_ONLY = 0x00;
43     // Every routing table entry can be matched either exact or prefix
44     static final int AID_MATCHING_EXACT_OR_PREFIX = 0x01;
45     // Every routing table entry is matched as a prefix
46     static final int AID_MATCHING_PREFIX_ONLY = 0x02;
47     // Every routing table entry can be matched either exact or prefix or subset only
48     static final int AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX = 0x03;
49 
50     int mDefaultIsoDepRoute;
51     //Let mDefaultRoute as default aid route
52     int mDefaultRoute;
53 
54     int mMaxAidRoutingTableSize;
55 
56     final byte[] mOffHostRouteUicc;
57     final byte[] mOffHostRouteEse;
58     // Used for backward compatibility in case application doesn't specify the
59     // SE
60     final int mDefaultOffHostRoute;
61 
62     // How the NFC controller can match AIDs in the routing table;
63     // see AID_MATCHING constants
64     final int mAidMatchingSupport;
65 
66     final Object mLock = new Object();
67 
68     // mAidRoutingTable contains the current routing table. The index is the route ID.
69     // The route can include routes to a eSE/UICC.
70     SparseArray<Set<String>> mAidRoutingTable =
71             new SparseArray<Set<String>>();
72 
73     // Easy look-up what the route is for a certain AID
74     HashMap<String, Integer> mRouteForAid = new HashMap<String, Integer>();
75 
doGetDefaultRouteDestination()76     private native int doGetDefaultRouteDestination();
doGetDefaultOffHostRouteDestination()77     private native int doGetDefaultOffHostRouteDestination();
doGetOffHostUiccDestination()78     private native byte[] doGetOffHostUiccDestination();
doGetOffHostEseDestination()79     private native byte[] doGetOffHostEseDestination();
doGetAidMatchingMode()80     private native int doGetAidMatchingMode();
doGetDefaultIsoDepRouteDestination()81     private native int doGetDefaultIsoDepRouteDestination();
82 
83     final class AidEntry {
84         boolean isOnHost;
85         String offHostSE;
86         int route;
87         int aidInfo;
88     }
89 
AidRoutingManager()90     public AidRoutingManager() {
91         mDefaultRoute = doGetDefaultRouteDestination();
92         if (DBG)
93             Log.d(TAG, "mDefaultRoute=0x" + Integer.toHexString(mDefaultRoute));
94         mDefaultOffHostRoute = doGetDefaultOffHostRouteDestination();
95         if (DBG)
96             Log.d(TAG, "mDefaultOffHostRoute=0x" + Integer.toHexString(mDefaultOffHostRoute));
97         mOffHostRouteUicc = doGetOffHostUiccDestination();
98         if (DBG)
99             Log.d(TAG, "mOffHostRouteUicc=" + Arrays.toString(mOffHostRouteUicc));
100         mOffHostRouteEse = doGetOffHostEseDestination();
101         if (DBG)
102           Log.d(TAG, "mOffHostRouteEse=" + Arrays.toString(mOffHostRouteEse));
103         mAidMatchingSupport = doGetAidMatchingMode();
104         if (DBG) Log.d(TAG, "mAidMatchingSupport=0x" + Integer.toHexString(mAidMatchingSupport));
105 
106         mDefaultIsoDepRoute = doGetDefaultIsoDepRouteDestination();
107         if (DBG) Log.d(TAG, "mDefaultIsoDepRoute=0x" + Integer.toHexString(mDefaultIsoDepRoute));
108     }
109 
supportsAidPrefixRouting()110     public boolean supportsAidPrefixRouting() {
111         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
112                 mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
113                  mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
114     }
115 
supportsAidSubsetRouting()116     public boolean supportsAidSubsetRouting() {
117         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
118     }
119 
calculateAidRouteSize(HashMap<String, AidEntry> routeCache)120     public int calculateAidRouteSize(HashMap<String, AidEntry> routeCache) {
121         // TAG + ROUTE + LENGTH_BYTE + POWER
122         int AID_HDR_LENGTH = 0x04;
123         int routeTableSize = 0x00;
124         for(Map.Entry<String, AidEntry> aidEntry : routeCache.entrySet()) {
125             String aid = aidEntry.getKey();
126             // removing prefix length
127             if(aid.endsWith("*")) {
128                 routeTableSize += ((aid.length() - 0x01) / 0x02) + AID_HDR_LENGTH;
129             } else {
130                 routeTableSize += (aid.length() / 0x02)+ AID_HDR_LENGTH;
131             }
132         }
133         if (DBG) Log.d(TAG, "calculateAidRouteSize: " + routeTableSize);
134         return routeTableSize;
135     }
136 
clearNfcRoutingTableLocked()137     private void clearNfcRoutingTableLocked() {
138         for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet())  {
139             String aid = aidEntry.getKey();
140             if (aid.endsWith("*")) {
141                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
142                     Log.e(TAG, "Device does not support prefix AIDs but AID [" + aid
143                             + "] is registered");
144                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
145                     if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid);
146                     // Cut off '*' since controller anyway treats all AIDs as a prefix
147                     aid = aid.substring(0, aid.length() - 1);
148                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
149                     mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
150                     aid = aid.substring(0, aid.length() - 1);
151                     if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid);
152                 }
153             }  else if (aid.endsWith("#")) {
154                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
155                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
156                             + "] is registered");
157                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
158                     mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) {
159                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
160                             + "] is registered");
161                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
162                     if (DBG) Log.d(TAG, "Unrouting subset AID " + aid);
163                     aid = aid.substring(0, aid.length() - 1);
164                 }
165             } else {
166                 if (DBG) Log.d(TAG, "Unrouting exact AID " + aid);
167             }
168 
169             NfcService.getInstance().unrouteAids(aid);
170         }
171         if(NfcService.getInstance().getNciVersion() != NfcService.getInstance().NCI_VERSION_1_0) {
172           // unRoute EmptyAid
173           NfcService.getInstance().unrouteAids("");
174         }
175     }
176 
getRouteForSecureElement(String se)177     private int getRouteForSecureElement(String se) {
178         if (se == null || se.length() <= 3) {
179             return 0;
180         }
181         try {
182             if (se.startsWith("eSE") && mOffHostRouteEse != null) {
183                 int index = Integer.parseInt(se.substring(3));
184                 if (mOffHostRouteEse.length >= index && index > 0) {
185                     return mOffHostRouteEse[index - 1] & 0xFF;
186                 }
187             } else if (se.startsWith("SIM") && mOffHostRouteUicc != null) {
188                 int index = Integer.parseInt(se.substring(3));
189                 if (mOffHostRouteUicc.length >= index && index > 0) {
190                     return mOffHostRouteUicc[index - 1] & 0xFF;
191                 }
192             }
193             if (mOffHostRouteEse == null && mOffHostRouteUicc == null)
194               return mDefaultOffHostRoute;
195         } catch (NumberFormatException e) { }
196         return 0;
197     }
198 
configureRouting(HashMap<String, AidEntry> aidMap, boolean force)199     public boolean configureRouting(HashMap<String, AidEntry> aidMap, boolean force) {
200         boolean aidRouteResolved = false;
201         HashMap<String, AidEntry> aidRoutingTableCache = new HashMap<String, AidEntry>(aidMap.size());
202         ArrayList<Integer> seList = new ArrayList<Integer>();
203         seList.add(mDefaultRoute);
204         if (mDefaultRoute != ROUTE_HOST) {
205             seList.add(ROUTE_HOST);
206         }
207 
208         SparseArray<Set<String>> aidRoutingTable = new SparseArray<Set<String>>(aidMap.size());
209         HashMap<String, Integer> routeForAid = new HashMap<String, Integer>(aidMap.size());
210         HashMap<String, Integer> infoForAid = new HashMap<String, Integer>(aidMap.size());
211         // Then, populate internal data structures first
212         for (Map.Entry<String, AidEntry> aidEntry : aidMap.entrySet())  {
213             int route = ROUTE_HOST;
214             if (!aidEntry.getValue().isOnHost) {
215                 String offHostSE = aidEntry.getValue().offHostSE;
216                 if (offHostSE == null) {
217                     route = mDefaultOffHostRoute;
218                 } else {
219                     route = getRouteForSecureElement(offHostSE);
220                     if (route == 0) {
221                         Log.e(TAG, "Invalid Off host Aid Entry " + offHostSE);
222                         continue;
223                     }
224                 }
225             }
226             if (!seList.contains(route))
227                 seList.add(route);
228             aidEntry.getValue().route = route;
229             int aidType = aidEntry.getValue().aidInfo;
230             String aid = aidEntry.getKey();
231             Set<String> entries =
232                     aidRoutingTable.get(route, new HashSet<String>());
233             entries.add(aid);
234             aidRoutingTable.put(route, entries);
235             routeForAid.put(aid, route);
236             infoForAid.put(aid, aidType);
237         }
238 
239         synchronized (mLock) {
240             if (routeForAid.equals(mRouteForAid) && !force) {
241                 if (DBG) Log.d(TAG, "Routing table unchanged, not updating");
242                 return false;
243             }
244 
245             // Otherwise, update internal structures and commit new routing
246             clearNfcRoutingTableLocked();
247             mRouteForAid = routeForAid;
248             mAidRoutingTable = aidRoutingTable;
249 
250             mMaxAidRoutingTableSize = NfcService.getInstance().getAidRoutingTableSize();
251             if (DBG) Log.d(TAG, "mMaxAidRoutingTableSize: " + mMaxAidRoutingTableSize);
252 
253             //calculate AidRoutingTableSize for existing route destination
254             for(int index = 0; index < seList.size(); index ++) {
255               mDefaultRoute = seList.get(index);
256               if(index != 0)
257                 if (DBG) Log.d(TAG, "AidRoutingTable is full, try to switch mDefaultRoute to 0x" + Integer.toHexString(mDefaultRoute));
258 
259               aidRoutingTableCache.clear();
260 
261               if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
262                   /* If a non-default route registers an exact AID which is shorter
263                    * than this exact AID, this will create a problem with controllers
264                    * that treat every AID in the routing table as a prefix.
265                    * For example, if App A registers F0000000041010 as an exact AID,
266                    * and App B registers F000000004 as an exact AID, and App B is not
267                    * the default route, the following would be added to the routing table:
268                    * F000000004 -> non-default destination
269                    * However, because in this mode, the controller treats every routing table
270                    * entry as a prefix, it means F0000000041010 would suddenly go to the non-default
271                    * destination too, whereas it should have gone to the default.
272                    *
273                    * The only way to prevent this is to add the longer AIDs of the
274                    * default route at the top of the table, so they will be matched first.
275                    */
276                   Set<String> defaultRouteAids = mAidRoutingTable.get(mDefaultRoute);
277                   if (defaultRouteAids != null) {
278                       for (String defaultRouteAid : defaultRouteAids) {
279                           // Check whether there are any shorted AIDs routed to non-default
280                           // TODO this is O(N^2) run-time complexity...
281                           for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet()) {
282                               String aid = aidEntry.getKey();
283                               int route = aidEntry.getValue();
284                               if (defaultRouteAid.startsWith(aid) && route != mDefaultRoute) {
285                                   if (DBG) Log.d(TAG, "Adding AID " + defaultRouteAid + " for default " +
286                                           "route, because a conflicting shorter AID will be " +
287                                           "added to the routing table");
288                                     aidRoutingTableCache.put(defaultRouteAid, aidMap.get(defaultRouteAid));
289                               }
290                           }
291                       }
292                   }
293               }
294 
295               // Add AID entries for all non-default routes
296               for (int i = 0; i < mAidRoutingTable.size(); i++) {
297                   int route = mAidRoutingTable.keyAt(i);
298                   if (route != mDefaultRoute) {
299                       Set<String> aidsForRoute = mAidRoutingTable.get(route);
300                       for (String aid : aidsForRoute) {
301                           if (aid.endsWith("*")) {
302                               if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
303                                   Log.e(TAG, "This device does not support prefix AIDs.");
304                               } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
305                                   if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route "
306                                           + Integer.toString(route));
307                                   // Cut off '*' since controller anyway treats all AIDs as a prefix
308                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
309                               } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
310                                 mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
311                                   if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route "
312                                           + Integer.toString(route));
313                                   aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
314                               }
315                           } else if (aid.endsWith("#")) {
316                               if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
317                                   Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
318                                           + "] is registered");
319                               } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
320                                   mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) {
321                                   Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
322                                           + "] is registered");
323                               } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
324                                   if (DBG) Log.d(TAG, "Routing subset AID " + aid + " to route "
325                                           + Integer.toString(route));
326                                   aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
327                               }
328                          } else {
329                               if (DBG) Log.d(TAG, "Routing exact AID " + aid + " to route "
330                                       + Integer.toString(route));
331                                 aidRoutingTableCache.put(aid, aidMap.get(aid));
332                           }
333                         }
334                  }
335               }
336 
337               if(mDefaultRoute != mDefaultIsoDepRoute) {
338                 if(NfcService.getInstance().getNciVersion() != NfcService.getInstance().NCI_VERSION_1_0) {
339                   String emptyAid = "";
340                   AidEntry entry = new AidEntry();
341                   entry.route = mDefaultRoute;
342                   if(mDefaultRoute==ROUTE_HOST) {
343                     entry.isOnHost = true;
344                   } else{
345                     entry.isOnHost = false;
346                    }
347                   entry.aidInfo = RegisteredAidCache.AID_ROUTE_QUAL_PREFIX;
348                   aidRoutingTableCache.put(emptyAid, entry);
349                   if (DBG) Log.d(TAG, "Add emptyAid into AidRoutingTable");
350                 }
351               }
352 
353               if( calculateAidRouteSize(aidRoutingTableCache) <= mMaxAidRoutingTableSize) {
354                 aidRouteResolved = true;
355                 break;
356               }
357           }
358 
359           if(aidRouteResolved == true) {
360               commit(aidRoutingTableCache);
361           } else {
362               NfcStatsLog.write(NfcStatsLog.NFC_ERROR_OCCURRED,
363                       NfcStatsLog.NFC_ERROR_OCCURRED__TYPE__AID_OVERFLOW, 0, 0);
364               Log.e(TAG, "RoutingTable unchanged because it's full, not updating");
365           }
366         }
367         return true;
368     }
369 
commit(HashMap<String, AidEntry> routeCache )370     private void commit(HashMap<String, AidEntry> routeCache ) {
371 
372         if(routeCache != null) {
373 
374             for (Map.Entry<String, AidEntry> aidEntry : routeCache.entrySet())  {
375                 int route = aidEntry.getValue().route;
376                 int aidType = aidEntry.getValue().aidInfo;
377                 String aid = aidEntry.getKey();
378                 if (DBG) Log.d (TAG, "commit aid:"+aid+"route:"+route+"aidtype:"+aidType);
379 
380                 NfcService.getInstance().routeAids(aid, route, aidType);
381             }
382         }
383 
384         // And finally commit the routing
385         NfcService.getInstance().commitRouting();
386     }
387 
388     /**
389      * This notifies that the AID routing table in the controller
390      * has been cleared (usually due to NFC being turned off).
391      */
onNfccRoutingTableCleared()392     public void onNfccRoutingTableCleared() {
393         // The routing table in the controller was cleared
394         // To stay in sync, clear our own tables.
395         synchronized (mLock) {
396             mAidRoutingTable.clear();
397             mRouteForAid.clear();
398         }
399     }
400 
dump(FileDescriptor fd, PrintWriter pw, String[] args)401     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
402         pw.println("Routing table:");
403         pw.println("    Default route: " + ((mDefaultRoute == 0x00) ? "host" : "secure element"));
404         synchronized (mLock) {
405             for (int i = 0; i < mAidRoutingTable.size(); i++) {
406                 Set<String> aids = mAidRoutingTable.valueAt(i);
407                 pw.println("    Routed to 0x" + Integer.toHexString(mAidRoutingTable.keyAt(i)) + ":");
408                 for (String aid : aids) {
409                     pw.println("        \"" + aid + "\"");
410                 }
411             }
412         }
413     }
414 
415     /**
416      * Dump debugging information as a AidRoutingManagerProto
417      *
418      * Note:
419      * See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto
420      * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
421      * {@link ProtoOutputStream#end(long)} after.
422      * Never reuse a proto field number. When removing a field, mark it as reserved.
423      */
dumpDebug(ProtoOutputStream proto)424     void dumpDebug(ProtoOutputStream proto) {
425         proto.write(AidRoutingManagerProto.DEFAULT_ROUTE, mDefaultRoute);
426         synchronized (mLock) {
427             for (int i = 0; i < mAidRoutingTable.size(); i++) {
428                 long token = proto.start(AidRoutingManagerProto.ROUTES);
429                 proto.write(AidRoutingManagerProto.Route.ID, mAidRoutingTable.keyAt(i));
430                 mAidRoutingTable.valueAt(i).forEach(aid -> {
431                     proto.write(AidRoutingManagerProto.Route.AIDS, aid);
432                 });
433                 proto.end(token);
434             }
435         }
436     }
437 }
438