| /* |
| * 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<RoutingEntryInfo> sRoutingTable = new Vector<RoutingEntryInfo>(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("Unrecognizable entry type: 0x%02X, stop parsing", type)); |
| return; |
| } |
| if (offset + 1 >= rt.length) { |
| // Buffer overflow |
| Log.e(TAG, String.format("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("RoutingTableSize: %d", lmrt_cmd.length)); |
| Log.i(TAG, String.format("RoutingTable: %s", lmrt_str)); |
| } |
| |
| public List<Entry> getRoutingTableEntryList(DeviceHost dh) { |
| update(dh); |
| List<Entry> 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; |
| } |
| } |