Merge "Add mdns files and unit tests"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4eeaf51..4774866 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -171,23 +171,6 @@
       ]
     }
   ],
-  "auto-postsubmit": [
-    // Test tag for automotive targets. These are only running in postsubmit so as to harden the
-    // automotive targets to avoid introducing additional test flake and build time. The plan for
-    // presubmit testing for auto is to augment the existing tests to cover auto use cases as well.
-    // Additionally, this tag is used in targeted test suites to limit resource usage on the test
-    // infra during the hardening phase.
-    // TODO: this tag to be removed once the above is no longer an issue.
-    {
-      "name": "FrameworksNetTests"
-    },
-    {
-      "name": "FrameworksNetIntegrationTests"
-    },
-    {
-      "name": "FrameworksNetDeflakeTest"
-    }
-  ],
   "imports": [
     {
       "path": "frameworks/base/core/java/android/net"
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index fd449a3..85b9f86 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -148,6 +148,7 @@
 
 #endif // __cplusplus
 
+// LINT.IfChange(match_type)
 enum UidOwnerMatchType {
     NO_MATCH = 0,
     HAPPY_BOX_MATCH = (1 << 0),
@@ -163,6 +164,7 @@
     OEM_DENY_2_MATCH = (1 << 10),
     OEM_DENY_3_MATCH = (1 << 11),
 };
+// LINT.ThenChange(packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java)
 
 enum BpfPermissionMatch {
     BPF_PERMISSION_INTERNET = 1 << 2,
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 39cd7f3..02083ff 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -5903,6 +5903,7 @@
      *
      * @param chain target chain.
      * @param enable whether the chain should be enabled.
+     * @throws UnsupportedOperationException if called on pre-T devices.
      * @throws IllegalStateException if enabling or disabling the firewall chain failed.
      * @hide
      */
@@ -5921,6 +5922,29 @@
     }
 
     /**
+     * Get the specified firewall chain status.
+     *
+     * @param chain target chain.
+     * @return {@code true} if chain is enabled, {@code false} if chain is disabled.
+     * @throws UnsupportedOperationException if called on pre-T devices.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+    })
+    public boolean getFirewallChainEnabled(@FirewallChain final int chain) {
+        try {
+            return mService.getFirewallChainEnabled(chain);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Replaces the contents of the specified UID-based firewall chain.
      *
      * @param chain target chain to replace.
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index bc73769..29fea00 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -244,5 +244,7 @@
 
     void setFirewallChainEnabled(int chain, boolean enable);
 
+    boolean getFirewallChainEnabled(int chain);
+
     void replaceFirewallChain(int chain, in int[] uids);
 }
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index 7b18765..9cae9e6 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -236,6 +236,8 @@
     /**
      * Create a tap interface with or without carrier for testing purposes.
      *
+     * Note: setting carrierUp = false is not supported until kernel version 5.0.
+     *
      * @param carrierUp whether the created interface has a carrier or not.
      * @param bringUp whether to bring up the interface before returning it.
      * @hide
@@ -254,7 +256,6 @@
      * Enable / disable carrier on TestNetworkInterface
      *
      * Note: TUNSETCARRIER is not supported until kernel version 5.0.
-     * TODO: add RequiresApi annotation.
      *
      * @param iface the interface to configure.
      * @param enabled true to turn carrier on, false to turn carrier off.
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 2780044..49392e0 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -82,13 +82,6 @@
   return (jint)status.code();
 }
 
-static jint native_setChildChain(JNIEnv* env, jobject self, jint childChain, jboolean enable) {
-  auto chain = static_cast<ChildChain>(childChain);
-  int res = mTc.toggleUidOwnerMap(chain, enable);
-  if (res) ALOGE("%s failed, error code = %d", __func__, res);
-  return (jint)res;
-}
-
 static jint native_replaceUidChain(JNIEnv* env, jobject self, jstring name, jboolean isAllowlist,
                                    jintArray jUids) {
     const ScopedUtfChars chainNameUtf8(env, name);
@@ -199,8 +192,6 @@
     (void*)native_addNiceApp},
     {"native_removeNiceApp", "(I)I",
     (void*)native_removeNiceApp},
-    {"native_setChildChain", "(IZ)I",
-    (void*)native_setChildChain},
     {"native_replaceUidChain", "(Ljava/lang/String;Z[I)I",
     (void*)native_replaceUidChain},
     {"native_setUidRule", "(III)I",
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 4dc056d..9331548 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -451,53 +451,6 @@
     return 0;
 }
 
-int TrafficController::toggleUidOwnerMap(ChildChain chain, bool enable) {
-    std::lock_guard guard(mMutex);
-    uint32_t key = UID_RULES_CONFIGURATION_KEY;
-    auto oldConfigure = mConfigurationMap.readValue(key);
-    if (!oldConfigure.ok()) {
-        ALOGE("Cannot read the old configuration from map: %s",
-              oldConfigure.error().message().c_str());
-        return -oldConfigure.error().code();
-    }
-    uint32_t match;
-    switch (chain) {
-        case DOZABLE:
-            match = DOZABLE_MATCH;
-            break;
-        case STANDBY:
-            match = STANDBY_MATCH;
-            break;
-        case POWERSAVE:
-            match = POWERSAVE_MATCH;
-            break;
-        case RESTRICTED:
-            match = RESTRICTED_MATCH;
-            break;
-        case LOW_POWER_STANDBY:
-            match = LOW_POWER_STANDBY_MATCH;
-            break;
-        case OEM_DENY_1:
-            match = OEM_DENY_1_MATCH;
-            break;
-        case OEM_DENY_2:
-            match = OEM_DENY_2_MATCH;
-            break;
-        case OEM_DENY_3:
-            match = OEM_DENY_3_MATCH;
-            break;
-        default:
-            return -EINVAL;
-    }
-    BpfConfig newConfiguration =
-            enable ? (oldConfigure.value() | match) : (oldConfigure.value() & ~match);
-    Status res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
-    if (!isOk(res)) {
-        ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str());
-    }
-    return -res.code();
-}
-
 Status TrafficController::swapActiveStatsMap() {
     std::lock_guard guard(mMutex);
 
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index 8512929..14c5eaf 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -71,8 +71,6 @@
     netdutils::Status updateUidOwnerMap(const uint32_t uid,
                                         UidOwnerMatchType matchType, IptOp op) EXCLUDES(mMutex);
 
-    int toggleUidOwnerMap(ChildChain chain, bool enable) EXCLUDES(mMutex);
-
     static netdutils::StatusOr<std::unique_ptr<netdutils::NetlinkListenerInterface>>
     makeSkDestroyListener();
 
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 151d0e3..3ee3ea1 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -16,15 +16,30 @@
 
 package com.android.server;
 
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.system.OsConstants.EINVAL;
+import static android.system.OsConstants.ENOENT;
 import static android.system.OsConstants.EOPNOTSUPP;
 
 import android.net.INetd;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
+import android.util.SparseLongArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.Struct.U32;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -41,6 +56,56 @@
     private static final boolean USE_NETD = !SdkLevel.isAtLeastT();
     private static boolean sInitialized = false;
 
+    // Lock for sConfigurationMap entry for UID_RULES_CONFIGURATION_KEY.
+    // This entry is not accessed by others.
+    // BpfNetMaps acquires this lock while sequence of read, modify, and write.
+    private static final Object sUidRulesConfigBpfMapLock = new Object();
+
+    private static final String CONFIGURATION_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_configuration_map";
+    private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
+    private static BpfMap<U32, U32> sConfigurationMap = null;
+
+    // LINT.IfChange(match_type)
+    private static final long NO_MATCH = 0;
+    private static final long HAPPY_BOX_MATCH = (1 << 0);
+    private static final long PENALTY_BOX_MATCH = (1 << 1);
+    private static final long DOZABLE_MATCH = (1 << 2);
+    private static final long STANDBY_MATCH = (1 << 3);
+    private static final long POWERSAVE_MATCH = (1 << 4);
+    private static final long RESTRICTED_MATCH = (1 << 5);
+    private static final long LOW_POWER_STANDBY_MATCH = (1 << 6);
+    private static final long IIF_MATCH = (1 << 7);
+    private static final long LOCKDOWN_VPN_MATCH = (1 << 8);
+    private static final long OEM_DENY_1_MATCH = (1 << 9);
+    private static final long OEM_DENY_2_MATCH = (1 << 10);
+    private static final long OEM_DENY_3_MATCH = (1 << 11);
+    // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/bpf_shared.h)
+
+    // TODO: Use Java BpfMap instead of JNI code (TrafficController) for map update.
+    // Currently, BpfNetMaps uses TrafficController for map update and TrafficController
+    // (changeUidOwnerRule and toggleUidOwnerMap) also does conversion from "firewall chain" to
+    // "match". Migrating map update from JNI to Java BpfMap will solve this duplication.
+    private static final SparseLongArray FIREWALL_CHAIN_TO_MATCH = new SparseLongArray();
+    static {
+        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_DOZABLE, DOZABLE_MATCH);
+        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_STANDBY, STANDBY_MATCH);
+        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_POWERSAVE, POWERSAVE_MATCH);
+        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_RESTRICTED, RESTRICTED_MATCH);
+        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
+        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_1, OEM_DENY_1_MATCH);
+        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_2, OEM_DENY_2_MATCH);
+        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_3, OEM_DENY_3_MATCH);
+    }
+
+    /**
+     * Only tests or BpfNetMaps#ensureInitialized can call this function.
+     */
+    @VisibleForTesting
+    public static void initialize(final Dependencies deps) {
+        sConfigurationMap = deps.getConfigurationMap();
+    }
+
     /**
      * Initializes the class if it is not already initialized. This method will open maps but not
      * cause any other effects. This method may be called multiple times on any thread.
@@ -50,10 +115,30 @@
         if (!USE_NETD) {
             System.loadLibrary("service-connectivity");
             native_init();
+            initialize(new Dependencies());
         }
         sInitialized = true;
     }
 
+    /**
+     * Dependencies of BpfNetMaps, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /**
+         *  Get configuration BPF map.
+         */
+        public BpfMap<U32, U32> getConfigurationMap() {
+            try {
+                return new BpfMap<>(
+                        CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U32.class);
+            } catch (ErrnoException e) {
+                Log.e(TAG, "Cannot open netd configuration map: " + e);
+                return null;
+            }
+        }
+    }
+
     /** Constructor used after T that doesn't need to use netd anymore. */
     public BpfNetMaps() {
         this(null);
@@ -61,17 +146,35 @@
         if (USE_NETD) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
     }
 
-    public BpfNetMaps(INetd netd) {
+    public BpfNetMaps(final INetd netd) {
         ensureInitialized();
         mNetd = netd;
     }
 
+    /**
+     * Get corresponding match from firewall chain.
+     */
+    @VisibleForTesting
+    public long getMatchByFirewallChain(final int chain) {
+        final long match = FIREWALL_CHAIN_TO_MATCH.get(chain, NO_MATCH);
+        if (match == NO_MATCH) {
+            throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
+        }
+        return match;
+    }
+
     private void maybeThrow(final int err, final String msg) {
         if (err != 0) {
             throw new ServiceSpecificException(err, msg + ": " + Os.strerror(err));
         }
     }
 
+    private void throwIfUseNetd(final String msg) {
+        if (USE_NETD) {
+            throw new UnsupportedOperationException(msg);
+        }
+    }
+
     /**
      * Add naughty app bandwidth rule for specific app
      *
@@ -125,12 +228,56 @@
      *
      * @param childChain target chain to enable
      * @param enable     whether to enable or disable child chain.
+     * @throws UnsupportedOperationException if called on pre-T devices.
      * @throws ServiceSpecificException in case of failure, with an error code indicating the
      *                                  cause of the failure.
      */
     public void setChildChain(final int childChain, final boolean enable) {
-        final int err = native_setChildChain(childChain, enable);
-        maybeThrow(err, "Unable to set child chain");
+        throwIfUseNetd("setChildChain is not available on pre-T devices");
+
+        final long match = getMatchByFirewallChain(childChain);
+        try {
+            synchronized (sUidRulesConfigBpfMapLock) {
+                final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
+                if (config == null) {
+                    throw new ServiceSpecificException(ENOENT,
+                            "Unable to get firewall chain status: sConfigurationMap does not have"
+                                    + " entry for UID_RULES_CONFIGURATION_KEY");
+                }
+                final long newConfig = enable ? (config.val | match) : (config.val & (~match));
+                sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(newConfig));
+            }
+        } catch (ErrnoException e) {
+            throw new ServiceSpecificException(e.errno,
+                    "Unable to set child chain: " + Os.strerror(e.errno));
+        }
+    }
+
+    /**
+     * Get the specified firewall chain status.
+     *
+     * @param childChain target chain
+     * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
+     * @throws UnsupportedOperationException if called on pre-T devices.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public boolean getChainEnabled(final int childChain) {
+        throwIfUseNetd("getChainEnabled is not available on pre-T devices");
+
+        final long match = getMatchByFirewallChain(childChain);
+        try {
+            final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
+            if (config == null) {
+                throw new ServiceSpecificException(ENOENT,
+                        "Unable to get firewall chain status: sConfigurationMap does not have"
+                                + " entry for UID_RULES_CONFIGURATION_KEY");
+            }
+            return (config.val & match) != 0;
+        } catch (ErrnoException e) {
+            throw new ServiceSpecificException(e.errno,
+                    "Unable to get firewall chain status: " + Os.strerror(e.errno));
+        }
     }
 
     /**
@@ -279,7 +426,6 @@
     private native int native_removeNaughtyApp(int uid);
     private native int native_addNiceApp(int uid);
     private native int native_removeNiceApp(int uid);
-    private native int native_setChildChain(int childChain, boolean enable);
     private native int native_replaceUidChain(String name, boolean isAllowlist, int[] uids);
     private native int native_setUidRule(int childChain, int uid, int firewallRule);
     private native int native_addUidInterfaceRules(String ifName, int[] uids);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 853a1a2..6568654 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -11384,6 +11384,13 @@
     }
 
     @Override
+    public boolean getFirewallChainEnabled(final int chain) {
+        enforceNetworkStackOrSettingsPermission();
+
+        return mBpfNetMaps.getChainEnabled(chain);
+    }
+
+    @Override
     public void replaceFirewallChain(final int chain, final int[] uids) {
         enforceNetworkStackOrSettingsPermission();
 
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index cc64239..458d225 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -57,7 +57,7 @@
 import com.android.net.module.util.TrackRecord
 import com.android.testutils.anyNetwork
 import com.android.testutils.ConnectivityModuleTest
-import com.android.testutils.DeviceInfoUtils
+import com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
@@ -145,6 +145,8 @@
             raResponder.start()
         }
 
+        // WARNING: this function requires kernel support. Call assumeChangingCarrierSupported() at
+        // the top of your test.
         fun setCarrierEnabled(enabled: Boolean) {
             runAsShell(MANAGE_TEST_NETWORKS) {
                 tnm.setCarrierEnabled(tapInterface, enabled)
@@ -295,6 +297,9 @@
         releaseTetheredInterface()
     }
 
+    // Setting the carrier up / down relies on TUNSETCARRIER which was added in kernel version 5.0.
+    private fun assumeChangingCarrierSupported() = assumeTrue(isKernelVersionAtLeast("5.0.0"))
+
     private fun addInterfaceStateListener(listener: EthernetStateListener) {
         runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
             em.addInterfaceStateListener(handler::post, listener)
@@ -302,6 +307,8 @@
         addedListeners.add(listener)
     }
 
+    // WARNING: setting hasCarrier to false requires kernel support. Call
+    // assumeChangingCarrierSupported() at the top of your test.
     private fun createInterface(hasCarrier: Boolean = true): EthernetTestInterface {
         val iface = EthernetTestInterface(
             context,
@@ -631,9 +638,7 @@
 
     @Test
     fun testNetworkRequest_forInterfaceWhileTogglingCarrier() {
-        // Notice this test case fails on devices running on an older kernel version(e.g. 4.14)
-        // that might not support ioctl new argument. Only run this test on 4.19 kernel or above.
-        assumeTrue(DeviceInfoUtils.isKernelVersionAtLeast("4.19.0"))
+        assumeChangingCarrierSupported()
 
         val iface = createInterface(false /* hasCarrier */)
 
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index f07a10d..99e7ecc 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -16,43 +16,93 @@
 
 package com.android.server;
 
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
 import static android.net.INetd.PERMISSION_INTERNET;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.Mockito.verify;
 
 import android.net.INetd;
 import android.os.Build;
+import android.os.ServiceSpecificException;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.Struct.U32;
 import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestBpfMap;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public final class BpfNetMapsTest {
     private static final String TAG = "BpfNetMapsTest";
+
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
     private static final int TEST_UID = 10086;
     private static final int[] TEST_UIDS = {10002, 10003};
     private static final String IFNAME = "wlan0";
     private static final String CHAINNAME = "fw_dozable";
+    private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
+    private static final List<Integer> FIREWALL_CHAINS = List.of(
+            FIREWALL_CHAIN_DOZABLE,
+            FIREWALL_CHAIN_STANDBY,
+            FIREWALL_CHAIN_POWERSAVE,
+            FIREWALL_CHAIN_RESTRICTED,
+            FIREWALL_CHAIN_LOW_POWER_STANDBY,
+            FIREWALL_CHAIN_OEM_DENY_1,
+            FIREWALL_CHAIN_OEM_DENY_2,
+            FIREWALL_CHAIN_OEM_DENY_3
+    );
+
     private BpfNetMaps mBpfNetMaps;
 
     @Mock INetd mNetd;
+    private static final TestBpfMap<U32, U32> sConfigurationMap =
+            new TestBpfMap<>(U32.class, U32.class);
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mBpfNetMaps = new BpfNetMaps(mNetd);
+        BpfNetMaps.initialize(makeDependencies());
+        sConfigurationMap.clear();
+    }
+
+    private static BpfNetMaps.Dependencies makeDependencies() {
+        return new BpfNetMaps.Dependencies() {
+            @Override
+            public BpfMap<U32, U32> getConfigurationMap() {
+                return sConfigurationMap;
+            }
+        };
     }
 
     @Test
@@ -65,4 +115,154 @@
         mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
         verify(mNetd).trafficSetNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
     }
+
+    private void doTestGetChainEnabled(final List<Integer> enableChains) throws Exception {
+        long match = 0;
+        for (final int chain: enableChains) {
+            match |= mBpfNetMaps.getMatchByFirewallChain(chain);
+        }
+        sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(match));
+
+        for (final int chain: FIREWALL_CHAINS) {
+            final String testCase = "EnabledChains: " + enableChains + " CheckedChain: " + chain;
+            if (enableChains.contains(chain)) {
+                assertTrue("Expected getChainEnabled returns True, " + testCase,
+                        mBpfNetMaps.getChainEnabled(chain));
+            } else {
+                assertFalse("Expected getChainEnabled returns False, " + testCase,
+                        mBpfNetMaps.getChainEnabled(chain));
+            }
+        }
+    }
+
+    private void doTestGetChainEnabled(final int enableChain) throws Exception {
+        doTestGetChainEnabled(List.of(enableChain));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testGetChainEnabled() throws Exception {
+        doTestGetChainEnabled(FIREWALL_CHAIN_DOZABLE);
+        doTestGetChainEnabled(FIREWALL_CHAIN_STANDBY);
+        doTestGetChainEnabled(FIREWALL_CHAIN_POWERSAVE);
+        doTestGetChainEnabled(FIREWALL_CHAIN_RESTRICTED);
+        doTestGetChainEnabled(FIREWALL_CHAIN_LOW_POWER_STANDBY);
+        doTestGetChainEnabled(FIREWALL_CHAIN_OEM_DENY_1);
+        doTestGetChainEnabled(FIREWALL_CHAIN_OEM_DENY_2);
+        doTestGetChainEnabled(FIREWALL_CHAIN_OEM_DENY_3);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testGetChainEnabledMultipleChainEnabled() throws Exception {
+        doTestGetChainEnabled(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY));
+        doTestGetChainEnabled(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_RESTRICTED));
+        doTestGetChainEnabled(FIREWALL_CHAINS);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testGetChainEnabledInvalidChain() {
+        final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+        assertThrows(expected, () -> mBpfNetMaps.getChainEnabled(-1 /* childChain */));
+        assertThrows(expected, () -> mBpfNetMaps.getChainEnabled(1000 /* childChain */));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testGetChainEnabledMissingConfiguration() {
+        // sConfigurationMap does not have entry for UID_RULES_CONFIGURATION_KEY
+        assertThrows(ServiceSpecificException.class,
+                () -> mBpfNetMaps.getChainEnabled(FIREWALL_CHAIN_DOZABLE));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testGetChainEnabledBeforeT() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBpfNetMaps.getChainEnabled(FIREWALL_CHAIN_DOZABLE));
+    }
+
+    private void doTestSetChildChain(final List<Integer> testChains) throws Exception {
+        long expectedMatch = 0;
+        for (final int chain: testChains) {
+            expectedMatch |= mBpfNetMaps.getMatchByFirewallChain(chain);
+        }
+
+        assertEquals(0, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+
+        for (final int chain: testChains) {
+            mBpfNetMaps.setChildChain(chain, true /* enable */);
+        }
+        assertEquals(expectedMatch, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+
+        for (final int chain: testChains) {
+            mBpfNetMaps.setChildChain(chain, false /* enable */);
+        }
+        assertEquals(0, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+    }
+
+    private void doTestSetChildChain(final int testChain) throws Exception {
+        doTestSetChildChain(List.of(testChain));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetChildChain() throws Exception {
+        sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+        doTestSetChildChain(FIREWALL_CHAIN_DOZABLE);
+        doTestSetChildChain(FIREWALL_CHAIN_STANDBY);
+        doTestSetChildChain(FIREWALL_CHAIN_POWERSAVE);
+        doTestSetChildChain(FIREWALL_CHAIN_RESTRICTED);
+        doTestSetChildChain(FIREWALL_CHAIN_LOW_POWER_STANDBY);
+        doTestSetChildChain(FIREWALL_CHAIN_OEM_DENY_1);
+        doTestSetChildChain(FIREWALL_CHAIN_OEM_DENY_2);
+        doTestSetChildChain(FIREWALL_CHAIN_OEM_DENY_3);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetChildChainMultipleChain() throws Exception {
+        sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+        doTestSetChildChain(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY));
+        doTestSetChildChain(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_RESTRICTED));
+        doTestSetChildChain(FIREWALL_CHAINS);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetChildChainInvalidChain() {
+        final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+        assertThrows(expected,
+                () -> mBpfNetMaps.setChildChain(-1 /* childChain */, true /* enable */));
+        assertThrows(expected,
+                () -> mBpfNetMaps.setChildChain(1000 /* childChain */, true /* enable */));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetChildChainMissingConfiguration() {
+        // sConfigurationMap does not have entry for UID_RULES_CONFIGURATION_KEY
+        assertThrows(ServiceSpecificException.class,
+                () -> mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true /* enable */));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testSetChildChainBeforeT() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true /* enable */));
+    }
 }