/*
 * Copyright (C) 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.
 */

#include <ifaddrs.h>
#include <net/if.h>
#include <sys/types.h>

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

#include <netdutils/MockSyscalls.h>

#include "InterfaceController.h"

using testing::ByMove;
using testing::Invoke;
using testing::Return;
using testing::StrictMock;
using testing::_;

namespace android {
namespace net {
namespace {

using netdutils::Fd;
using netdutils::ScopedMockSyscalls;
using netdutils::Slice;
using netdutils::Status;
using netdutils::StatusOr;
using netdutils::UniqueFd;
using netdutils::makeSlice;
using netdutils::status::ok;
using netdutils::statusFromErrno;

constexpr Fd kDevRandomFd(777);
constexpr Fd kStableSecretFd(9999);
const char kDevRandomPath[] = "/dev/random";
const char kTestIface[] = "wlan5";
const char kStableSecretProperty[] = "persist.netd.stable_secret";
const char kStableSecretPath[] = "/proc/sys/net/ipv6/conf/wlan5/stable_secret";
const char kTestIPv6Address[] = "\x20\x01\x0d\xb8\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10";
const char kTestIPv6AddressString[] = "2001:db8:506:708:90a:b0c:d0e:f10";

// getProperty() and setProperty() are forwarded to this mock
class MockProperties {
  public:
    MOCK_CONST_METHOD2(get, std::string(const std::string& key, const std::string& dflt));
    MOCK_CONST_METHOD2(set, Status(const std::string& key, const std::string& val));
};

}  // namespace

class StablePrivacyTest : public testing::Test {
  protected:
    void expectOpenFile(const std::string& path, const Fd fd, int err) {
        if (err == 0) {
            EXPECT_CALL(mSyscalls, open(path, _, _)).WillOnce(Return(ByMove(UniqueFd(fd))));
            EXPECT_CALL(mSyscalls, close(fd)).WillOnce(Return(ok));
        } else {
            EXPECT_CALL(mSyscalls, open(path, _, _))
                .WillOnce(Return(ByMove(statusFromErrno(err, "open() failed"))));
        }
    }

    void expectReadFromDevRandom(const std::string& data) {
        expectOpenFile(kDevRandomPath, kDevRandomFd, 0);
        EXPECT_CALL(mSyscalls, read(kDevRandomFd, _)).WillOnce(Invoke([data](Fd, const Slice buf) {
            EXPECT_EQ(data.size(), buf.size());
            return take(buf, copy(buf, makeSlice(data)));
        }));
    }

    void expectGetPropertyDefault(const std::string& key) {
        EXPECT_CALL(mProperties, get(key, _))
            .WillOnce(Invoke([](const std::string&, const std::string& dflt) { return dflt; }));
    }

    void expectGetProperty(const std::string& key, const std::string& val) {
        EXPECT_CALL(mProperties, get(key, _))
            .WillOnce(Invoke([val](const std::string&, const std::string&) { return val; }));
    }

    void expectSetProperty(const std::string& key, const std::string& val, Status status) {
        EXPECT_CALL(mProperties, set(key, val)).WillOnce(Return(status));
    }

    void expectWriteToFile(const Fd fd, const std::string& val, int err) {
        EXPECT_CALL(mSyscalls, write(fd, _))
            .WillOnce(Invoke([val, err](Fd, const Slice buf) -> StatusOr<size_t> {
                EXPECT_EQ(val, toString(buf));
                if (err) {
                    return statusFromErrno(err, "write() failed");
                }
                return val.size();
            }));
    }

    Status enableStablePrivacyAddresses(const std::string& iface) {
        return InterfaceController::enableStablePrivacyAddresses(iface, mGet, mSet);
    }

    StrictMock<ScopedMockSyscalls> mSyscalls;
    StrictMock<MockProperties> mProperties;

    const std::function<std::string(const std::string&, const std::string&)> mGet =
        [this](const std::string& key, const std::string& dflt) {
            return mProperties.get(key, dflt);
        };
    const std::function<Status(const std::string&, const std::string&)> mSet =
        [this](const std::string& key, const std::string& val) {
            return mProperties.set(key, val);
        };
};

TEST_F(StablePrivacyTest, PropertyOpenEnoent) {
    expectOpenFile(kStableSecretPath, kStableSecretFd, ENOENT);
    EXPECT_NE(ok, enableStablePrivacyAddresses(kTestIface));
}

TEST_F(StablePrivacyTest, PropertyOpenEaccess) {
    expectOpenFile(kStableSecretPath, kStableSecretFd, EACCES);
    EXPECT_NE(ok, enableStablePrivacyAddresses(kTestIface));
}

TEST_F(StablePrivacyTest, FirstBootWriteOkSetPropertyOk) {
    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
    expectGetPropertyDefault(kStableSecretProperty);
    expectReadFromDevRandom(kTestIPv6Address);
    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, 0);
    expectSetProperty(kStableSecretProperty, kTestIPv6AddressString, ok);
    EXPECT_EQ(ok, enableStablePrivacyAddresses(kTestIface));
}

TEST_F(StablePrivacyTest, FirstBootWriteOkSetPropertyFail) {
    const auto kError = statusFromErrno(EINVAL, "");
    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
    expectGetPropertyDefault(kStableSecretProperty);
    expectReadFromDevRandom(kTestIPv6Address);
    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, 0);
    expectSetProperty(kStableSecretProperty, kTestIPv6AddressString, kError);
    EXPECT_EQ(kError, enableStablePrivacyAddresses(kTestIface));
}

TEST_F(StablePrivacyTest, FirstBootWriteFail) {
    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
    expectGetPropertyDefault(kStableSecretProperty);
    expectReadFromDevRandom(kTestIPv6Address);
    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, ENOSPC);
    EXPECT_NE(ok, enableStablePrivacyAddresses(kTestIface));
}

TEST_F(StablePrivacyTest, ExistingPropertyWriteOk) {
    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
    expectGetProperty(kStableSecretProperty, kTestIPv6AddressString);
    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, 0);
    EXPECT_EQ(ok, enableStablePrivacyAddresses(kTestIface));
}

TEST_F(StablePrivacyTest, ExistingPropertyWriteFail) {
    expectOpenFile(kStableSecretPath, kStableSecretFd, 0);
    expectGetProperty(kStableSecretProperty, kTestIPv6AddressString);
    expectWriteToFile(kStableSecretFd, kTestIPv6AddressString, EACCES);
    EXPECT_NE(ok, enableStablePrivacyAddresses(kTestIface));
}

class GetIfaceListTest : public testing::Test {};

TEST_F(GetIfaceListTest, IfaceNames) {
    StatusOr<std::vector<std::string>> ifaceNames = InterfaceController::getIfaceNames();
    EXPECT_EQ(ok, ifaceNames.status());
    struct ifaddrs *ifaddr, *ifa;
    EXPECT_EQ(0, getifaddrs(&ifaddr));
    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        const auto val = std::find(
                ifaceNames.value().begin(), ifaceNames.value().end(), ifa->ifa_name);
        EXPECT_NE(ifaceNames.value().end(), val);
    }
    freeifaddrs(ifaddr);
}

TEST_F(GetIfaceListTest, IfaceExist) {
    StatusOr<std::map<std::string, uint32_t>> ifaceMap = InterfaceController::getIfaceList();
    EXPECT_EQ(ok, ifaceMap.status());
    struct ifaddrs *ifaddr, *ifa;
    EXPECT_EQ(0, getifaddrs(&ifaddr));
    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        uint32_t ifaceIndex = if_nametoindex(ifa->ifa_name);
        const auto ifacePair = ifaceMap.value().find(ifa->ifa_name);
        EXPECT_NE(ifaceMap.value().end(), ifacePair);
        EXPECT_EQ(ifaceIndex, ifacePair->second);
    }
    freeifaddrs(ifaddr);
}

}  // namespace net
}  // namespace android