• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 
17 package com.android.nfc;
18 
19 import android.nfc.Entry;
20 import android.sysprop.NfcProperties;
21 import android.util.Log;
22 
23 import androidx.annotation.VisibleForTesting;
24 
25 import com.android.nfc.cardemulation.RoutingOptionManager;
26 
27 import java.io.PrintWriter;
28 import java.nio.charset.StandardCharsets;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Vector;
34 
35 /**
36 * Parse the Routing Table from the last backup lmrt cmd and dump it with a clear typography
37 */
38 public class RoutingTableParser {
39     static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
40     private static final String TAG = "RoutingTableParser";
41     private static int sRoutingTableSize = 0;
42     private static int sRoutingTableMaxSize = 0;
43     private static final Vector<RoutingEntryInfo> sRoutingTable = new Vector<RoutingEntryInfo>(0);
44 
45     // Entry types
46     static final byte TYPE_TECHNOLOGY = 0;
47     static final byte TYPE_PROTOCOL = 1;
48     static final byte TYPE_AID = 2;
49     static final byte TYPE_SYSTEMCODE = 3;
50     static final byte TYPE_UNSUPPORTED = 4;
51 
52     // Commit status
53     static final int STATS_HOST_OK = 0;
54     static final int STATS_OFFHOST_OK = 1;
55     static final int STATS_NOT_FOUND = 2;
56 
57     private interface GetEntryStr {
getEntryStr(byte[] entry)58         String getEntryStr(byte[] entry);
59     }
60 
61     private GetEntryStr[] mGetEntryStrFuncs = new GetEntryStr[] {
62         new GetEntryStr() { public String getEntryStr(byte[] entry) {
63                 return getTechStr(entry); } },
64         new GetEntryStr() { public String getEntryStr(byte[] entry) {
65                 return getProtoStr(entry); } },
66         new GetEntryStr() { public String getEntryStr(byte[] entry) {
67                 return getAidStr(entry); } },
68         new GetEntryStr() { public String getEntryStr(byte[] entry) {
69                 return getSystemCodeStr(entry); } },
70     };
71 
getTechStr(byte[] tech)72     private String getTechStr(byte[] tech) {
73         String[] tech_mask_list = {
74             "TECHNOLOGY_A", "TECHNOLOGY_B", "TECHNOLOGY_F", "TECHNOLOGY_V"
75         };
76 
77         if (tech[0] > tech_mask_list.length) {
78             return "UNSUPPORTED_TECH";
79         }
80         return tech_mask_list[tech[0]];
81     }
82 
getProtoStr(byte[] proto)83     private String getProtoStr(byte[] proto) {
84         String[] proto_mask_list = {
85             "PROTOCOL_UNDETERMINED", "PROTOCOL_T1T", "PROTOCOL_T2T", "PROTOCOL_T3T",
86             "PROTOCOL_ISO_DEP", "PROTOCOL_NFC_DEP", "PROTOCOL_T5T", "PROTOCOL_NDEF"
87         };
88         if (proto[0] > proto_mask_list.length) {
89             return "UNSUPPORTED_PROTO";
90         }
91         return proto_mask_list[proto[0]];
92     }
93 
getAidStr(byte[] aid)94     private String getAidStr(byte[] aid) {
95         String aidStr = "";
96 
97         for (byte b : aid) {
98             aidStr += String.format("%02X", b);
99         }
100 
101         if (aidStr.length() == 0) {
102             return "Empty_AID";
103         }
104         return "AID_" + aidStr;
105     }
106 
getSystemCodeStr(byte[] sc)107     private String getSystemCodeStr(byte[] sc) {
108         String systemCodeStr = "";
109         for (byte b : sc) {
110             systemCodeStr += String.format("%02X", b);
111         }
112         return "SYSTEMCODE_" + systemCodeStr;
113     }
114 
getBlockCtrlStr(byte mask)115     private String getBlockCtrlStr(byte mask) {
116         if ((mask & 0x40) != 0) {
117             return "True";
118         }
119         return "False";
120     }
121 
getPrefixSubsetStr(byte mask, byte type)122     private String getPrefixSubsetStr(byte mask, byte type) {
123         if (type != TYPE_AID) {
124             return "";
125         }
126 
127         String prefix_subset_str = "";
128         if ((mask & 0x10) != 0) {
129             prefix_subset_str += "Prefix ";
130         }
131         if ((mask & 0x20) != 0) {
132             prefix_subset_str += "Subset";
133         }
134         if (prefix_subset_str.equals("")) {
135             return "Exact";
136         }
137         return prefix_subset_str;
138     }
139 
formatRow(String entry, String eeId, String pwrState, String blkCtrl, String extra)140     private String formatRow(String entry, String eeId,
141             String pwrState, String blkCtrl, String extra) {
142         String fmt = "\t%-36s\t%8s\t%-11s\t%-10s\t%-10s";
143         return String.format(fmt, entry, eeId, pwrState, blkCtrl, extra);
144     }
145 
146     private class RoutingEntryInfo {
147         public final byte mQualifier;
148         public final byte mType;
149         public final byte mNfceeId;
150         public final byte mPowerState;
151         public final byte[] mEntry;
152 
RoutingEntryInfo(byte qualifier, byte type, byte eeId, byte pwrState, byte[] entry)153         private RoutingEntryInfo(byte qualifier, byte type, byte eeId, byte pwrState,
154                 byte[] entry) {
155             mQualifier = qualifier;
156             mType = type;
157             mNfceeId = eeId;
158             mPowerState = pwrState;
159             mEntry = entry;
160         }
161 
dump(PrintWriter pw)162         private void dump(PrintWriter pw) {
163             String blkCtrl = getBlockCtrlStr(mQualifier);
164             String eeId = String.format("0x%02X", mNfceeId);
165             String pwrState = String.format("0x%02X", mPowerState);
166             String entry = mGetEntryStrFuncs[mType].getEntryStr(mEntry);
167             String extra = getPrefixSubsetStr(mQualifier, mType);
168 
169             pw.println(formatRow(entry, eeId, pwrState, blkCtrl, extra));
170         }
171     }
172 
validateEntryInfo(byte type, byte[] entry)173     private boolean validateEntryInfo(byte type, byte[] entry) {
174         switch(type) {
175             case TYPE_TECHNOLOGY:
176                 if (entry.length != 1) return false;
177                 break;
178             case TYPE_PROTOCOL:
179                 if (entry.length != 1) return false;
180                 break;
181             case TYPE_AID:
182                 if (entry.length > 16) return false;
183                 break;
184             case TYPE_SYSTEMCODE:
185                 if (entry.length != 2) return false;
186                 break;
187             default:
188                 return false;
189         }
190         return true;
191     }
192 
193     /**
194     * Check commit status by inputting type and entry
195     */
196     @VisibleForTesting
getCommitStatus(byte type, byte[] entry)197     public int getCommitStatus(byte type, byte[] entry) {
198         if (!validateEntryInfo(type, entry)) return STATS_NOT_FOUND;
199 
200         for (RoutingEntryInfo routingEntry : sRoutingTable) {
201             if (routingEntry.mType != type) {
202                 continue;
203             }
204             if (Arrays.equals(routingEntry.mEntry, entry)) {
205                 return routingEntry.mNfceeId == 0x00 ? STATS_HOST_OK : STATS_OFFHOST_OK;
206             }
207             if (routingEntry.mType != TYPE_AID) {
208                 continue;
209             }
210             if ((routingEntry.mQualifier & 0x10) != 0
211                     && entry.length > routingEntry.mEntry.length) {
212                 int ptr = 0;
213                 while (entry[ptr] == routingEntry.mEntry[ptr]) {
214                     ptr += 1;
215                 }
216                 if (ptr == routingEntry.mEntry.length) {
217                     return routingEntry.mNfceeId == 0x00 ? STATS_HOST_OK : STATS_OFFHOST_OK;
218                 }
219             }
220             if ((routingEntry.mQualifier & 0x20) != 0
221                     && entry.length < routingEntry.mEntry.length) {
222                 int ptr = 0;
223                 while (entry[ptr] == routingEntry.mEntry[ptr]) {
224                     ptr += 1;
225                 }
226                 if (ptr == entry.length) {
227                     return routingEntry.mNfceeId == 0x00 ? STATS_HOST_OK : STATS_OFFHOST_OK;
228                 }
229             }
230         }
231         return STATS_NOT_FOUND;
232     }
233 
addRoutingEntry(byte[] rt, int offset)234     private void addRoutingEntry(byte[] rt, int offset) {
235         if (offset + 1 >= rt.length) return;
236         int valueLength = Byte.toUnsignedInt(rt[offset + 1]);
237 
238         // Qualifier-Type(1 byte) + Length(1 byte) + Value(valueLength bytes)
239         if (offset + 2 + valueLength > rt.length) return;
240 
241         byte qualifier = (byte) (rt[offset] & 0xF0);
242         byte type = (byte) (rt[offset] & 0x0F);
243         byte eeId = rt[offset + 2];
244         byte pwrState = rt[offset + 3];
245         byte[] entry = new byte[valueLength - 2];
246         for (int i = 0; i < valueLength - 2; i++) {
247             entry[i] = rt[offset + 4 + i];
248         }
249 
250         if (type == TYPE_SYSTEMCODE && (entry.length & 1) == 0 && entry.length <= 64) {
251             for (int i = 0; i < entry.length; i += 2) {
252                 byte[] sc_entry = {entry[i], entry[i + 1]};
253                 sRoutingTable.add(new RoutingEntryInfo(qualifier, type, eeId, pwrState, sc_entry));
254             }
255         } else if (validateEntryInfo(type, entry)) {
256             sRoutingTable.add(new RoutingEntryInfo(qualifier, type, eeId, pwrState, entry));
257         }
258     }
259 
260     /**
261     * Parse the raw data of routing table
262     */
parse(byte[] rt)263     public void parse(byte[] rt) {
264         int offset = 0;
265 
266         logRoutingTableRawData(rt);
267 
268         sRoutingTable.clear();
269         while (offset < rt.length) {
270             byte type = (byte) (rt[offset] & 0x0F);
271             if (type >= TYPE_UNSUPPORTED) {
272                 // Unrecognizable entry type
273                 Log.e(TAG, String.format("parse: Unrecognizable entry type=0x%02X, stop parsing",
274                         type));
275                 return;
276             }
277             if (offset + 1 >= rt.length) {
278                 // Buffer overflow
279                 Log.e(TAG, String.format("parse: Wrong tlv length, stop parsing"));
280                 return;
281             }
282             // Qualifier-Type(1 byte) + Length(1 byte) + Value(valueLength bytes)
283             int tlvLength = Byte.toUnsignedInt(rt[offset + 1]) + 2;
284 
285             addRoutingEntry(rt, offset);
286 
287             offset += tlvLength;
288         }
289     }
290 
291     /**
292     * Get Routing Table from the last backup lmrt cmd and parse it
293     */
update(DeviceHost dh)294     public void update(DeviceHost dh) {
295         sRoutingTableMaxSize = dh.getMaxRoutingTableSize();
296         byte[] rt = dh.getRoutingTable();
297         sRoutingTableSize = rt.length;
298         parse(rt);
299     }
300 
301     /**
302     * Get Routing Table from the last backup lmrt cmd and dump it
303     */
dump(DeviceHost dh, PrintWriter pw)304     public void dump(DeviceHost dh, PrintWriter pw) {
305         update(dh);
306 
307         pw.println("--- dumpRoutingTable: start ---");
308         pw.println(String.format(Locale.US, "RoutingTableSize=%d/%d",
309                 sRoutingTableSize, sRoutingTableMaxSize));
310         pw.println(formatRow("Entry", "NFCEE_ID", "Power State", "Block Ctrl", "Extra Info"));
311 
312         for (RoutingEntryInfo routingEntry : sRoutingTable) {
313             routingEntry.dump(pw);
314         }
315 
316         pw.println("--- dumpRoutingTable:  end  ---");
317     }
318 
logRoutingTableRawData(byte[] lmrt_cmd)319     private void logRoutingTableRawData(byte[] lmrt_cmd) {
320         if (!DBG) return;
321         String lmrt_str = "";
322 
323         for (byte b : lmrt_cmd) {
324             lmrt_str += String.format("%02X ", b);
325         }
326         Log.i(TAG, String.format("logRoutingTableRawData: RoutingTableSize=%d", lmrt_cmd.length));
327         Log.i(TAG, String.format("logRoutingTableRawData: RoutingTable=%s", lmrt_str));
328     }
329 
getRoutingTableEntryList(DeviceHost dh)330     public List<Entry> getRoutingTableEntryList(DeviceHost dh) {
331         update(dh);
332         List<Entry> entries = new ArrayList<>();
333         for (RoutingEntryInfo info : sRoutingTable) {
334             String entry = switch (info.mType) {
335                 case TYPE_TECHNOLOGY -> getTechStr(info.mEntry);
336                 case TYPE_PROTOCOL -> getProtoStr(info.mEntry);
337                 case TYPE_AID -> getAidStr(info.mEntry);
338                 case TYPE_SYSTEMCODE -> new String(info.mEntry, StandardCharsets.UTF_8);
339                 default -> null;
340             };
341             entries.add(new Entry(entry, info.mType, info.mNfceeId,
342                     RoutingOptionManager.getInstance().getSecureElementForRoute(info.mNfceeId)));
343         }
344         return entries;
345     }
346 }
347