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