// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

// Date: Mon. Nov 7 14:47:36 CST 2011

#include "butil/compat.h"
#include <arpa/inet.h>                         // inet_pton, inet_ntop
#include <netdb.h>                             // gethostbyname_r
#include <unistd.h>                            // gethostname
#include <errno.h>                             // errno
#include <string.h>                            // strcpy
#include <stdio.h>                             // snprintf
#include <stdlib.h>                            // strtol
#include <sys/un.h>                            // sockaddr_un
#include <sys/socket.h>                        // SO_REUSEADDR SO_REUSEPORT
#include <memory>
#include <gflags/gflags.h>
#include <sys/poll.h>
#if defined(OS_MACOSX)
#include <sys/event.h>                           // kevent(), kqueue()
#endif
#include "butil/build_config.h"                // OS_MACOSX
#include "butil/fd_guard.h"                    // fd_guard
#include "butil/endpoint.h"                    // ip_t
#include "butil/logging.h"
#include "butil/memory/singleton_on_pthread_once.h"
#include "butil/strings/string_piece.h"
#include "butil/fd_utility.h"
#include "butil/memory/scope_guard.h"

//supported since Linux 3.9.
DEFINE_bool(reuse_port, false, "Enable SO_REUSEPORT for all listened sockets");

DEFINE_bool(reuse_addr, true, "Enable SO_REUSEADDR for all listened sockets");

DEFINE_bool(reuse_uds_path, false, "remove unix domain socket file before listen to it");

__BEGIN_DECLS
int BAIDU_WEAK bthread_connect(
    int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen) {
    return ::connect(sockfd, serv_addr, addrlen);
}

int BAIDU_WEAK bthread_timed_connect(
    int sockfd, const struct sockaddr* serv_addr,
    socklen_t addrlen, const timespec* abstime);

__END_DECLS

#include "butil/details/extended_endpoint.hpp"

namespace butil {

using details::ExtendedEndPoint;

static void set_endpoint(EndPoint* ep, ip_t ip, int port) {
    ep->ip = ip;
    ep->port = port;
    if (ExtendedEndPoint::is_extended(*ep)) {
        ExtendedEndPoint* eep = ExtendedEndPoint::address(*ep);
        if (eep) {
            eep->inc_ref();
        } else {
            ep->ip = IP_ANY;
            ep->port = 0;
        }
    }
}

void EndPoint::reset(void) {
    if (ExtendedEndPoint::is_extended(*this)) {
        ExtendedEndPoint* eep = ExtendedEndPoint::address(*this);
        if (eep) {
            eep->dec_ref();
        }
    }
    ip = IP_ANY;
    port = 0;
}

EndPoint::EndPoint(ip_t ip2, int port2) : ip(ip2), port(port2) {
    // Should never construct an extended endpoint by this way
    if (ExtendedEndPoint::is_extended(*this)) {
        CHECK(0) << "EndPoint construct with value that points to an extended EndPoint";
        ip = IP_ANY;
        port = 0;
    }
}

EndPoint::EndPoint(const EndPoint& rhs) {
    set_endpoint(this, rhs.ip, rhs.port);
}

EndPoint::~EndPoint() {
    reset();
}

void EndPoint::operator=(const EndPoint& rhs) {
    reset();
    set_endpoint(this, rhs.ip, rhs.port);
}

int str2ip(const char* ip_str, ip_t* ip) {
    // ip_str can be NULL when called by EndPoint(0, ...)
    if (ip_str != NULL) {
        for (; isspace(*ip_str); ++ip_str);
        int rc = inet_pton(AF_INET, ip_str, ip);
        if (rc > 0) {
            return 0;
        }
    }
    return -1;
}

IPStr ip2str(ip_t ip) {
    IPStr str;
    if (inet_ntop(AF_INET, &ip, str._buf, INET_ADDRSTRLEN) == NULL) {
        return ip2str(IP_NONE);
    }
    return str;
}

int ip2hostname(ip_t ip, char* host, size_t host_len) {
    if (host == NULL || host_len == 0) {
        errno = EINVAL;
        return -1;
    }
    sockaddr_in sa;
    bzero((char*)&sa, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = 0;    // useless since we don't need server_name
    sa.sin_addr = ip;
    if (getnameinfo((const sockaddr*)&sa, sizeof(sa),
                    host, host_len, NULL, 0, NI_NAMEREQD) != 0) {
        return -1;
    }
    // remove baidu-specific domain name (that every name has)
    butil::StringPiece str(host);
    if (str.ends_with(".baidu.com")) {
        host[str.size() - 10] = '\0';
    }
    return 0;
}

int ip2hostname(ip_t ip, std::string* host) {
    char buf[128];
    if (ip2hostname(ip, buf, sizeof(buf)) == 0) {
        host->assign(buf);
        return 0;
    }
    return -1;
}

EndPointStr endpoint2str(const EndPoint& point) {
    EndPointStr str;
    if (ExtendedEndPoint::is_extended(point)) {
        ExtendedEndPoint* eep = ExtendedEndPoint::address(point);
        if (eep) {
            eep->to(&str);
        } else {
            str._buf[0] = '\0';
        }
        return str;
    }
    if (inet_ntop(AF_INET, &point.ip, str._buf, INET_ADDRSTRLEN) == NULL) {
        return endpoint2str(EndPoint(IP_NONE, 0));
    }
    char* buf = str._buf + strlen(str._buf);
    *buf++ = ':';
    snprintf(buf, 16, "%d", point.port);
    return str;
}

int hostname2ip(const char* hostname, ip_t* ip) {
    char buf[256];
    if (NULL == hostname) {
        if (gethostname(buf, sizeof(buf)) < 0) {
            return -1;
        }
        hostname = buf;
    } else {
        // skip heading space
        for (; isspace(*hostname); ++hostname);
    }

#if defined(OS_MACOSX)
    // gethostbyname on MAC is thread-safe (with current usage) since the
    // returned hostent is TLS. Check following link for the ref:
    // https://lists.apple.com/archives/darwin-dev/2006/May/msg00008.html
    struct hostent* result = gethostbyname(hostname);
    if (result == NULL) {
        return -1;
    }
#else
    int aux_buf_len = 1024;
    std::unique_ptr<char[]> aux_buf(new char[aux_buf_len]);
    int ret = 0;
    int error = 0;
    struct hostent ent;
    struct hostent* result = NULL;
    do {
        result = NULL;
        error = 0;
        ret = gethostbyname_r(hostname,
                              &ent,
                              aux_buf.get(),
                              aux_buf_len,
                              &result,
                              &error);
        if (ret != ERANGE) { // aux_buf is not long enough
            break;
        }
        aux_buf_len *= 2;
        aux_buf.reset(new char[aux_buf_len]);
    } while (1);
    if (ret != 0 || result == NULL) {
        return -1;
    }
#endif // defined(OS_MACOSX)
    // Only fetch the first address here
    bcopy((char*)result->h_addr, (char*)ip, result->h_length);
    return 0;
}

struct MyAddressInfo {
    char my_hostname[256];
    ip_t my_ip;
    IPStr my_ip_str;

    MyAddressInfo() {
        my_ip = IP_ANY;
        if (gethostname(my_hostname, sizeof(my_hostname)) < 0) {
            my_hostname[0] = '\0';
        } else if (hostname2ip(my_hostname, &my_ip) != 0) {
            my_ip = IP_ANY;
        }
        my_ip_str = ip2str(my_ip);
    }
};

ip_t my_ip() {
    return get_leaky_singleton<MyAddressInfo>()->my_ip;
}

const char* my_ip_cstr() {
    return get_leaky_singleton<MyAddressInfo>()->my_ip_str.c_str();
}

const char* my_hostname() {
    return get_leaky_singleton<MyAddressInfo>()->my_hostname;
}

int str2endpoint(const char* str, EndPoint* point) {
    if (ExtendedEndPoint::create(str, point)) {
        return 0;
    }

    // Should be enough to hold ip address
    char buf[64];
    size_t i = 0;
    for (; i < sizeof(buf) && str[i] != '\0' && str[i] != ':'; ++i) {
        buf[i] = str[i];
    }
    if (i >= sizeof(buf) || str[i] != ':') {
        return -1;
    }
    buf[i] = '\0';
    if (str2ip(buf, &point->ip) != 0) {
        return -1;
    }
    ++i;
    char* end = NULL;
    point->port = strtol(str + i, &end, 10);
    if (end == str + i) {
        return -1;
    } else if (*end) {
        for (; isspace(*end); ++end);
        if (*end) {
            return -1;
        }
    }
    if (point->port < 0 || point->port > 65535) {
        return -1;
    }
    return 0;
}

int str2endpoint(const char* ip_str, int port, EndPoint* point) {
    if (ExtendedEndPoint::create(ip_str, port, point)) {
        return 0;
    }

    if (str2ip(ip_str, &point->ip) != 0) {
        return -1;
    }
    if (port < 0 || port > 65535) {
        return -1;
    }
    point->port = port;
    return 0;
}

int hostname2endpoint(const char* str, EndPoint* point) {
    // Should be enough to hold ip address
    // The definitive descriptions of the rules for forming domain names appear in RFC 1035, RFC 1123, RFC 2181,
    // and RFC 5892. The full domain name may not exceed the length of 253 characters in its textual representation
    // (Domain Names - Domain Concepts and Facilities. IETF. doi:10.17487/RFC1034. RFC 1034.).
    // For cacheline optimize, use buf size as 256;
    char buf[256];
    size_t i = 0;
    for (; i < MAX_DOMAIN_LENGTH && str[i] != '\0' && str[i] != ':'; ++i) {
        buf[i] = str[i];
    }

    if (i >= MAX_DOMAIN_LENGTH || str[i] != ':') {
        return -1;
    }

    buf[i] = '\0';
    if (hostname2ip(buf, &point->ip) != 0) {
        return -1;
    }
    if (str[i] == ':') {
        ++i;
    }
    char* end = NULL;
    point->port = strtol(str + i, &end, 10);
    if (end == str + i) {
        return -1;
    } else if (*end) {
        for (; isspace(*end); ++end);
        if (*end) {
            return -1;
        }
    }
    if (point->port < 0 || point->port > 65535) {
        return -1;
    }
    return 0;
}

int hostname2endpoint(const char* name_str, int port, EndPoint* point) {
    if (hostname2ip(name_str, &point->ip) != 0) {
        return -1;
    }
    if (port < 0 || port > 65535) {
        return -1;
    }
    point->port = port;
    return 0;
}

int endpoint2hostname(const EndPoint& point, char* host, size_t host_len) {
    if (ExtendedEndPoint::is_extended(point)) {
        ExtendedEndPoint* eep = ExtendedEndPoint::address(point);
        if (eep) {
            return eep->to_hostname(host, host_len);
        }
        return -1;
    }

    if (ip2hostname(point.ip, host, host_len) == 0) {
        size_t len = strlen(host);
        if (len + 1 < host_len) {
            snprintf(host + len, host_len - len, ":%d", point.port);
        }
        return 0;
    }
    return -1;
}

int endpoint2hostname(const EndPoint& point, std::string* host) {
    char buf[256];
    if (endpoint2hostname(point, buf, sizeof(buf)) == 0) {
        host->assign(buf);
        return 0;
    }
    return -1;
}

#if defined(OS_LINUX)
static short epoll_to_poll_events(uint32_t epoll_events) {
    // Most POLL* and EPOLL* are same values.
    short poll_events = (epoll_events &
                         (EPOLLIN | EPOLLPRI | EPOLLOUT |
                          EPOLLRDNORM | EPOLLRDBAND |
                          EPOLLWRNORM | EPOLLWRBAND |
                          EPOLLMSG | EPOLLERR | EPOLLHUP));
    CHECK_EQ((uint32_t)poll_events, epoll_events);
    return poll_events;
}
#elif defined(OS_MACOSX)
short kqueue_to_poll_events(int kqueue_events) {
    //TODO: add more values?
    short poll_events = 0;
    if (kqueue_events == EVFILT_READ) {
        poll_events |= POLLIN;
    }
    if (kqueue_events == EVFILT_WRITE) {
        poll_events |= POLLOUT;
    }
    return poll_events;
}
#endif

int pthread_fd_wait(int fd, unsigned events,
                    const timespec* abstime) {
#if defined(OS_LINUX)
    const short poll_events = epoll_to_poll_events(events);
#elif defined(OS_MACOSX)
    const short poll_events = kqueue_to_poll_events(events);
#endif
    if (poll_events == 0) {
        errno = EINVAL;
        return -1;
    }
    pollfd ufds = { fd, poll_events, 0 };
    int64_t abstime_us = -1;
    if (NULL != abstime) {
        abstime_us = butil::timespec_to_microseconds(*abstime);
    }
    while (true) {
        int diff_ms = -1;
        if (NULL != abstime) {
            int64_t now_us = butil::gettimeofday_us();
            if (abstime_us <= now_us) {
                errno = ETIMEDOUT;
                return -1;
            }
            diff_ms = (abstime_us - now_us + 999L) / 1000L;
        }
        int rc = poll(&ufds, 1, diff_ms);
        if (rc > 0) {
            break;
        } else if (rc == 0) {
            errno = ETIMEDOUT;
            return -1;
        } else {
            if (errno == EINTR) {
                continue;
            }
            return -1;
        }
    }
    if (ufds.revents & POLLNVAL) {
        errno = EBADF;
        return -1;
    }
    return 0;
}

int pthread_timed_connect(int sockfd, const struct sockaddr* serv_addr,
                          socklen_t addrlen, const timespec* abstime) {
    bool is_blocking = butil::is_blocking(sockfd);
    if (is_blocking) {
        butil::make_non_blocking(sockfd);
    }
    // Scoped non-blocking.
    BRPC_SCOPE_EXIT {
        if (is_blocking) {
            butil::make_blocking(sockfd);
        }
    };

    const int rc = ::connect(sockfd, serv_addr, addrlen);
    if (rc == 0 || errno != EINPROGRESS) {
        return rc;
    }
#if defined(OS_LINUX)
    if (pthread_fd_wait(sockfd, EPOLLOUT, abstime) < 0) {
#elif defined(OS_MACOSX)
    if (pthread_fd_wait(sockfd, EVFILT_WRITE, abstime) < 0) {
#endif
        return -1;
    }

    if (is_connected(sockfd) != 0) {
        return -1;
    }
    return 0;
}

int tcp_connect(EndPoint server, int* self_port) {
    return tcp_connect(server, self_port, -1);
}

int tcp_connect(const EndPoint& server, int* self_port, int connect_timeout_ms) {
    struct sockaddr_storage serv_addr{};
    socklen_t serv_addr_size = 0;
    if (endpoint2sockaddr(server, &serv_addr, &serv_addr_size) != 0) {
        return -1;
    }
    fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0));
    if (sockfd < 0) {
        return -1;
    }
    timespec abstime{};
    timespec* abstime_ptr = NULL;
    if (connect_timeout_ms > 0) {
        abstime = butil::milliseconds_from_now(connect_timeout_ms);
        abstime_ptr = &abstime;
    }
    int rc;
    if (bthread_timed_connect != NULL) {
        rc = bthread_timed_connect(sockfd, (struct sockaddr*)&serv_addr,
                                   serv_addr_size, abstime_ptr);
    } else {
        rc = pthread_timed_connect(sockfd, (struct sockaddr*) &serv_addr,
                                   serv_addr_size, abstime_ptr);
    }
    if (rc < 0) {
        return -1;
    }
    if (self_port != NULL) {
        EndPoint pt;
        if (get_local_side(sockfd, &pt) == 0) {
            *self_port = pt.port;
        } else {
            CHECK(false) << "Fail to get the local port of sockfd=" << sockfd;
        }
    }
    return sockfd.release();
}

int tcp_listen(EndPoint point) {
    struct sockaddr_storage serv_addr;
    socklen_t serv_addr_size = 0;
    if (endpoint2sockaddr(point, &serv_addr, &serv_addr_size) != 0) {
        return -1;
    }
    fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0));
    if (sockfd < 0) {
        return -1;
    }

    if (FLAGS_reuse_addr) {
#if defined(SO_REUSEADDR)
        const int on = 1;
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
                       &on, sizeof(on)) != 0) {
            return -1;
        }
#else
        LOG(ERROR) << "Missing def of SO_REUSEADDR while -reuse_addr is on";
        return -1;
#endif
    }

    if (FLAGS_reuse_port) {
#if defined(SO_REUSEPORT)
        const int on = 1;
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT,
                       &on, sizeof(on)) != 0) {
            LOG(WARNING) << "Fail to setsockopt SO_REUSEPORT of sockfd=" << sockfd;
        }
#else
        LOG(ERROR) << "Missing def of SO_REUSEPORT while -reuse_port is on";
        return -1;
#endif
    }

    if (FLAGS_reuse_uds_path && serv_addr.ss_family == AF_UNIX) {
        ::unlink(((sockaddr_un*) &serv_addr)->sun_path);
    }

    if (::bind(sockfd, (struct sockaddr*)& serv_addr, serv_addr_size) != 0) {
        return -1;
    }
    if (listen(sockfd, 65535) != 0) {
        //             ^^^ kernel would silently truncate backlog to the value
        //             defined in /proc/sys/net/core/somaxconn if it is less
        //             than 65535
        return -1;
    }
    return sockfd.release();
}

int get_local_side(int fd, EndPoint *out) {
    struct sockaddr_storage addr;
    socklen_t socklen = sizeof(addr);
    const int rc = getsockname(fd, (struct sockaddr*)&addr, &socklen);
    if (rc != 0) {
        return rc;
    }
    if (out) {
        return sockaddr2endpoint(&addr, socklen, out);
    }
    return 0;
}

int get_remote_side(int fd, EndPoint *out) {
    struct sockaddr_storage addr;
    bzero(&addr, sizeof(addr));
    socklen_t socklen = sizeof(addr);
    const int rc = getpeername(fd, (struct sockaddr*)&addr, &socklen);
    if (rc != 0) {
        return rc;
    }
    if (out) {
        return sockaddr2endpoint(&addr, socklen, out);
    }
    return 0;
}

int endpoint2sockaddr(const EndPoint& point, struct sockaddr_storage* ss, socklen_t* size) {
    bzero(ss, sizeof(*ss));
    if (ExtendedEndPoint::is_extended(point)) {
        ExtendedEndPoint* eep = ExtendedEndPoint::address(point);
        if (!eep) {
            return -1;
        }
        int ret = eep->to(ss);
        if (ret < 0) {
            return -1;
        }
        if (size) {
            *size = static_cast<socklen_t>(ret);
        }
        return 0;
    }
    struct sockaddr_in* in4 = (struct sockaddr_in*) ss;
    in4->sin_family = AF_INET;
    in4->sin_addr = point.ip;
    in4->sin_port = htons(point.port);
    if (size) {
        *size = sizeof(*in4);
    }
    return 0;
}

int sockaddr2endpoint(struct sockaddr_storage* ss, socklen_t size, EndPoint* point) {
    if (ss->ss_family == AF_INET) {
        *point = EndPoint(*(sockaddr_in*)ss);
        return 0;
    }
    if (ExtendedEndPoint::create(ss, size, point)) {
        return 0;
    }
    return -1;
}

sa_family_t get_endpoint_type(const EndPoint& point) {
    if (ExtendedEndPoint::is_extended(point)) {
        ExtendedEndPoint* eep = ExtendedEndPoint::address(point);
        if (eep) {
            return eep->family();
        }
        return AF_UNSPEC;
    }
    return AF_INET;
}

bool is_endpoint_extended(const EndPoint& point) {
    return ExtendedEndPoint::is_extended(point);
}

}  // namespace butil
