// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.

// Licensed under the Apache License, Version 2.0 (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 "ip_utilities.h"

#include "connkey.h"

using namespace std;

// LCOV_EXCL_START Reason: temporary until we add relevant UT until 07/10
bool
operator<(const IpAddress &this_ip_addr, const IpAddress &other_ip_addr)
{
    if (this_ip_addr.ip_type < other_ip_addr.ip_type) return true;
    if (this_ip_addr.ip_type > other_ip_addr.ip_type) return false;
    if (this_ip_addr.ip_type == IP_VERSION_4) {
        return ntohl(this_ip_addr.addr4_t.s_addr) < ntohl(other_ip_addr.addr4_t.s_addr);
    }
    return memcmp(&this_ip_addr.addr6_t, &other_ip_addr.addr6_t, sizeof(struct in6_addr)) < 0;
}

bool
operator==(const IpAddress &this_ip_addr, const IpAddress &other_ip_addr)
{
    if (this_ip_addr.ip_type != other_ip_addr.ip_type) return false;
    if (this_ip_addr.ip_type == IP_VERSION_4) return this_ip_addr.addr4_t.s_addr == other_ip_addr.addr4_t.s_addr;
    return memcmp(&this_ip_addr.addr6_t, &other_ip_addr.addr6_t, sizeof(struct in6_addr)) == 0;
}

bool
operator<=(const IpAddress &this_ip_addr, const IpAddress &other_ip_addr)
{
    if (this_ip_addr < other_ip_addr || this_ip_addr == other_ip_addr) return true;
    return false;
}

bool
operator<(const IPRange &range1, const IPRange &range2)
{
    return range1.start < range2.start || (range1.start == range2.start && range1.end < range2.end);
}
// LCOV_EXCL_STOP

Maybe<pair<string, int>>
extractAddressAndMaskSize(const string &cidr)
{
    size_t delimiter_pos = cidr.find("/");
    if (delimiter_pos == string::npos) return genError("provided value is not in CIDR notation: " + cidr);
    string address = cidr.substr(0, delimiter_pos);
    string mask_size = cidr.substr(delimiter_pos + 1, cidr.size() - delimiter_pos - 1);
    try {
        return make_pair(address, stoi(mask_size));
    } catch(...) {
        return genError("failed to cast provided value to integer: " + mask_size);
    }
    return genError("failed to parse provided string as a CIDR: " + cidr);
}

template<typename Integer>
pair<Integer, Integer>
applyMaskOnAddress(const vector<Integer> &oct, Integer mask)
{
    Integer start = (oct[0] | oct[1] | oct[2] | oct[3]) & mask;
    Integer end = (oct[0] | oct[1] | oct[2] | oct[3]) | (~mask);
    return make_pair(start, end);
}

Maybe<pair<string, string>>
createRangeFromCidrV4(const pair<string, int> &cidr_values)
{
    string address = cidr_values.first;
    int mask_size = cidr_values.second;
    vector<uint32_t> oct;
    for (int i=3; i>=0; i--) {
        size_t delimiter_pos = address.find(".");
        string oct_str = address.substr(0, delimiter_pos);
        try {
            oct.push_back(static_cast<uint32_t>(stoul(oct_str)) << (i * 8));
        } catch (...) {
            return genError("failed to cast provided value to integer: " + oct_str);
        }
        if ((i == 0) != (delimiter_pos == string::npos)) {
            return genError("provided value is not in a correct ipv4 structure: " + makeSeparatedStr(oct, "."));
        }
        address.erase(0, delimiter_pos + 1);
    }

    unsigned int mask = 0xffffffff;
    mask <<= (32 - mask_size);

    unsigned int start, end;
    tie(start, end) = applyMaskOnAddress<unsigned int>(oct, mask);

    auto construct_address = [](unsigned int value)
    {
        stringstream address_stream;
        for (int i = 3; i >= 0; i--) {
            address_stream << ((value >> (i * 8)) & 0xff) << (i > 0 ? "." : "");
        }
        return address_stream.str();
    };

    return make_pair<string, string>(construct_address(start), construct_address(end));
}

// LCOV_EXCL_START Reason: it is tested, but for some reason coverage doesn't catch it
Maybe<pair<string, string>>
createRangeFromCidrV6(const pair<string, int> &cidr_values)
{
    string address = cidr_values.first;
    int mask_size = cidr_values.second;
    // fill compressed zeros
    struct in6_addr v6;
    if (inet_pton(AF_INET6, address.c_str(), &v6) == -1) {
        return genError("faild to convert provided value to ipv6: " + address);
    };
    struct in6_addr *addr = &v6;
    vector<unsigned int> oct_from_str;
    for (int i=0; i<15; i+=2){
        char hex[8];
        unsigned int num;
        sprintf(hex, "%02x%02x", static_cast<int>(addr->s6_addr[i]), static_cast<int>(addr->s6_addr[i+1]));
        sscanf(hex, "%x", &num);
        oct_from_str.push_back(num);
    }

    uint64_t mask = 0xffffffffffffffff;
    function<string(uint64_t, bool)> construct_address;
    int oct_offset;

    if (mask_size > 64) {
        oct_offset = 7;
        mask <<= (128 - mask_size);
        construct_address = [oct_from_str](uint64_t value, bool is_start)
        {
            (void)is_start;
            stringstream address_stream;
            for (int i = 0; i < 4; i++) {
                address_stream << hex << oct_from_str[i] << ":";
            }
            for (int i = 3; i >= 0; i--) {
                address_stream << hex << (unsigned int)((value >> (i * 16)) & 0xffff) << (i > 0 ? ":" : "");
            }
            return address_stream.str();
        };
    } else {
        oct_offset = 3;
        mask <<= (64 - mask_size);
        construct_address = [](uint64_t value, bool is_start)
        {
            stringstream address_stream;
            for (int i = 3; i >= 0; i--) {
                address_stream << hex << (unsigned int)((value >> (i * 16)) & 0xffff) << ":";
            }
            address_stream << (is_start ? "0:0:0:0" : "ffff:ffff:ffff:ffff");
            return address_stream.str();
        };
    }

    uint64_t start, end;
    vector<uint64_t> oct;
    for (int i = 3; i >= 0; i--) {
        oct.push_back(static_cast<uint64_t>(oct_from_str[oct_offset - i]) << (i * 16));
    }
    tie(start, end) = applyMaskOnAddress<uint64_t>(oct, mask);
    return make_pair<string, string>(
        construct_address(start, true),
        construct_address(end, false)
    );
}
// LCOV_EXCL_STOP

namespace IPUtilities {
Maybe<map<IpAddress, string>>
getInterfaceIPs()
{
    struct ifaddrs *if_addr_list = nullptr;
    if (getifaddrs(&if_addr_list) == -1) {
        return genError(string("Failed to get interface IP's. Error: ") + strerror(errno));
    }

    map<IpAddress, string> interface_ips;
    for (struct ifaddrs *if_addr = if_addr_list; if_addr != nullptr; if_addr = if_addr->ifa_next) {
        if (if_addr->ifa_addr == nullptr) continue;
        if (if_addr->ifa_addr->sa_family != AF_INET && if_addr->ifa_addr->sa_family != AF_INET6) continue;

        char address_buffer[INET6_ADDRSTRLEN] = { '\0' };
        if (if_addr->ifa_addr->sa_family == AF_INET) {
            struct in_addr addr = reinterpret_cast<struct sockaddr_in *>(if_addr->ifa_addr)->sin_addr;
            inet_ntop(AF_INET, &addr, address_buffer, INET_ADDRSTRLEN);
            string address_string(address_buffer);
            if (address_string.find("127.0.0.1") != string::npos) continue;

            IpAddress ip_addr;
            ip_addr.ip_type = IP_VERSION_4;
            memcpy(&ip_addr.ip.ipv4, &addr, sizeof(ip_addr.ip.ipv4));
            interface_ips.emplace(ip_addr, address_string);
        } else {
            struct in6_addr addr = reinterpret_cast<struct sockaddr_in6 *>(if_addr->ifa_addr)->sin6_addr;
            inet_ntop(AF_INET6, &addr, address_buffer, INET6_ADDRSTRLEN);
            string address_string(address_buffer);
            if (address_string.find("::1") != string::npos) continue;

            IpAddress ip_addr;
            ip_addr.ip_type = IP_VERSION_6;
            memcpy(&ip_addr.ip.ipv6, &addr, sizeof(ip_addr.ip.ipv6));
            interface_ips.emplace(ip_addr, address_string);
        }
    }

    if (if_addr_list != nullptr) freeifaddrs(if_addr_list);

    return interface_ips;
}

Maybe<pair<string, string>>
createRangeFromCidr(const string &cidr)
{
    auto cidr_values = extractAddressAndMaskSize(cidr);
    if (!cidr_values.ok()) return genError("Failed to create range from Cidr: " + cidr_values.getErr());
    return cidr.find(".") != string::npos
        ? createRangeFromCidrV4(cidr_values.unpack())
        : createRangeFromCidrV6(cidr_values.unpack());
}

bool
isIpAddrInRange(const IPRange &rule_ip_range, const IpAddress &ip_addr)
{
    IpAddress min_ip = rule_ip_range.start;
    IpAddress max_ip = rule_ip_range.end;

    if (ip_addr.ip_type == IP_VERSION_4) {
        if (max_ip.ip_type != IP_VERSION_4) return 0;
        return
            memcmp(&ip_addr.ip.ipv4, &min_ip.ip.ipv4, sizeof(struct in_addr)) >= 0 &&
            memcmp(&ip_addr.ip.ipv4, &max_ip.ip.ipv4, sizeof(struct in_addr)) <= 0;
    }
    if (ip_addr.ip_type == IP_VERSION_6) {
        if (max_ip.ip_type != IP_VERSION_6) return 0;
        return
            memcmp(&ip_addr.ip.ipv6, &min_ip.ip.ipv6, sizeof(struct in6_addr)) >= 0 &&
            memcmp(&ip_addr.ip.ipv6, &max_ip.ip.ipv6, sizeof(struct in6_addr)) <= 0;
    }
    return 0;
}

string
IpAddrToString(const IpAddress &address)
{
    if (address.ip_type == IP_VERSION_6) {
        char ip_str[INET6_ADDRSTRLEN];
        struct sockaddr_in6 sa6;

        sa6.sin6_family = AF_INET6;
        sa6.sin6_addr = address.ip.ipv6;

        inet_ntop(AF_INET6, &(sa6.sin6_addr), ip_str, INET6_ADDRSTRLEN);
        return string(ip_str);
    }

    char ip_str[INET_ADDRSTRLEN];
    struct sockaddr_in sa;

    sa.sin_family = AF_INET;
    sa.sin_addr = address.ip.ipv4;

    inet_ntop(AF_INET, &(sa.sin_addr), ip_str, INET_ADDRSTRLEN);
    return string(ip_str);
}

IpAddress
createIpFromString(const string &ip_string)
{
    IpAddress res_address = {0, IP_VERSION_ANY};
    if (ip_string == "any") return res_address;
    auto  maybe_ip_addr = IPAddr::createIPAddr(ip_string);
    if (!maybe_ip_addr.ok()) {
        return res_address;
    }
    IPAddr ip_addr = maybe_ip_addr.unpack();
    res_address.ip_type = static_cast<IpVersion>(ip_addr.getType());
    if (ip_addr.getType() == IPType::V4) {
        res_address.addr4_t = ip_addr.getIPv4();
    } else {
        res_address.addr6_t = ip_addr.getIPv6();
    }
    return res_address;
}

IpAddress
ConvertToIpAddress(const IPAddr &addr) {
    IpAddress address;
    switch (addr.getType()) {
        case IPType::UNINITIALIZED: {
            address.addr4_t = {0};
            address.ip_type = IP_VERSION_ANY;
            break;
        }
        case IPType::V4: {
            address.addr4_t = addr.getIPv4(); // reference to a local variable ?
            address.ip_type = IP_VERSION_4;
            break;
        }
        case IPType::V6: {
            address.addr6_t = addr.getIPv6();
            address.ip_type = IP_VERSION_6;
            break;
        }
        default:
            dbgAssert(false) << AlertInfo(AlertTeam::CORE, "ip utilities") << "Unsupported IP type";
    }
    return address;
}

IpAttrFromString::operator Maybe<IpAddress>()
{
    auto ip_addr = IPAddr::createIPAddr(data);
    if (!ip_addr.ok()) return genError("Could not create IP address. Error: " + ip_addr.getErr());
    return ConvertToIpAddress(ip_addr.unpackMove());
}

IpAttrFromString::operator Maybe<IpProto>()
{
    int value;
    try {
        value = stoi(data);
    } catch (...) {
        return genError("provided value is not a legal number. Value: " + data);
    }

    if (value > static_cast<int>(UINT8_MAX) || value < 0) {
        return genError("provided value is not a legal ip protocol number. Value: " + data);
    }

    return static_cast<IpProto>(value);
}

IpAttrFromString::operator Maybe<Port>()
{
    int value;
    try {
        value = stoi(data);
    } catch (...) {
        return genError("provided value is not a legal number. Value: " + data);
    }

    if (value > static_cast<int>(UINT16_MAX) || value < 0) {
        return genError("provided value is not a legal port number. Value: " + data);
    }

    return static_cast<Port>(value);
}
}
