TetherControllerTest: Add tests for getTetherStats with BPF maps

Bug: 149963652
Test: build, atest
Change-Id: I5b2186bb9134eee52462c930956ced635fee00ba
Merged-In: I5b2186bb9134eee52462c930956ced635fee00ba
diff --git a/server/TetherControllerTest.cpp b/server/TetherControllerTest.cpp
index 309a6d5..7199a3d 100644
--- a/server/TetherControllerTest.cpp
+++ b/server/TetherControllerTest.cpp
@@ -20,28 +20,43 @@
 #include <vector>
 
 #include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
+#include <inttypes.h>
 #include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include <gtest/gtest.h>
 
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <gmock/gmock.h>
 #include <netdutils/StatusOr.h>
 
-#include "TetherController.h"
 #include "IptablesBaseTest.h"
+#include "OffloadUtils.h"
+#include "TetherController.h"
 
 using android::base::Join;
 using android::base::StringPrintf;
+using android::bpf::BpfMap;
 using android::netdutils::StatusOr;
+using ::testing::Contains;
 using TetherStats = android::net::TetherController::TetherStats;
 using TetherStatsList = android::net::TetherController::TetherStatsList;
 
 namespace android {
 namespace net {
 
+constexpr int TEST_MAP_SIZE = 10;
+
+// Comparison for TetherStats. Need to override operator== because class TetherStats doesn't have.
+// TODO: once C++20 is used, use default operator== in TetherStats and remove the overriding here.
+bool operator==(const TetherStats& lhs, const TetherStats& rhs) {
+    return lhs.intIface == rhs.intIface && lhs.extIface == rhs.extIface &&
+           lhs.rxBytes == rhs.rxBytes && lhs.txBytes == rhs.txBytes &&
+           lhs.rxPackets == rhs.rxPackets && lhs.txPackets == rhs.txPackets;
+}
+
 class TetherControllerTest : public IptablesBaseTest {
 public:
     TetherControllerTest() {
@@ -50,6 +65,42 @@
 
 protected:
     TetherController mTetherCtrl;
+    BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap{BPF_MAP_TYPE_HASH, TEST_MAP_SIZE};
+    BpfMap<uint32_t, TetherStatsValue> mFakeTetherStatsMap{BPF_MAP_TYPE_HASH, TEST_MAP_SIZE};
+
+    void SetUp() {
+        SKIP_IF_BPF_NOT_SUPPORTED;
+
+        ASSERT_TRUE(mFakeIfaceIndexNameMap.isValid());
+        ASSERT_TRUE(mFakeTetherStatsMap.isValid());
+
+        mTetherCtrl.mIfaceIndexNameMap = mFakeIfaceIndexNameMap;
+        ASSERT_TRUE(mTetherCtrl.mIfaceIndexNameMap.isValid());
+        mTetherCtrl.mBpfStatsMap = mFakeTetherStatsMap;
+        ASSERT_TRUE(mTetherCtrl.mBpfStatsMap.isValid());
+    }
+
+    std::string toString(const TetherStatsList& statsList) {
+        std::string result;
+        for (const auto& stats : statsList) {
+            result += StringPrintf("%s, %s, %" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 "\n",
+                                   stats.intIface.c_str(), stats.extIface.c_str(), stats.rxBytes,
+                                   stats.rxPackets, stats.txBytes, stats.txPackets);
+        }
+        return result;
+    }
+
+    void updateMaps(uint32_t ifaceIndex, const char* ifaceName, uint64_t rxBytes,
+                    uint64_t rxPackets, uint64_t txBytes, uint64_t txPackets) {
+        IfaceValue iface{};
+        strlcpy(iface.name, ifaceName, sizeof(iface.name));
+        ASSERT_RESULT_OK(mFakeIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY));
+
+        // {rx, tx}Errors in |tetherStats| are set zero because getTetherStats doesn't use them.
+        const TetherStatsValue tetherStats = {rxPackets, rxBytes, 0 /*unused*/,
+                                              txPackets, txBytes, 0 /*unused*/};
+        ASSERT_RESULT_OK(mFakeTetherStatsMap.writeValue(ifaceIndex, tetherStats, BPF_ANY));
+    };
 
     int setDefaults() {
         return mTetherCtrl.setDefaults();
@@ -435,5 +486,33 @@
     EXPECT_TRUE(std::equal(expectedError.rbegin(), expectedError.rend(), err.rbegin()));
 }
 
+TEST_F(TetherControllerTest, TestGetTetherStatsWithBpfTetherStatsMap) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    // Setup BPF tether stats maps. The tether stats of interface rmnet0 comes from two tether
+    // stats map items with different interface index. Therefore, need to sum up both of them
+    // for the tether stats of interface rmnet0.
+    updateMaps(101, "wlan0", 100, 10, 200, 20);
+    updateMaps(102, "rmnet0", 300, 30, 400, 40);
+    updateMaps(103, "rmnet0", 500, 50, 600, 60);
+    const TetherStats expected0("BPFOffloadInterface", "wlan0", 100, 10, 200, 20);
+    const TetherStats expected1("BPFOffloadInterface", "rmnet0", 800, 80, 1000, 100);
+
+    // Setup iptables tether counters. IPv4 and IPv6 counters are added together.
+    addIptablesRestoreOutput(kIPv4TetherCounters, kIPv6TetherCounters);
+    const TetherStats expected2("wlan0", "rmnet0", 20002002, 20027, 10002373, 10026);
+    const TetherStats expected3("bt-pan", "rmnet0", 1708806, 1450, 107471, 1040);
+
+    const StatusOr<TetherStatsList> result = mTetherCtrl.getTetherStats();
+    ASSERT_OK(result);
+    const TetherStatsList& actual = result.value();
+    ASSERT_EQ(4U, actual.size());
+    EXPECT_THAT(actual, Contains(expected0)) << toString(actual);
+    EXPECT_THAT(actual, Contains(expected1)) << toString(actual);
+    EXPECT_THAT(actual, Contains(expected2)) << toString(actual);
+    EXPECT_THAT(actual, Contains(expected3)) << toString(actual);
+    clearIptablesRestoreOutput();
+}
+
 }  // namespace net
 }  // namespace android