/*
 * Copyright 2017 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.
 *
 * ControllersTest.cpp - unit tests for Controllers.cpp
 */

#include <set>
#include <string>
#include <vector>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <android-base/strings.h>

#include "Controllers.h"
#include "IptablesBaseTest.h"

using testing::ContainerEq;

namespace android {
namespace net {

class ControllersTest : public IptablesBaseTest {
  public:
    ControllersTest() {
        Controllers::execIptablesRestore = fakeExecIptablesRestore;
        Controllers::execIptablesRestoreWithOutput = fakeExecIptablesRestoreWithOutput;
    }

  protected:
    void initChildChains() { Controllers::initChildChains(); };
    std::set<std::string> findExistingChildChains(IptablesTarget a, const char* b, const char*c) {
        return Controllers::findExistingChildChains(a, b, c);
    }
};

TEST_F(ControllersTest, TestFindExistingChildChains) {
    ExpectedIptablesCommands expectedCmds = {
        { V6, "*raw\n-S PREROUTING\nCOMMIT\n" },
    };
    sIptablesRestoreOutput.push_back(
        "-P PREROUTING ACCEPT\n"
        "-A PREROUTING -j bw_raw_PREROUTING\n"
        "-A PREROUTING -j idletimer_raw_PREROUTING\n"
        "-A PREROUTING -j tetherctrl_raw_PREROUTING\n"
    );
    std::set<std::string> expectedChains = {
        "bw_raw_PREROUTING",
        "idletimer_raw_PREROUTING",
        "tetherctrl_raw_PREROUTING",
    };
    std::set<std::string> actual = findExistingChildChains(V6, "raw", "PREROUTING");
    EXPECT_THAT(expectedChains, ContainerEq(actual));
    expectIptablesRestoreCommands(expectedCmds);
}

TEST_F(ControllersTest, TestInitIptablesRules) {
    // Test what happens when we boot and there are no rules.
    ExpectedIptablesCommands expected = {
            {V4V6,
             "*filter\n"
             ":INPUT -\n"
             "-F INPUT\n"
             ":bw_INPUT -\n"
             "-A INPUT -j bw_INPUT\n"
             ":fw_INPUT -\n"
             "-A INPUT -j fw_INPUT\n"
             "COMMIT\n"},
            {V4V6,
             "*filter\n"
             ":FORWARD -\n"
             "-F FORWARD\n"
             ":oem_fwd -\n"
             "-A FORWARD -j oem_fwd\n"
             ":fw_FORWARD -\n"
             "-A FORWARD -j fw_FORWARD\n"
             ":bw_FORWARD -\n"
             "-A FORWARD -j bw_FORWARD\n"
             ":tetherctrl_FORWARD -\n"
             "-A FORWARD -j tetherctrl_FORWARD\n"
             "COMMIT\n"},
            {V4V6,
             "*raw\n"
             ":PREROUTING -\n"
             "-F PREROUTING\n"
             ":clat_raw_PREROUTING -\n"
             "-A PREROUTING -j clat_raw_PREROUTING\n"
             ":bw_raw_PREROUTING -\n"
             "-A PREROUTING -j bw_raw_PREROUTING\n"
             ":idletimer_raw_PREROUTING -\n"
             "-A PREROUTING -j idletimer_raw_PREROUTING\n"
             ":tetherctrl_raw_PREROUTING -\n"
             "-A PREROUTING -j tetherctrl_raw_PREROUTING\n"
             "COMMIT\n"},
            {V4V6,
             "*mangle\n"
             ":FORWARD -\n"
             "-F FORWARD\n"
             ":tetherctrl_mangle_FORWARD -\n"
             "-A FORWARD -j tetherctrl_mangle_FORWARD\n"
             "COMMIT\n"},
            {V4V6,
             "*mangle\n"
             ":INPUT -\n"
             "-F INPUT\n"
             ":wakeupctrl_mangle_INPUT -\n"
             "-A INPUT -j wakeupctrl_mangle_INPUT\n"
             ":routectrl_mangle_INPUT -\n"
             "-A INPUT -j routectrl_mangle_INPUT\n"
             "COMMIT\n"},
            {V4,
             "*nat\n"
             ":PREROUTING -\n"
             "-F PREROUTING\n"
             ":oem_nat_pre -\n"
             "-A PREROUTING -j oem_nat_pre\n"
             "COMMIT\n"},
            {V4,
             "*nat\n"
             ":POSTROUTING -\n"
             "-F POSTROUTING\n"
             ":tetherctrl_nat_POSTROUTING -\n"
             "-A POSTROUTING -j tetherctrl_nat_POSTROUTING\n"
             "COMMIT\n"},
            {V4,
             "*filter\n"
             "-S OUTPUT\n"
             "COMMIT\n"},
            {V4,
             "*filter\n"
             ":oem_out -\n"
             "-A OUTPUT -j oem_out\n"
             ":fw_OUTPUT -\n"
             "-A OUTPUT -j fw_OUTPUT\n"
             ":st_OUTPUT -\n"
             "-A OUTPUT -j st_OUTPUT\n"
             ":bw_OUTPUT -\n"
             "-A OUTPUT -j bw_OUTPUT\n"
             "COMMIT\n"},
            {V6,
             "*filter\n"
             "-S OUTPUT\n"
             "COMMIT\n"},
            {V6,
             "*filter\n"
             ":oem_out -\n"
             "-A OUTPUT -j oem_out\n"
             ":fw_OUTPUT -\n"
             "-A OUTPUT -j fw_OUTPUT\n"
             ":st_OUTPUT -\n"
             "-A OUTPUT -j st_OUTPUT\n"
             ":bw_OUTPUT -\n"
             "-A OUTPUT -j bw_OUTPUT\n"
             "COMMIT\n"},
            {V4,
             "*mangle\n"
             "-S POSTROUTING\n"
             "COMMIT\n"},
            {V4,
             "*mangle\n"
             ":oem_mangle_post -\n"
             "-A POSTROUTING -j oem_mangle_post\n"
             ":bw_mangle_POSTROUTING -\n"
             "-A POSTROUTING -j bw_mangle_POSTROUTING\n"
             ":idletimer_mangle_POSTROUTING -\n"
             "-A POSTROUTING -j idletimer_mangle_POSTROUTING\n"
             "COMMIT\n"},
            {V6,
             "*mangle\n"
             "-S POSTROUTING\n"
             "COMMIT\n"},
            {V6,
             "*mangle\n"
             ":oem_mangle_post -\n"
             "-A POSTROUTING -j oem_mangle_post\n"
             ":bw_mangle_POSTROUTING -\n"
             "-A POSTROUTING -j bw_mangle_POSTROUTING\n"
             ":idletimer_mangle_POSTROUTING -\n"
             "-A POSTROUTING -j idletimer_mangle_POSTROUTING\n"
             "COMMIT\n"},
    };

    // Check that we run these commands and these only.
    initChildChains();
    expectIptablesRestoreCommands(expected);
    expectIptablesRestoreCommands(ExpectedIptablesCommands{});

    // Now test what happens when some rules exist (e.g., if we crash and restart).

    // First, explicitly tell the iptables test code to return empty output to all the commands we
    // send. This allows us to tell it to return non-empty output to particular commands in the
    // following code.
    for (size_t i = 0; i < expected.size(); i++) {
        sIptablesRestoreOutput.push_back("");
    }

    // Define a macro to remove a substring from a string. We use a macro instead of a function so
    // we can assert in it. In the following code, we use ASSERT_* to check for programming errors
    // in the test code, and EXPECT_* to check for errors in the actual code.
#define DELETE_SUBSTRING(substr, str) {                      \
        size_t start = (str).find((substr));                 \
        ASSERT_NE(std::string::npos, start);                 \
        (str).erase(start, strlen((substr)));                \
        ASSERT_EQ(std::string::npos, (str).find((substr)));  \
    }

    // Now set test expectations.

    // 1. Test that if we find rules that we don't create ourselves, we ignore them.
    // First check that command #7 is where we list the OUTPUT chain in the (IPv4) filter table:
    ASSERT_NE(std::string::npos, expected[7].second.find("*filter\n-S OUTPUT\n"));
    // ... and pretend that when we run that command, we find the following rules. Because we don't
    // create any of these rules ourselves, our behaviour is unchanged.
    sIptablesRestoreOutput[7] =
        "-P OUTPUT ACCEPT\n"
        "-A OUTPUT -o r_rmnet_data8 -p udp -m udp --dport 1900 -j DROP\n";

    // 2. Test that rules that we create ourselves are not added if they already exist.
    // Pretend that when we list the OUTPUT chain in the (IPv6) filter table, we find the oem_out
    // and st_OUTPUT chains:
    ASSERT_NE(std::string::npos, expected[9].second.find("*filter\n-S OUTPUT\n"));
    sIptablesRestoreOutput[9] =
        "-A OUTPUT -j oem_out\n"
        "-A OUTPUT -j st_OUTPUT\n";
    // ... and expect that when we populate the OUTPUT chain, we do not re-add them.
    DELETE_SUBSTRING("-A OUTPUT -j oem_out\n", expected[10].second);
    DELETE_SUBSTRING("-A OUTPUT -j st_OUTPUT\n", expected[10].second);

    // 3. Now test that when we list the POSTROUTING chain in the mangle table, we find a mixture of
    // netd-created rules and vendor rules:
    ASSERT_NE(std::string::npos, expected[13].second.find("*mangle\n-S POSTROUTING\n"));
    sIptablesRestoreOutput[13] =
        "-P POSTROUTING ACCEPT\n"
        "-A POSTROUTING -j oem_mangle_post\n"
        "-A POSTROUTING -j bw_mangle_POSTROUTING\n"
        "-A POSTROUTING -j idletimer_mangle_POSTROUTING\n"
        "-A POSTROUTING -j qcom_qos_reset_POSTROUTING\n"
        "-A POSTROUTING -j qcom_qos_filter_POSTROUTING\n";
    // and expect that we don't re-add the netd-created rules that already exist.
    DELETE_SUBSTRING("-A POSTROUTING -j oem_mangle_post\n", expected[14].second);
    DELETE_SUBSTRING("-A POSTROUTING -j bw_mangle_POSTROUTING\n", expected[14].second);
    DELETE_SUBSTRING("-A POSTROUTING -j idletimer_mangle_POSTROUTING\n", expected[14].second);

    // In this last case, also check that our expectations are reasonable.
    std::string expectedCmd14 =
        "*mangle\n"
        ":oem_mangle_post -\n"
        ":bw_mangle_POSTROUTING -\n"
        ":idletimer_mangle_POSTROUTING -\n"
        "COMMIT\n";
    ASSERT_EQ(expectedCmd14, expected[14].second);

    // Finally, actually test that initChildChains runs the expected commands, and nothing more.
    initChildChains();
    expectIptablesRestoreCommands(expected);
    expectIptablesRestoreCommands(ExpectedIptablesCommands{});
}

}  // namespace net
}  // namespace android