Merge changes from topic "trafficctrl_coverage"

* changes:
  Extend unit test for dump function
  Add unit test for getFirewallType
  Add unit test for dump function
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index dfa7097..ff2ebad 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -30,6 +30,8 @@
 
 #include <gtest/gtest.h>
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <binder/Status.h>
@@ -49,6 +51,7 @@
 using android::netdutils::Status;
 using base::Result;
 using netdutils::isOk;
+using netdutils::statusFromErrno;
 
 constexpr int TEST_MAP_SIZE = 10;
 constexpr uid_t TEST_UID = 10086;
@@ -56,6 +59,13 @@
 constexpr uid_t TEST_UID3 = 98765;
 constexpr uint32_t TEST_TAG = 42;
 constexpr uint32_t TEST_COUNTERSET = 1;
+constexpr int TEST_COOKIE = 1;
+constexpr char TEST_IFNAME[] = "test0";
+constexpr int TEST_IFINDEX = 999;
+constexpr int RXPACKETS = 1;
+constexpr int RXBYTES = 100;
+constexpr int TXPACKETS = 0;
+constexpr int TXBYTES = 0;
 
 #define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
 
@@ -69,6 +79,8 @@
     BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
     BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
     BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+    BpfMap<uint32_t, uint8_t> mFakeUidCounterSetMap;
+    BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
 
     void SetUp() {
         std::lock_guard guard(mTc.mMutex);
@@ -91,6 +103,12 @@
         mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
         ASSERT_VALID(mFakeUidPermissionMap);
 
+        mFakeUidCounterSetMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+        ASSERT_VALID(mFakeUidCounterSetMap);
+
+        mFakeIfaceIndexNameMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+        ASSERT_VALID(mFakeIfaceIndexNameMap);
+
         mTc.mCookieTagMap = mFakeCookieTagMap;
         ASSERT_VALID(mTc.mCookieTagMap);
         mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
@@ -108,21 +126,38 @@
         mTc.mUidPermissionMap = mFakeUidPermissionMap;
         ASSERT_VALID(mTc.mUidPermissionMap);
         mTc.mPrivilegedUser.clear();
+
+        mTc.mUidCounterSetMap = mFakeUidCounterSetMap;
+        ASSERT_VALID(mTc.mUidCounterSetMap);
+
+        mTc.mIfaceIndexNameMap = mFakeIfaceIndexNameMap;
+        ASSERT_VALID(mTc.mIfaceIndexNameMap);
     }
 
     void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
         UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
         EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
-        *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
-        StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
-        EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
-        key->tag = 0;
+        *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = TEST_IFINDEX};
+        StatsValue statsMapValue = {.rxPackets = RXPACKETS, .rxBytes = RXBYTES,
+                                    .txPackets = TXPACKETS, .txBytes = TXBYTES};
         EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
         EXPECT_RESULT_OK(mFakeAppUidStatsMap.writeValue(uid, statsMapValue, BPF_ANY));
         // put tag information back to statsKey
         key->tag = tag;
     }
 
+    void populateFakeCounterSet(uint32_t uid, uint32_t counterSet) {
+        EXPECT_RESULT_OK(mFakeUidCounterSetMap.writeValue(uid, counterSet, BPF_ANY));
+    }
+
+    void populateFakeIfaceIndexName(const char* name, uint32_t ifaceIndex) {
+        if (name == nullptr || ifaceIndex <= 0) return;
+
+        IfaceValue iface;
+        strlcpy(iface.name, name, sizeof(IfaceValue));
+        EXPECT_RESULT_OK(mFakeIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY));
+    }
+
     void checkUidOwnerRuleForChain(ChildChain chain, UidOwnerMatchType match) {
         uint32_t uid = TEST_UID;
         EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, DENYLIST));
@@ -242,17 +277,17 @@
         EXPECT_EQ(tag, cookieMapResult.value().tag);
         Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
         EXPECT_RESULT_OK(statsMapResult);
-        EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
-        EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+        EXPECT_EQ((uint64_t)RXPACKETS, statsMapResult.value().rxPackets);
+        EXPECT_EQ((uint64_t)RXBYTES, statsMapResult.value().rxBytes);
         tagStatsMapKey.tag = 0;
         statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
         EXPECT_RESULT_OK(statsMapResult);
-        EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
-        EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+        EXPECT_EQ((uint64_t)RXPACKETS, statsMapResult.value().rxPackets);
+        EXPECT_EQ((uint64_t)RXBYTES, statsMapResult.value().rxBytes);
         auto appStatsResult = mFakeAppUidStatsMap.readValue(uid);
         EXPECT_RESULT_OK(appStatsResult);
-        EXPECT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
-        EXPECT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
+        EXPECT_EQ((uint64_t)RXPACKETS, appStatsResult.value().rxPackets);
+        EXPECT_EQ((uint64_t)RXBYTES, appStatsResult.value().rxBytes);
     }
 
     Status updateUidOwnerMaps(const std::vector<uint32_t>& appUids,
@@ -265,6 +300,62 @@
         return ret;
     }
 
+    Status dump(bool verbose, std::vector<std::string>& outputLines) {
+      if (!outputLines.empty()) return statusFromErrno(EUCLEAN, "Output buffer is not empty");
+
+      android::base::unique_fd localFd, remoteFd;
+      if (!Pipe(&localFd, &remoteFd)) return statusFromErrno(errno, "Failed on pipe");
+
+      // dump() blocks until another thread has consumed all its output.
+      std::thread dumpThread =
+          std::thread([this, remoteFd{std::move(remoteFd)}, verbose]() {
+            mTc.dump(remoteFd, verbose);
+          });
+
+      std::string dumpContent;
+      if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) {
+        return statusFromErrno(errno, "Failed to read dump results from fd");
+      }
+      dumpThread.join();
+
+      std::stringstream dumpStream(std::move(dumpContent));
+      std::string line;
+      while (std::getline(dumpStream, line)) {
+        outputLines.push_back(line);
+      }
+
+      return netdutils::status::ok;
+    }
+
+    // Strings in the |expect| must exist in dump results in order. But no need to be consecutive.
+    bool expectDumpsysContains(std::vector<std::string>& expect) {
+        if (expect.empty()) return false;
+
+        std::vector<std::string> output;
+        Status result = dump(true, output);
+        if (!isOk(result)) {
+            GTEST_LOG_(ERROR) << "TrafficController dump failed: " << netdutils::toString(result);
+            return false;
+        }
+
+        int matched = 0;
+        auto it = expect.begin();
+        for (const auto& line : output) {
+            if (it == expect.end()) break;
+            if (std::string::npos != line.find(*it)) {
+                matched++;
+                ++it;
+            }
+        }
+
+        if (matched != expect.size()) {
+            // dump results for debugging
+            for (const auto& o : output) LOG(INFO) << "output: " << o;
+            for (const auto& e : expect) LOG(INFO) << "expect: " << e;
+            return false;
+        }
+        return true;
+    }
 };
 
 TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
@@ -645,6 +736,83 @@
     expectPrivilegedUserSetEmpty();
 }
 
+TEST_F(TrafficControllerTest, TestDumpsys) {
+    StatsKey tagStatsMapKey;
+    populateFakeStats(TEST_COOKIE, TEST_UID, TEST_TAG, &tagStatsMapKey);
+    populateFakeCounterSet(TEST_UID3, TEST_COUNTERSET);
+
+    // Expect: (part of this depends on hard-code values in populateFakeStats())
+    //
+    // mCookieTagMap:
+    // cookie=1 tag=0x2a uid=10086
+    //
+    // mUidCounterSetMap:
+    // 98765 1
+    //
+    // mAppUidStatsMap::
+    // uid rxBytes rxPackets txBytes txPackets
+    // 10086 100 1 0 0
+    //
+    // mStatsMapA:
+    // ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets
+    // 999 test0 0x2a 10086 1 100 1 0 0
+    std::vector<std::string> expectedLines = {
+        "mCookieTagMap:",
+        fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID),
+        "mUidCounterSetMap:",
+        fmt::format("{} {}", TEST_UID3, TEST_COUNTERSET),
+        "mAppUidStatsMap::",  // TODO@: fix double colon
+        "uid rxBytes rxPackets txBytes txPackets",
+        fmt::format("{} {} {} {} {}", TEST_UID, RXBYTES, RXPACKETS, TXBYTES, TXPACKETS),
+        "mStatsMapA",
+        "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
+        fmt::format("{} {} {:#x} {} {} {} {} {} {}",
+                    TEST_IFINDEX, TEST_IFNAME, TEST_TAG, TEST_UID, TEST_COUNTERSET, RXBYTES,
+                    RXPACKETS, TXBYTES, TXPACKETS)};
+
+    populateFakeIfaceIndexName(TEST_IFNAME, TEST_IFINDEX);
+    expectedLines.emplace_back("mIfaceIndexNameMap:");
+    expectedLines.emplace_back(fmt::format("ifaceIndex={} ifaceName={}",
+                                           TEST_IFINDEX, TEST_IFNAME));
+
+    ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, HAPPY_BOX_MATCH,
+                                        TrafficController::IptOpInsert)));
+    expectedLines.emplace_back("mUidOwnerMap:");
+    expectedLines.emplace_back(fmt::format("{}  HAPPY_BOX_MATCH", TEST_UID));
+
+    mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, {TEST_UID2});
+    expectedLines.emplace_back("mUidPermissionMap:");
+    expectedLines.emplace_back(fmt::format("{}  BPF_PERMISSION_UPDATE_DEVICE_STATS", TEST_UID2));
+    expectedLines.emplace_back("mPrivilegedUser:");
+    expectedLines.emplace_back(fmt::format("{} ALLOW_UPDATE_DEVICE_STATS", TEST_UID2));
+    EXPECT_TRUE(expectDumpsysContains(expectedLines));
+}
+
+TEST_F(TrafficControllerTest, getFirewallType) {
+    static const struct TestConfig {
+        ChildChain childChain;
+        FirewallType firewallType;
+    } testConfigs[] = {
+            // clang-format off
+            {NONE, DENYLIST},
+            {DOZABLE, ALLOWLIST},
+            {STANDBY, DENYLIST},
+            {POWERSAVE, ALLOWLIST},
+            {RESTRICTED, ALLOWLIST},
+            {LOW_POWER_STANDBY, ALLOWLIST},
+            {LOCKDOWN, DENYLIST},
+            {OEM_DENY_1, DENYLIST},
+            {OEM_DENY_2, DENYLIST},
+            {INVALID_CHAIN, DENYLIST},
+            // clang-format on
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.childChain, config.firewallType));
+        EXPECT_EQ(config.firewallType, mTc.getFirewallType(config.childChain));
+    }
+}
+
 constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
 constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;