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