diff options
-rw-r--r-- | src/com/android/nfc/RoutingTableParser.java | 284 | ||||
-rw-r--r-- | tests/unit/src/com/android/nfc/NfcRoutingTableParseTest.java | 267 |
2 files changed, 472 insertions, 79 deletions
diff --git a/src/com/android/nfc/RoutingTableParser.java b/src/com/android/nfc/RoutingTableParser.java index d2c7f1ad..9174c244 100644 --- a/src/com/android/nfc/RoutingTableParser.java +++ b/src/com/android/nfc/RoutingTableParser.java @@ -17,10 +17,12 @@ package com.android.nfc; import android.os.SystemProperties; -import java.util.Locale; import android.util.Log; 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 @@ -28,57 +30,92 @@ import java.io.PrintWriter; public class RoutingTableParser { static final boolean DBG = SystemProperties.getBoolean("persist.nfc.debug_enabled", 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 String getTechStr(int value) { + 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 (value > tech_mask_list.length) { + if (tech[0] > tech_mask_list.length) { return "UNSUPPORTED_TECH"; } - return tech_mask_list[value]; + return tech_mask_list[tech[0]]; } - private String getProtoStr(int value) { + 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 (value > proto_mask_list.length) { + if (proto[0] > proto_mask_list.length) { return "UNSUPPORTED_PROTO"; } - return proto_mask_list[value]; + return proto_mask_list[proto[0]]; } - private String getAidStr(byte[] rt, int offset, int aidLen) { - String Aid = ""; - for (int i = 0; i < aidLen; i++) { - Aid += String.format("%02X", rt[offset + i]); + private String getAidStr(byte[] aid) { + String aidStr = ""; + + for (byte b : aid) { + aidStr += String.format("%02X", b); } - if (Aid.length() == 0) { + if (aidStr.length() == 0) { return "Empty_AID"; } - return "AID_" + Aid; + return "AID_" + aidStr; } - private String getSystemCodeStr(byte[] rt, int offset, int scLen) { - String SystemCode = ""; - for (int i = 0; i < scLen; i++) { - SystemCode += String.format("%02X", rt[offset + i]); + private String getSystemCodeStr(byte[] sc) { + String systemCodeStr = ""; + for (byte b : sc) { + systemCodeStr += String.format("%02X", b); } - return "SYSTEMCODE_" + SystemCode; + return "SYSTEMCODE_" + systemCodeStr; } - private String getBlockCtrlStr(int mask) { + private String getBlockCtrlStr(byte mask) { if ((mask & 0x40) != 0) { return "True"; } return "False"; } - private String getPrefixSubsetStr(int mask) { + private String getPrefixSubsetStr(byte mask, byte type) { + if (type != TYPE_AID) { + return ""; + } + String prefix_subset_str = ""; if ((mask & 0x10) != 0) { prefix_subset_str += "Prefix "; @@ -98,84 +135,172 @@ public class RoutingTableParser { return String.format(fmt, entry, eeId, pwrState, blkCtrl, extra); } - private void dumpTechEntry(byte[] rt, int rtSize, PrintWriter pw, int offset) { - if (offset + 4 >= rtSize) return; + 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; + } - String blkCtrl = getBlockCtrlStr(rt[offset] & 0xF0); - String eeId = String.format("0x%02X", rt[offset + 2]); - String pwrState = String.format("0x%02X", rt[offset + 3]); - String entry = getTechStr(rt[offset + 4]); + 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, "")); + pw.println(formatRow(entry, eeId, pwrState, blkCtrl, extra)); + } } - private void dumpProtoEntry(byte[] rt, int rtSize, PrintWriter pw, int offset) { - if (offset + 4 >= rtSize) return; + 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; + } - String blkCtrl = getBlockCtrlStr(rt[offset] & 0xF0); - String eeId = String.format("0x%02X", rt[offset + 2]); - String pwrState = String.format("0x%02X", rt[offset + 3]); - String entry = getProtoStr(rt[offset + 4]); + /** + * Check commit status by inputting type and entry + */ + public int getCommitStatus(byte type, byte[] entry) { + if (!validateEntryInfo(type, entry)) return STATS_NOT_FOUND; - pw.println(formatRow(entry, eeId, pwrState, blkCtrl, "")); + 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 dumpAidEntry(byte[] rt, int rtSize, PrintWriter pw, int offset) { - if (offset + 4 + rt[offset + 1] - 2 >= rtSize) return; + private void addRoutingEntry(byte[] rt, int offset) { + if (offset + 1 >= rt.length) return; + int valueLength = rt[offset + 1]; + + // Qualifier-Type(1 byte) + Length(1 byte) + Value(valueLength bytes) + if (offset + 2 + valueLength > rt.length) return; - String blkCtrl = getBlockCtrlStr(rt[offset] & 0xF0); - String extra = getPrefixSubsetStr(rt[offset] & 0xF0); - String eeId = String.format("0x%02X", rt[offset + 2]); - String pwrState = String.format("0x%02X", rt[offset + 3]); - String entry = getAidStr(rt, offset + 4, rt[offset + 1] - 2); + 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]; + } - pw.println(formatRow(entry, eeId, pwrState, blkCtrl, extra)); + 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)); + } } - private void dumpSystemEntry(byte[] rt, int rtSize, PrintWriter pw, int offset) { - if (offset + 4 + rt[offset + 1] - 2 >= rtSize) return; + /** + * 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 = rt[offset + 1] + 2; - String blkCtrl = getBlockCtrlStr(rt[offset] & 0xF0); - String eeId = String.format("0x%02X", rt[offset + 2]); - String pwrState = String.format("0x%02X", rt[offset + 3]); - String entry = getSystemCodeStr(rt, offset + 4, rt[offset + 1] - 2); + addRoutingEntry(rt, offset); - pw.println(formatRow(entry, eeId, pwrState, blkCtrl, "")); + offset += tlvLength; + } } /** - * Get Routing Table from the last backup lmrt cmd and dump it + * Get Routing Table from the last backup lmrt cmd and parse it */ - public void dump(DeviceHost dh, PrintWriter pw) { - int offset = 0; + public void update(DeviceHost dh) { + sRoutingTableMaxSize = dh.getMaxRoutingTableSize(); byte[] rt = dh.getRoutingTable(); - int maxSize = dh.getMaxRoutingTableSize(); + sRoutingTableSize = rt.length; + parse(rt); + } - logRoutingTableRawData(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", rt.length, maxSize)); + pw.println(String.format(Locale.US, "RoutingTableSize: %d/%d", + sRoutingTableSize, sRoutingTableMaxSize)); pw.println(formatRow("Entry", "NFCEE_ID", "Power State", "Block Ctrl", "Extra Info")); - while (offset < rt.length) { - int type = rt[offset] & 0x0F; - if (type == 0x00) { - // Technology-based routing entry - dumpTechEntry(rt, rt.length, pw, offset); - } else if (type == 0x01) { - // Protocol-based routing entry - dumpProtoEntry(rt, rt.length, pw, offset); - } else if (type == 0x02) { - // AID-based routing entry - dumpAidEntry(rt, rt.length, pw, offset); - } else if (type == 0x03) { - // System Code-based routing entry - dumpSystemEntry(rt, rt.length, pw, offset); - } else { - // Unrecognizable entry type - Log.d(TAG, String.format("Unrecognizable entry type: 0x%02X, stop parsing", type)); - break; - } - offset += rt[offset+1] + 2; + + for (RoutingEntryInfo routingEntry : sRoutingTable) { + routingEntry.dump(pw); } pw.println("--- dumpRoutingTable: end ---"); @@ -184,10 +309,11 @@ public class RoutingTableParser { private void logRoutingTableRawData(byte[] lmrt_cmd) { if (!DBG) return; String lmrt_str = ""; - for (int i = 0; i < lmrt_cmd.length; i++) { - lmrt_str += String.format("%02X ", lmrt_cmd[i]); + + for (byte b : lmrt_cmd) { + lmrt_str += String.format("%02X ", b); } - Log.d(TAG, String.format("RoutingTableSize: %d", lmrt_cmd.length)); - Log.d(TAG, String.format("RoutingTable: %s", lmrt_str)); + Log.i(TAG, String.format("RoutingTableSize: %d", lmrt_cmd.length)); + Log.i(TAG, String.format("RoutingTable: %s", lmrt_str)); } } diff --git a/tests/unit/src/com/android/nfc/NfcRoutingTableParseTest.java b/tests/unit/src/com/android/nfc/NfcRoutingTableParseTest.java new file mode 100644 index 00000000..d435fde0 --- /dev/null +++ b/tests/unit/src/com/android/nfc/NfcRoutingTableParseTest.java @@ -0,0 +1,267 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public final class NfcRoutingTableParseTest { + private static final String TAG = NfcRoutingTableParseTest.class.getSimpleName(); + private RoutingTableParser mRoutingTableParser; + + // NFCEE-ID + static final byte EE_ID_HOST = (byte) 0x00; + static final byte EE_ID_UICC = (byte) 0x81; + static final byte EE_ID_ESE = (byte) 0x86; + + // Power State Mask + static final byte APPLY_ALL = (byte) 0x3F; + static final byte SWITCH_ON_SUB_3 = (byte) 0x20; + static final byte SWITCH_ON_SUB_2 = (byte) 0x10; + static final byte SWITCH_ON_SUB_1 = (byte) 0x08; + static final byte BATTERY_OFF = (byte) 0x04; + static final byte SWITCH_OFF = (byte) 0x02; + static final byte SWITCH_ON = (byte) 0x01; + + @Before + public void setUp() { + mRoutingTableParser = new RoutingTableParser(); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testParseValidTechnologyEntry() { + /** + * set qualifier = 0x00 to indicates the routing is allowed for the power modes + * where it is not supported + */ + byte qualifier = (byte) 0x00; + byte type = RoutingTableParser.TYPE_TECHNOLOGY; + byte eeId = EE_ID_HOST; + byte pwrState = (byte) (SWITCH_ON_SUB_3 | SWITCH_ON_SUB_2 | SWITCH_ON_SUB_1 | SWITCH_ON); + byte[] entry = hexStrToByteArray("01"); + byte[] rt = generateRoutingEntry(qualifier, type, eeId, pwrState, entry); + mRoutingTableParser.parse(rt); + + int ret = mRoutingTableParser.getCommitStatus(type, entry); + + assertThat(ret).isEqualTo(mRoutingTableParser.STATS_HOST_OK); + } + + @Test + public void testParseInvalidTechnologyEntry() { + /** + * set qualifier = 0x00 to indicates the routing is allowed for the power modes + * where it is not supported + */ + byte qualifier = (byte) 0x00; + byte type = RoutingTableParser.TYPE_TECHNOLOGY; + byte eeId = EE_ID_HOST; + byte pwrState = (byte) (SWITCH_ON_SUB_3 | SWITCH_ON_SUB_2 | SWITCH_ON_SUB_1 | SWITCH_ON); + byte[] entry = hexStrToByteArray("0001"); + byte[] rt = generateRoutingEntry(qualifier, type, eeId, pwrState, entry); + mRoutingTableParser.parse(rt); + + int ret = mRoutingTableParser.getCommitStatus(type, entry); + + assertThat(ret).isEqualTo(mRoutingTableParser.STATS_NOT_FOUND); + } + + @Test + public void testParseValidProtocolEntry() { + /** + * set qualifier = 0x00 to indicates the routing is allowed for the power modes + * where it is not supported + */ + byte qualifier = (byte) 0x00; + byte type = RoutingTableParser.TYPE_PROTOCOL; + byte eeId = EE_ID_HOST; + byte pwrState = SWITCH_ON; + byte[] entry = hexStrToByteArray("04"); + byte[] rt = generateRoutingEntry(qualifier, type, eeId, pwrState, entry); + mRoutingTableParser.parse(rt); + + int ret = mRoutingTableParser.getCommitStatus(type, entry); + + assertThat(ret).isEqualTo(mRoutingTableParser.STATS_HOST_OK); + } + + @Test + public void testParseInvalidProtocolEntry() { + /** + * set qualifier = 0x00 to indicates the routing is allowed for the power modes + * where it is not supported + */ + byte qualifier = (byte) 0x00; + byte type = RoutingTableParser.TYPE_PROTOCOL; + byte eeId = EE_ID_HOST; + byte pwrState = SWITCH_ON; + byte[] entry = hexStrToByteArray("0405"); + byte[] rt = generateRoutingEntry(qualifier, type, eeId, pwrState, entry); + mRoutingTableParser.parse(rt); + + int ret = mRoutingTableParser.getCommitStatus(type, entry); + + assertThat(ret).isEqualTo(mRoutingTableParser.STATS_NOT_FOUND); + } + + @Test + public void testParseValidAidEntry() { + /** + * set qualifier = 0x40 to indicates the routing is blocked for the power modes + * where it is not supported + */ + byte qualifier = (byte) 0x40; + byte type = RoutingTableParser.TYPE_AID; + byte eeId = EE_ID_UICC; + byte pwrState = (byte) (APPLY_ALL ^ BATTERY_OFF); + byte[] entry = hexStrToByteArray("6E6663746573743031"); + byte[] rt = generateRoutingEntry(qualifier, type, eeId, pwrState, entry); + mRoutingTableParser.parse(rt); + + int ret = mRoutingTableParser.getCommitStatus(type, entry); + + assertThat(ret).isEqualTo(mRoutingTableParser.STATS_OFFHOST_OK); + } + + @Test + public void testParseInvalidAidEntry() { + /** + * set qualifier = 0x40 to indicates the routing is blocked for the power modes + * where it is not supported + */ + byte qualifier = (byte) 0x40; + byte type = RoutingTableParser.TYPE_AID; + byte eeId = EE_ID_UICC; + byte pwrState = (byte) (APPLY_ALL ^ BATTERY_OFF); + byte[] entry = hexStrToByteArray("6E66637465737430316E6663746573743031"); + byte[] rt = generateRoutingEntry(qualifier, type, eeId, pwrState, entry); + mRoutingTableParser.parse(rt); + + int ret = mRoutingTableParser.getCommitStatus(type, entry); + + assertThat(ret).isEqualTo(mRoutingTableParser.STATS_NOT_FOUND); + } + + @Test + public void testParseValidSystemCodeEntry() { + /** + * set qualifier = 0x40 to indicates the routing is blocked for the power modes + * where it is not supported + */ + byte qualifier = (byte) 0x40; + byte type = RoutingTableParser.TYPE_SYSTEMCODE; + byte eeId = EE_ID_ESE; + byte pwrState = (byte) (APPLY_ALL ^ BATTERY_OFF); + byte[] entry = hexStrToByteArray("FEFE"); + byte[] rt = generateRoutingEntry(qualifier, type, eeId, pwrState, entry); + mRoutingTableParser.parse(rt); + + int ret = mRoutingTableParser.getCommitStatus(type, entry); + + assertThat(ret).isEqualTo(mRoutingTableParser.STATS_OFFHOST_OK); + } + + @Test + public void testParseSeveralValidSystemCodeEntry() { + /** + * set qualifier = 0x40 to indicates the routing is blocked for the power modes + * where it is not supported + */ + byte qualifier = (byte) 0x40; + byte type = RoutingTableParser.TYPE_SYSTEMCODE; + byte eeId = EE_ID_ESE; + byte pwrState = (byte) (APPLY_ALL ^ BATTERY_OFF); + byte[] entry1 = hexStrToByteArray("FEFE"); + byte[] entry2 = hexStrToByteArray("EEEE"); + byte[] entryAll = hexStrToByteArray("FEFEEEEE"); + byte[] rt = generateRoutingEntry(qualifier, type, eeId, pwrState, entryAll); + mRoutingTableParser.parse(rt); + + int ret1 = mRoutingTableParser.getCommitStatus(type, entry1); + int ret2 = mRoutingTableParser.getCommitStatus(type, entry2); + + assertThat(ret1).isEqualTo(mRoutingTableParser.STATS_OFFHOST_OK); + assertThat(ret2).isEqualTo(mRoutingTableParser.STATS_OFFHOST_OK); + } + + @Test + public void testParseInvalidSystemCodeEntry() { + /** + * set qualifier = 0x40 to indicates the routing is blocked for the power modes + * where it is not supported + */ + byte qualifier = (byte) 0x40; + byte type = RoutingTableParser.TYPE_SYSTEMCODE; + byte eeId = EE_ID_ESE; + byte pwrState = (byte) (APPLY_ALL ^ BATTERY_OFF); + byte[] entry = hexStrToByteArray("FEFEFE"); + byte[] rt = generateRoutingEntry(qualifier, type, eeId, pwrState, entry); + mRoutingTableParser.parse(rt); + + int ret = mRoutingTableParser.getCommitStatus(type, entry); + + assertThat(ret).isEqualTo(mRoutingTableParser.STATS_NOT_FOUND); + } + + private byte[] generateRoutingEntry(byte qualifier, byte type, byte eeId, byte pwrState, + byte[] entry) { + int length = 2 + entry.length; + byte[] rt = new byte[length + 2]; + rt[0] = (byte) (qualifier | type); + rt[1] = (byte) length; + rt[2] = eeId; + rt[3] = pwrState; + + for (int i = 0; i < entry.length; i++) { + rt[i + 4] = entry[i]; + } + + return rt; + } + + private byte[] hexStrToByteArray(String hexStr) { + if (hexStr.length() % 2 != 0) { + return new byte[0]; + } + + char[] hex = hexStr.toCharArray(); + int length = hexStr.length() / 2; + byte[] byteArr = new byte[length]; + for (int i = 0; i < length; i++) { + int high = Character.digit(hex[i * 2], 16); + int low = Character.digit(hex[i * 2 + 1], 16); + int value = (high << 4) | low; + + if (value > 127) { + value -= 256; + } + byteArr [i] = (byte) value; + } + + return byteArr; + } +} |