Merge changes from topic "cookie_tag_map_raw_dump"

* changes:
  Add option to dump cookieTagMap in base64 format
  Use BpfDump#fromBase64EncodedString
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index c9b3686..6014722 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -30,7 +30,6 @@
 import static android.system.OsConstants.IPPROTO_IPV6;
 import static android.system.OsConstants.IPPROTO_UDP;
 
-import static com.android.net.module.util.BpfDump.BASE64_DELIMITER;
 import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
 import static com.android.net.module.util.HexDump.dumpHexString;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
@@ -65,8 +64,6 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.VintfRuntimeInfo;
-import android.text.TextUtils;
-import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
 
@@ -76,6 +73,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.net.module.util.BpfDump;
 import com.android.net.module.util.Ipv6Utils;
 import com.android.net.module.util.PacketBuilder;
 import com.android.net.module.util.Struct;
@@ -106,7 +104,6 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
@@ -1273,32 +1270,6 @@
         runUdp4Test(true /* verifyBpf */);
     }
 
-    @Nullable
-    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);
-        if (keyValueStrs.length != 2 /* key + value */) {
-            fail("The length is " + keyValueStrs.length + " but expect 2. "
-                    + "Split string(s): " + TextUtils.join(",", keyValueStrs));
-        }
-
-        final byte[] keyBytes = Base64.decode(keyValueStrs[0], Base64.DEFAULT);
-        Log.d(TAG, "keyBytes: " + dumpHexString(keyBytes));
-        final ByteBuffer keyByteBuffer = ByteBuffer.wrap(keyBytes);
-        keyByteBuffer.order(ByteOrder.nativeOrder());
-        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 V v = Struct.parse(valueClass, valueByteBuffer);
-
-        return new Pair<>(k, v);
-    }
-
     @NonNull
     private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
             Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
@@ -1309,7 +1280,8 @@
         final HashMap<K, V> map = new HashMap<>();
 
         for (final String line : rawMapStr.split(LINE_DELIMITER)) {
-            final Pair<K, V> rule = parseMapKeyValue(keyClass, valueClass, line.trim());
+            final Pair<K, V> rule =
+                    BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
             map.put(rule.first, rule.second);
         }
         return map;
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 96c615b..c4ffdec 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -154,6 +154,7 @@
 import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
 import com.android.net.module.util.BestClock;
 import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.BpfDump;
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.DeviceConfigUtils;
@@ -2532,6 +2533,7 @@
         // usage: dumpsys netstats --full --uid --tag --poll --checkin
         final boolean poll = argSet.contains("--poll") || argSet.contains("poll");
         final boolean checkin = argSet.contains("--checkin");
+        final boolean bpfRawMap = argSet.contains("--bpfRawMap");
         final boolean fullHistory = argSet.contains("--full") || argSet.contains("full");
         final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail");
         final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail");
@@ -2573,6 +2575,11 @@
                 return;
             }
 
+            if (bpfRawMap) {
+                dumpRawMapLocked(pw, args);
+                return;
+            }
+
             pw.println("Directory:");
             pw.increaseIndent();
             pw.println(mStatsDir);
@@ -2743,6 +2750,38 @@
         proto.flush();
     }
 
+    private <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map,
+            IndentingPrintWriter pw) throws ErrnoException {
+        if (map == null) {
+            pw.println("Map is null");
+            return;
+        }
+        if (map.isEmpty()) {
+            pw.println("No entries");
+            return;
+        }
+        // If there is a concurrent entry deletion, value could be null. http://b/220084230.
+        // Also, map.forEach could restart iteration from the beginning and dump could contain
+        // duplicated entries. User of this dump needs to take care of the duplicated entries.
+        map.forEach((k, v) -> {
+            if (v != null) {
+                pw.println(BpfDump.toBase64EncodedString(k, v));
+            }
+        });
+    }
+
+    @GuardedBy("mStatsLock")
+    private void dumpRawMapLocked(final IndentingPrintWriter pw, final String[] args) {
+        if (CollectionUtils.contains(args, "--cookieTagMap")) {
+            try {
+                dumpRawMap(mCookieTagMap, pw);
+            } catch (ErrnoException e) {
+                pw.println("Error dumping cookieTag map: " + e);
+            }
+            return;
+        }
+    }
+
     private static void dumpInterfaces(ProtoOutputStream proto, long tag,
             ArrayMap<String, NetworkIdentitySet> ifaces) {
         for (int i = 0; i < ifaces.size(); i++) {
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index f64e35b..153f121 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -122,6 +122,7 @@
 import android.system.ErrnoException;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
+import android.util.Pair;
 
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
@@ -130,8 +131,10 @@
 import com.android.connectivity.resources.R;
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.net.module.util.BpfDump;
 import com.android.net.module.util.IBpfMap;
 import com.android.net.module.util.LocationPermissionChecker;
+import com.android.net.module.util.Struct;
 import com.android.net.module.util.Struct.U32;
 import com.android.net.module.util.Struct.U8;
 import com.android.net.module.util.bpf.CookieTagMapKey;
@@ -168,6 +171,7 @@
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoUnit;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -212,6 +216,11 @@
     private static final long WAIT_TIMEOUT = 2 * 1000;  // 2 secs
     private static final int INVALID_TYPE = -1;
 
+    private static final String DUMPSYS_BPF_RAW_MAP = "--bpfRawMap";
+    private static final String DUMPSYS_COOKIE_TAG_MAP = "--cookieTagMap";
+    private static final String LINE_DELIMITER = "\\n";
+
+
     private long mElapsedRealtime;
 
     private File mStatsDir;
@@ -2333,12 +2342,27 @@
                 dump.contains(message));
     }
 
-    private String getDump() {
+    private String getDump(final String[] args) {
         final StringWriter sw = new StringWriter();
-        mService.dump(new FileDescriptor(), new PrintWriter(sw), new String[]{});
+        mService.dump(new FileDescriptor(), new PrintWriter(sw), args);
         return sw.toString();
     }
 
+    private String getDump() {
+        return getDump(new String[]{});
+    }
+
+    private <K extends Struct, V extends Struct> Map<K, V> parseBpfRawMap(
+            Class<K> keyClass, Class<V> valueClass, String dumpStr) {
+        final HashMap<K, V> map = new HashMap<>();
+        for (final String line : dumpStr.split(LINE_DELIMITER)) {
+            final Pair<K, V> keyValue =
+                    BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
+            map.put(keyValue.first, keyValue.second);
+        }
+        return map;
+    }
+
     @Test
     public void testDumpCookieTagMap() throws ErrnoException {
         initBpfMapsWithTagData(UID_BLUE);
@@ -2350,6 +2374,23 @@
     }
 
     @Test
+    public void testDumpCookieTagMapBpfRawMap() throws ErrnoException {
+        initBpfMapsWithTagData(UID_BLUE);
+
+        final String dump = getDump(new String[]{DUMPSYS_BPF_RAW_MAP, DUMPSYS_COOKIE_TAG_MAP});
+        Map<CookieTagMapKey, CookieTagMapValue> cookieTagMap = parseBpfRawMap(
+                CookieTagMapKey.class, CookieTagMapValue.class, dump);
+
+        final CookieTagMapValue val1 = cookieTagMap.get(new CookieTagMapKey(2002));
+        assertEquals(1, val1.tag);
+        assertEquals(1002, val1.uid);
+
+        final CookieTagMapValue val2 = cookieTagMap.get(new CookieTagMapKey(3002));
+        assertEquals(2, val2.tag);
+        assertEquals(1002, val2.uid);
+    }
+
+    @Test
     public void testDumpUidCounterSetMap() throws ErrnoException {
         initBpfMapsWithTagData(UID_BLUE);