Add BpfDump#dumpMap

Bug: 217624062
Test: atest NetworkStaticLibTests
Change-Id: I7b0a949e667da4120cc20d8587019bfb0f922584
diff --git a/staticlibs/device/com/android/net/module/util/BpfDump.java b/staticlibs/device/com/android/net/module/util/BpfDump.java
index 67e9c93..2534d61 100644
--- a/staticlibs/device/com/android/net/module/util/BpfDump.java
+++ b/staticlibs/device/com/android/net/module/util/BpfDump.java
@@ -15,13 +15,17 @@
  */
 package com.android.net.module.util;
 
+import android.system.ErrnoException;
+import android.system.Os;
 import android.util.Base64;
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
 
+import java.io.PrintWriter;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.function.BiFunction;
 
 /**
  * The classes and the methods for BPF dump utilization.
@@ -74,6 +78,38 @@
         return new Pair<>(k, v);
     }
 
+    /**
+     * Dump the BpfMap name and entries
+     */
+    public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
+            PrintWriter pw, String mapName, BiFunction<K, V, String> entryToString) {
+        dumpMap(map, pw, mapName, "" /* header */, entryToString);
+    }
+
+    /**
+     * Dump the BpfMap name, header, and entries
+     */
+    public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
+            PrintWriter pw, String mapName, String header, BiFunction<K, V, String> entryToString) {
+        pw.println(mapName + ":");
+        if (!header.isEmpty()) {
+            pw.println("  " + header);
+        }
+        try {
+            map.forEach((key, value) -> {
+                // Value could be null if there is a concurrent entry deletion.
+                // http://b/220084230.
+                if (value != null) {
+                    pw.println("  " + entryToString.apply(key, value));
+                } else {
+                    pw.println("Entry is deleted while dumping, iterating from first entry");
+                }
+            });
+        } catch (ErrnoException e) {
+            pw.println("Map dump end with error: " + Os.strerror(e.errno));
+        }
+    }
+
     // TODO: add a helper to dump bpf map content with the map name, the header line
     // (ex: "BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif"), a lambda that
     // knows how to dump each line, and the PrintWriter.
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
index 395011c..3932925 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
@@ -18,15 +18,21 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.TestBpfMap;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BpfDumpTest {
@@ -79,4 +85,37 @@
         assertThrowsIllegalArgumentException(
                 TEST_KEY_VAL_BASE64 + BASE64_DELIMITER + TEST_KEY_BASE64);
     }
+
+    private String getDumpMap(final IBpfMap<Struct.U32, Struct.U32> map) {
+        final StringWriter sw = new StringWriter();
+        BpfDump.dumpMap(map, new PrintWriter(sw), "mapName", "header",
+                (key, val) -> "key=" + key.val + ", val=" + val.val);
+        return sw.toString();
+    }
+
+    @Test
+    public void testDumpMap() throws Exception {
+        final IBpfMap<Struct.U32, Struct.U32> map =
+                new TestBpfMap<>(Struct.U32.class, Struct.U32.class);
+        map.updateEntry(new Struct.U32(123), new Struct.U32(456));
+
+        final String dump = getDumpMap(map);
+        assertEquals(dump, "mapName:\n"
+                + "  header\n"
+                + "  key=123, val=456\n");
+    }
+
+    @Test
+    public void testDumpMapMultipleEntries() throws Exception {
+        final IBpfMap<Struct.U32, Struct.U32> map =
+                new TestBpfMap<>(Struct.U32.class, Struct.U32.class);
+        map.updateEntry(new Struct.U32(123), new Struct.U32(456));
+        map.updateEntry(new Struct.U32(789), new Struct.U32(123));
+
+        final String dump = getDumpMap(map);
+        assertTrue(dump.contains("mapName:"));
+        assertTrue(dump.contains("header"));
+        assertTrue(dump.contains("key=123, val=456"));
+        assertTrue(dump.contains("key=789, val=123"));
+    }
 }