Turn TestBpfMap into a common utility class.

- Move it to frameworks/libs/net/common/testutils.
- Inherit from BpfMap, so the class can be used anywhere that
  uses a real BpfMap. For example, BpfCoordinatorTest uses
  BpfMap, not IBpfMap.

Test: atest BpfCoordinatorTest
Change-Id: I7ee65e47fa46ac6aafc831f5b1823b44267ba2a6
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 2dda269..1a1328f 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -28,6 +28,7 @@
     ],
     libs: [
         "androidx.annotation_annotation",
+        "net-utils-device-common-bpf",  // TestBpfMap extends IBpfMap.
     ],
     static_libs: [
         "androidx.test.ext.junit",
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
new file mode 100644
index 0000000..5614a99
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 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.testutils;
+
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.Struct;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+
+/**
+ *
+ * Fake BPF map class for tests that have no no privilege to access real BPF maps. All member
+ * functions which eventually call JNI to access the real native BPF map are overridden.
+ *
+ * Inherits from BpfMap instead of implementing IBpfMap so that any class using a BpfMap can use
+ * this class in its tests.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ */
+public class TestBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
+    private final HashMap<K, V> mMap = new HashMap<K, V>();
+
+    public TestBpfMap(final Class<K> key, final Class<V> value) {
+        super(key, value);
+    }
+
+    @Override
+    public void forEach(BiConsumer<K, V> action) throws ErrnoException {
+        // TODO: consider using mocked #getFirstKey and #getNextKey to iterate. It helps to
+        // implement the entry deletion in the iteration if required.
+        for (Map.Entry<K, V> entry : mMap.entrySet()) {
+            action.accept(entry.getKey(), entry.getValue());
+        }
+    }
+
+    @Override
+    public void updateEntry(K key, V value) throws ErrnoException {
+        mMap.put(key, value);
+    }
+
+    @Override
+    public void insertEntry(K key, V value) throws ErrnoException,
+            IllegalArgumentException {
+        // The entry is created if and only if it doesn't exist. See BpfMap#insertEntry.
+        if (mMap.get(key) != null) {
+            throw new IllegalArgumentException(key + " already exist");
+        }
+        mMap.put(key, value);
+    }
+
+    @Override
+    public void replaceEntry(K key, V value) throws ErrnoException, NoSuchElementException {
+        if (!mMap.containsKey(key)) throw new NoSuchElementException();
+        mMap.put(key, value);
+    }
+
+    @Override
+    public boolean insertOrReplaceEntry(K key, V value) throws ErrnoException {
+        // Returns true if inserted, false if replaced.
+        boolean ret = !mMap.containsKey(key);
+        mMap.put(key, value);
+        return ret;
+    }
+
+    @Override
+    public boolean deleteEntry(Struct key) throws ErrnoException {
+        return mMap.remove(key) != null;
+    }
+
+    @Override
+    public boolean isEmpty() throws ErrnoException {
+        return mMap.isEmpty();
+    }
+
+    @Override
+    public K getNextKey(@NonNull K key) {
+        // Expensive, but since this is only for tests...
+        Iterator<K> it = mMap.keySet().iterator();
+        while (it.hasNext()) {
+            if (Objects.equals(it.next(), key)) {
+                return it.hasNext() ? it.next() : null;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public K getFirstKey() {
+        for (K key : mMap.keySet()) {
+            return key;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean containsKey(@NonNull K key) throws ErrnoException {
+        return mMap.containsKey(key);
+    }
+
+    @Override
+    public V getValue(@NonNull K key) throws ErrnoException {
+        // Return value for a given key. Otherwise, return null without an error ENOENT.
+        // BpfMap#getValue treats that the entry is not found as no error.
+        return mMap.get(key);
+    }
+
+    @Override
+    public void clear() throws ErrnoException {
+        // TODO: consider using mocked #getFirstKey and #deleteEntry to implement.
+        mMap.clear();
+    }
+}