BpfCoordinator: Add an option to choose which raw bpf map to dump

Make that tethering dumpsys can choose which map to dump.
The related functions in BpfCoordinator and its test are refactored
into template functions. This is used in the following commits which
will dump stats map for testing.

Bug: 227159997
Test: atest TetheringCoverageTests

Change-Id: I23d747543d16a971773c132b60227eb3f42efe25
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index bba0a40..60949a2 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -65,6 +65,7 @@
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.Struct;
 import com.android.net.module.util.Struct.U32;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
@@ -120,6 +121,7 @@
     private static final String TETHER_LIMIT_MAP_PATH = makeMapPath("limit");
     private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
     private static final String TETHER_DEV_MAP_PATH = makeMapPath("dev");
+    private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
 
     // Using "," as a separator is safe because base64 characters are [0-9a-zA-Z/=+].
     private static final String DUMP_BASE64_DELIMITER = ",";
@@ -1074,7 +1076,8 @@
         }
     }
 
-    private String ipv4RuleToBase64String(Tether4Key key, Tether4Value value) {
+    private <K extends Struct, V extends Struct> String bpfMapEntryToBase64String(
+            final K key, final V value) {
         final byte[] keyBytes = key.writeToBytes();
         final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT)
                 .replace("\n", "");
@@ -1095,18 +1098,27 @@
             pw.println("No rules");
             return;
         }
-        map.forEach((k, v) -> pw.println(ipv4RuleToBase64String(k, v)));
+        map.forEach((k, v) -> pw.println(bpfMapEntryToBase64String(k, v)));
     }
 
     /**
      * Dump raw BPF map in base64 encoded strings. For test only.
+     * Only allow to dump one map path once.
+     * Format:
+     * $ dumpsys tethering bpfRawMap --<map name>
      */
-    public void dumpRawMap(@NonNull IndentingPrintWriter pw) {
-        try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
-            // TODO: dump downstream map.
-            dumpRawIpv4ForwardingRuleMap(upstreamMap, pw);
-        } catch (ErrnoException e) {
-            pw.println("Error dumping IPv4 map: " + e);
+    public void dumpRawMap(@NonNull IndentingPrintWriter pw, @Nullable String[] args) {
+        // TODO: consider checking the arg order that <map name> is after "bpfRawMap". Probably
+        // it is okay for now because this is used by test only and test is supposed to use
+        // expected argument order.
+        // TODO: dump downstream4 map.
+        if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
+            try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
+                dumpRawIpv4ForwardingRuleMap(upstreamMap, pw);
+            } catch (ErrnoException e) {
+                pw.println("Error dumping IPv4 map: " + e);
+            }
+            return;
         }
     }
 
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 1703ecf..491e0dc 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2513,9 +2513,8 @@
                 writer, "  ");
 
         // Used for testing instead of human debug.
-        // TODO: add options to choose which map to dump.
         if (argsContain(args, "bpfRawMap")) {
-            mBpfCoordinator.dumpRawMap(pw);
+            mBpfCoordinator.dumpRawMap(pw, args);
             return;
         }
 
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index cdab805..1d71557 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -136,6 +136,7 @@
             ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
 
     private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
+    private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
     private static final String BASE64_DELIMITER = ",";
     private static final String LINE_DELIMITER = "\\n";
 
@@ -956,7 +957,8 @@
                 return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD3);
             });
 
-            final HashMap<Tether4Key, Tether4Value> upstreamMap = pollIpv4UpstreamMapFromDump();
+            final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
+                    Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
             assertNotNull(upstreamMap);
             assertEquals(1, upstreamMap.size());
 
@@ -1021,7 +1023,8 @@
     }
 
     @Nullable
-    private Pair<Tether4Key, Tether4Value> parseTether4KeyValue(@NonNull String dumpStr) {
+    private <K extends Struct, V extends Struct> Pair<K, V> parseMapKeyValue(
+            Class<K> keyClass, Class<V> valueClass, @NonNull String dumpStr) {
         Log.w(TAG, "Parsing string: " + dumpStr);
 
         String[] keyValueStrs = dumpStr.split(BASE64_DELIMITER);
@@ -1034,36 +1037,38 @@
         Log.d(TAG, "keyBytes: " + dumpHexString(keyBytes));
         final ByteBuffer keyByteBuffer = ByteBuffer.wrap(keyBytes);
         keyByteBuffer.order(ByteOrder.nativeOrder());
-        final Tether4Key tether4Key = Struct.parse(Tether4Key.class, keyByteBuffer);
-        Log.w(TAG, "tether4Key: " + tether4Key);
+        final K k = Struct.parse(keyClass, keyByteBuffer);
 
         final byte[] valueBytes = Base64.decode(keyValueStrs[1], Base64.DEFAULT);
         Log.d(TAG, "valueBytes: " + dumpHexString(valueBytes));
         final ByteBuffer valueByteBuffer = ByteBuffer.wrap(valueBytes);
         valueByteBuffer.order(ByteOrder.nativeOrder());
-        final Tether4Value tether4Value = Struct.parse(Tether4Value.class, valueByteBuffer);
-        Log.w(TAG, "tether4Value: " + tether4Value);
+        final V v = Struct.parse(valueClass, valueByteBuffer);
 
-        return new Pair<>(tether4Key, tether4Value);
+        return new Pair<>(k, v);
     }
 
     @NonNull
-    private HashMap<Tether4Key, Tether4Value> dumpIpv4UpstreamMap() throws Exception {
-        final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE,
-                DUMPSYS_TETHERING_RAWMAP_ARG);
-        final HashMap<Tether4Key, Tether4Value> map = new HashMap<>();
+    private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
+            Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+            throws Exception {
+        final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
+        final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args);
+        final HashMap<K, V> map = new HashMap<>();
 
         for (final String line : rawMapStr.split(LINE_DELIMITER)) {
-            final Pair<Tether4Key, Tether4Value> rule = parseTether4KeyValue(line.trim());
+            final Pair<K, V> rule = parseMapKeyValue(keyClass, valueClass, line.trim());
             map.put(rule.first, rule.second);
         }
         return map;
     }
 
     @Nullable
-    private HashMap<Tether4Key, Tether4Value> pollIpv4UpstreamMapFromDump() throws Exception {
+    private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
+            Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+            throws Exception {
         for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
-            final HashMap<Tether4Key, Tether4Value> map = dumpIpv4UpstreamMap();
+            final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
             if (!map.isEmpty()) return map;
 
             Thread.sleep(DUMP_POLLING_INTERVAL_MS);