You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
11 KiB
323 lines
11 KiB
/*
|
|
* 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.sysprop.NfcProperties;
|
|
import android.util.Log;
|
|
|
|
import androidx.annotation.VisibleForTesting;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.util.Arrays;
|
|
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(false);
|
|
private static final String TAG = "RoutingTableParser";
|
|
private static int sRoutingTableSize = 0;
|
|
private static int sRoutingTableMaxSize = 0;
|
|
private static 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));
|
|
}
|
|
}
|