/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.nfc; import android.nfc.Entry; import android.sysprop.NfcProperties; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.nfc.cardemulation.RoutingOptionManager; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Vector; /** * Parse the Routing Table from the last backup lmrt cmd and dump it with a clear typography */ public class RoutingTableParser { static final boolean DBG = NfcProperties.debug_enabled().orElse(true); private static final String TAG = "RoutingTableParser"; private static int sRoutingTableSize = 0; private static int sRoutingTableMaxSize = 0; private static final Vector sRoutingTable = new Vector(0); // Entry types static final byte TYPE_TECHNOLOGY = 0; static final byte TYPE_PROTOCOL = 1; static final byte TYPE_AID = 2; static final byte TYPE_SYSTEMCODE = 3; static final byte TYPE_UNSUPPORTED = 4; // Commit status static final int STATS_HOST_OK = 0; static final int STATS_OFFHOST_OK = 1; static final int STATS_NOT_FOUND = 2; private interface GetEntryStr { String getEntryStr(byte[] entry); } private GetEntryStr[] mGetEntryStrFuncs = new GetEntryStr[] { new GetEntryStr() { public String getEntryStr(byte[] entry) { return getTechStr(entry); } }, new GetEntryStr() { public String getEntryStr(byte[] entry) { return getProtoStr(entry); } }, new GetEntryStr() { public String getEntryStr(byte[] entry) { return getAidStr(entry); } }, new GetEntryStr() { public String getEntryStr(byte[] entry) { return getSystemCodeStr(entry); } }, }; private String getTechStr(byte[] tech) { String[] tech_mask_list = { "TECHNOLOGY_A", "TECHNOLOGY_B", "TECHNOLOGY_F", "TECHNOLOGY_V" }; if (tech[0] > tech_mask_list.length) { return "UNSUPPORTED_TECH"; } return tech_mask_list[tech[0]]; } private String getProtoStr(byte[] proto) { String[] proto_mask_list = { "PROTOCOL_UNDETERMINED", "PROTOCOL_T1T", "PROTOCOL_T2T", "PROTOCOL_T3T", "PROTOCOL_ISO_DEP", "PROTOCOL_NFC_DEP", "PROTOCOL_T5T", "PROTOCOL_NDEF" }; if (proto[0] > proto_mask_list.length) { return "UNSUPPORTED_PROTO"; } return proto_mask_list[proto[0]]; } private String getAidStr(byte[] aid) { String aidStr = ""; for (byte b : aid) { aidStr += String.format("%02X", b); } if (aidStr.length() == 0) { return "Empty_AID"; } return "AID_" + aidStr; } private String getSystemCodeStr(byte[] sc) { String systemCodeStr = ""; for (byte b : sc) { systemCodeStr += String.format("%02X", b); } return "SYSTEMCODE_" + systemCodeStr; } private String getBlockCtrlStr(byte mask) { if ((mask & 0x40) != 0) { return "True"; } return "False"; } private String getPrefixSubsetStr(byte mask, byte type) { if (type != TYPE_AID) { return ""; } String prefix_subset_str = ""; if ((mask & 0x10) != 0) { prefix_subset_str += "Prefix "; } if ((mask & 0x20) != 0) { prefix_subset_str += "Subset"; } if (prefix_subset_str.equals("")) { return "Exact"; } return prefix_subset_str; } private String formatRow(String entry, String eeId, String pwrState, String blkCtrl, String extra) { String fmt = "\t%-36s\t%8s\t%-11s\t%-10s\t%-10s"; return String.format(fmt, entry, eeId, pwrState, blkCtrl, extra); } private class RoutingEntryInfo { public final byte mQualifier; public final byte mType; public final byte mNfceeId; public final byte mPowerState; public final byte[] mEntry; private RoutingEntryInfo(byte qualifier, byte type, byte eeId, byte pwrState, byte[] entry) { mQualifier = qualifier; mType = type; mNfceeId = eeId; mPowerState = pwrState; mEntry = entry; } private void dump(PrintWriter pw) { String blkCtrl = getBlockCtrlStr(mQualifier); String eeId = String.format("0x%02X", mNfceeId); String pwrState = String.format("0x%02X", mPowerState); String entry = mGetEntryStrFuncs[mType].getEntryStr(mEntry); String extra = getPrefixSubsetStr(mQualifier, mType); pw.println(formatRow(entry, eeId, pwrState, blkCtrl, extra)); } } private boolean validateEntryInfo(byte type, byte[] entry) { switch(type) { case TYPE_TECHNOLOGY: if (entry.length != 1) return false; break; case TYPE_PROTOCOL: if (entry.length != 1) return false; break; case TYPE_AID: if (entry.length > 16) return false; break; case TYPE_SYSTEMCODE: if (entry.length != 2) return false; break; default: return false; } return true; } /** * Check commit status by inputting type and entry */ @VisibleForTesting public int getCommitStatus(byte type, byte[] entry) { if (!validateEntryInfo(type, entry)) return STATS_NOT_FOUND; for (RoutingEntryInfo routingEntry : sRoutingTable) { if (routingEntry.mType != type) { continue; } if (Arrays.equals(routingEntry.mEntry, entry)) { return routingEntry.mNfceeId == 0x00 ? STATS_HOST_OK : STATS_OFFHOST_OK; } if (routingEntry.mType != TYPE_AID) { continue; } if ((routingEntry.mQualifier & 0x10) != 0 && entry.length > routingEntry.mEntry.length) { int ptr = 0; while (entry[ptr] == routingEntry.mEntry[ptr]) { ptr += 1; } if (ptr == routingEntry.mEntry.length) { return routingEntry.mNfceeId == 0x00 ? STATS_HOST_OK : STATS_OFFHOST_OK; } } if ((routingEntry.mQualifier & 0x20) != 0 && entry.length < routingEntry.mEntry.length) { int ptr = 0; while (entry[ptr] == routingEntry.mEntry[ptr]) { ptr += 1; } if (ptr == entry.length) { return routingEntry.mNfceeId == 0x00 ? STATS_HOST_OK : STATS_OFFHOST_OK; } } } return STATS_NOT_FOUND; } private void addRoutingEntry(byte[] rt, int offset) { if (offset + 1 >= rt.length) return; int valueLength = Byte.toUnsignedInt(rt[offset + 1]); // Qualifier-Type(1 byte) + Length(1 byte) + Value(valueLength bytes) if (offset + 2 + valueLength > rt.length) return; byte qualifier = (byte) (rt[offset] & 0xF0); byte type = (byte) (rt[offset] & 0x0F); byte eeId = rt[offset + 2]; byte pwrState = rt[offset + 3]; byte[] entry = new byte[valueLength - 2]; for (int i = 0; i < valueLength - 2; i++) { entry[i] = rt[offset + 4 + i]; } if (type == TYPE_SYSTEMCODE && (entry.length & 1) == 0 && entry.length <= 64) { for (int i = 0; i < entry.length; i += 2) { byte[] sc_entry = {entry[i], entry[i + 1]}; sRoutingTable.add(new RoutingEntryInfo(qualifier, type, eeId, pwrState, sc_entry)); } } else if (validateEntryInfo(type, entry)) { sRoutingTable.add(new RoutingEntryInfo(qualifier, type, eeId, pwrState, entry)); } } /** * Parse the raw data of routing table */ public void parse(byte[] rt) { int offset = 0; logRoutingTableRawData(rt); sRoutingTable.clear(); while (offset < rt.length) { byte type = (byte) (rt[offset] & 0x0F); if (type >= TYPE_UNSUPPORTED) { // Unrecognizable entry type Log.e(TAG, String.format("parse: Unrecognizable entry type=0x%02X, stop parsing", type)); return; } if (offset + 1 >= rt.length) { // Buffer overflow Log.e(TAG, String.format("parse: Wrong tlv length, stop parsing")); return; } // Qualifier-Type(1 byte) + Length(1 byte) + Value(valueLength bytes) int tlvLength = Byte.toUnsignedInt(rt[offset + 1]) + 2; addRoutingEntry(rt, offset); offset += tlvLength; } } /** * Get Routing Table from the last backup lmrt cmd and parse it */ public void update(DeviceHost dh) { sRoutingTableMaxSize = dh.getMaxRoutingTableSize(); byte[] rt = dh.getRoutingTable(); sRoutingTableSize = rt.length; parse(rt); } /** * Get Routing Table from the last backup lmrt cmd and dump it */ public void dump(DeviceHost dh, PrintWriter pw) { update(dh); pw.println("--- dumpRoutingTable: start ---"); pw.println(String.format(Locale.US, "RoutingTableSize=%d/%d", sRoutingTableSize, sRoutingTableMaxSize)); pw.println(formatRow("Entry", "NFCEE_ID", "Power State", "Block Ctrl", "Extra Info")); for (RoutingEntryInfo routingEntry : sRoutingTable) { routingEntry.dump(pw); } pw.println("--- dumpRoutingTable: end ---"); } private void logRoutingTableRawData(byte[] lmrt_cmd) { if (!DBG) return; String lmrt_str = ""; for (byte b : lmrt_cmd) { lmrt_str += String.format("%02X ", b); } Log.i(TAG, String.format("logRoutingTableRawData: RoutingTableSize=%d", lmrt_cmd.length)); Log.i(TAG, String.format("logRoutingTableRawData: RoutingTable=%s", lmrt_str)); } public List getRoutingTableEntryList(DeviceHost dh) { update(dh); List entries = new ArrayList<>(); for (RoutingEntryInfo info : sRoutingTable) { String entry = switch (info.mType) { case TYPE_TECHNOLOGY -> getTechStr(info.mEntry); case TYPE_PROTOCOL -> getProtoStr(info.mEntry); case TYPE_AID -> getAidStr(info.mEntry); case TYPE_SYSTEMCODE -> new String(info.mEntry, StandardCharsets.UTF_8); default -> null; }; entries.add(new Entry(entry, info.mType, info.mNfceeId, RoutingOptionManager.getInstance().getSecureElementForRoute(info.mNfceeId))); } return entries; } }