/*
    Copyright (c) 2007-2009 FastMQ Inc.
    This file is part of 0MQ.
    0MQ is free software; you can redistribute it and/or modify it under
    the terms of the Lesser GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    0MQ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    Lesser GNU General Public License for more details.
    You should have received a copy of the Lesser GNU General Public License
    along with this program.  If not, see .
*/
#include 
#include 
#include 
#include 
#include "ip.hpp"
#include "platform.hpp"
#include "err.hpp"
#include "stdint.hpp"
#if defined ZMQ_HAVE_SOLARIS
#include 
#include 
#include 
//  On Solaris platform, network interface name can be queried by ioctl.
static int resolve_nic_name (in_addr* addr_, char const *interface_)
{
    //  * resolves to INADDR_ANY
    if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) {
        addr_->s_addr = htonl (INADDR_ANY);
        return 0;
    }
    //  Create a socket.
    int fd = socket (AF_INET, SOCK_DGRAM, 0);
    zmq_assert (fd != -1);
    //  Retrieve number of interfaces.
    lifnum ifn;
    ifn.lifn_family = AF_UNSPEC;
    ifn.lifn_flags = 0;
    int rc = ioctl (fd, SIOCGLIFNUM, (char*) &ifn);
    zmq_assert (rc != -1);
    //  Allocate memory to get interface names.
    size_t ifr_size = sizeof (struct lifreq) * ifn.lifn_count;
    char *ifr = (char*) malloc (ifr_size);
    errno_assert (ifr);
    
    //  Retrieve interface names.
    lifconf ifc;
    ifc.lifc_family = AF_UNSPEC;
    ifc.lifc_flags = 0;
    ifc.lifc_len = ifr_size;
    ifc.lifc_buf = ifr;
    rc = ioctl (fd, SIOCGLIFCONF, (char*) &ifc);
    zmq_assert (rc != -1);
    //  Find the interface with the specified name and AF_INET family.
    bool found = false;
    lifreq *ifrp = ifc.lifc_req;
    for (int n = 0; n < (int) (ifc.lifc_len / sizeof (lifreq));
          n ++, ifrp ++) {
        if (!strcmp (interface_, ifrp->lifr_name)) {
            rc = ioctl (fd, SIOCGLIFADDR, (char*) ifrp);
            zmq_assert (rc != -1);
            if (ifrp->lifr_addr.ss_family == AF_INET) {
                *addr_ = ((sockaddr_in*) &ifrp->lifr_addr)->sin_addr;
                found = true;
                break;
            }
        }
    }
    //  Clean-up.
    free (ifr);
    close (fd);
    //  If interface was not found among interface names, we assume it's
    //  specified in the form of IP address.
    if (!found) {
        rc = inet_pton (AF_INET, interface_, addr_);
        if (rc != 1) {
            errno = EINVAL;
            return -1;
        }
    }
    return 0;
}
#elif defined ZMQ_HAVE_AIX || ZMQ_HAVE_HPUX
#include 
#include 
#include 
#include 
#include "formatting.hpp"
static int resolve_nic_name (in_addr* addr_, char const *interface_)
{
    //  * resolves to INADDR_ANY
    if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) {
        addr_->s_addr = htonl (INADDR_ANY);
        return 0;
    }
    //  Create a socket.
    int sd = socket (AF_INET, SOCK_DGRAM, 0);
    zmq_assert (sd != -1);
    struct ifreq ifr; 
    //  Copy interface name for ioctl get.
    zmq_strncpy (ifr.ifr_name, interface_, sizeof (ifr.ifr_name));
    //  Fetch interface address.
    int rc = ioctl (sd, SIOCGIFADDR, (caddr_t) &ifr, sizeof (struct ifreq));
    if(rc != -1) {
        struct sockaddr *sa = (struct sockaddr *) &ifr.ifr_addr;
        *addr_ = ((sockaddr_in*)sa)->sin_addr;
    }
    else {
        //  Assume interface_ is in IP format xxx.xxx.xxx.xxx.
        rc = inet_pton (AF_INET, interface_, addr_);
        if (rc != 0) {
            errno = EINVAL;
            return -1;
        }
    }
    //  Clean up.
    close (sd);
    return 0;
}
#elif defined ZMQ_HAVE_WINDOWS
static int resolve_nic_name (in_addr* addr_, char const *interface_)
{
    //  * resolves to INADDR_ANY
    if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) {
        addr_->s_addr = htonl (INADDR_ANY);
        return 0;
    }
    //  Windows doesn't use sensible NIC names. Thus, we expect IP address of
    //  the NIC instead.
    in_addr addr;
    ((sockaddr_in*) addr_)->sin_family = AF_INET;
    addr.S_un.S_addr = inet_addr ((const char *) interface_);
    if (addr.S_un.S_addr == INADDR_NONE) {
        errno = EINVAL;
        return -1;
    }
    *addr_ = addr;
    return 0;
}
#elif ((defined ZMQ_HAVE_LINUX || defined ZMQ_HAVE_FREEBSD ||\
    defined ZMQ_HAVE_OSX || defined ZMQ_HAVE_OPENBSD ||\
    defined ZMQ_HAVE_QNXNTO) && defined ZMQ_HAVE_IFADDRS)
#include 
//  On these platforms, network interface name can be queried
//  using getifaddrs function.
static int resolve_nic_name (in_addr* addr_, char const *interface_)
{
    //  * resolves to INADDR_ANY
    if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) {
        addr_->s_addr = htonl (INADDR_ANY);
        return 0;
    }
    //  Initialuse the output parameter.
    memset (addr_, 0, sizeof (in_addr));
    //  Get the addresses.
    ifaddrs* ifa = NULL;
    int rc = getifaddrs (&ifa);
    zmq_assert (rc == 0);    
    zmq_assert (ifa != NULL);
    //  Find the corresponding network interface.
    bool found = false;
    for (ifaddrs *ifp = ifa; ifp != NULL ;ifp = ifp->ifa_next)
        if (ifp->ifa_addr && ifp->ifa_addr->sa_family == AF_INET 
            && !strcmp (interface_, ifp->ifa_name)) 
        {
            *addr_ = ((sockaddr_in*) ifp->ifa_addr)->sin_addr;
            found = true;
        }
    //  Clean-up;
    freeifaddrs (ifa);
    //  If interface was not found among interface names, we assume it's
    //  specified in the form of IP address.
    if (!found) {
        rc = inet_pton (AF_INET, interface_, addr_);
        if (rc != 1) {
            errno = EINVAL;
            return -1;
        }
    }
    return 0;
}
#else
//  On other platforms interface name is interpreted as raw IP address.
static int resolve_nic_name (in_addr* addr_, char const *interface_)
{
    //  * resolves to INADDR_ANY
    if (!interface_ || (strlen (interface_) == 1 && *interface_ == '*')) {
        addr_->s_addr = htonl (INADDR_ANY);
        return 0;
    }
    //  Convert IP address into sockaddr_in structure.
    int rc = inet_pton (AF_INET, interface_, addr_);
    zmq_assert (rc != 0);
    errno_assert (rc == 1);
    return 0;
}
#endif
int zmq::resolve_ip_interface (sockaddr_in* addr_, char const *interface_)
{
    //  Find the ':' that separates NIC name from port.
    const char *delimiter = strchr (interface_, ':');
    if (!delimiter) {
        errno = EINVAL;
        return -1;
    }
    //  Clean the structure and fill in protocol family.
    memset (addr_, 0, sizeof (sockaddr_in));
    addr_->sin_family = AF_INET;
    //  Resolve the name of the NIC.
    std::string nic_name (interface_, delimiter - interface_);
    if (resolve_nic_name (&addr_->sin_addr, nic_name.c_str ()) != 0)
        return -1;
    //  Resolve the port.
    addr_->sin_port = htons ((uint16_t) atoi (delimiter + 1));
    if (!addr_->sin_port) {
        errno = EINVAL;
        return 0;
    }
    return 0;
}
int zmq::resolve_ip_hostname (sockaddr_in *addr_, const char *hostname_)
{
    //  Find the ':' that separates hostname name from port.
    const char *delimiter = strchr (hostname_, ':');
    if (!delimiter) {
        errno = EINVAL;
        return -1;
    }
    //  Separate the hostname.
    std::string hostname (hostname_, delimiter - hostname_);
    //  Resolve host name. Some of the error info is lost in case of error,
    //  however, there's no way to report EAI errors via errno.
    addrinfo req;
    memset (&req, 0, sizeof (req));
    req.ai_family = AF_INET;
    addrinfo *res;
    int rc = getaddrinfo (hostname.c_str (), NULL, &req, &res);
    if (rc) {
        errno = EINVAL;
        return -1;
    }
    zmq_assert (res->ai_addr->sa_family == AF_INET);
    memcpy (addr_, res->ai_addr, sizeof (sockaddr_in));
    freeaddrinfo (res);
    
    //  Fill in the port number.
    addr_->sin_port = htons ((uint16_t) atoi (delimiter + 1));
    if (!addr_->sin_port) {
        errno = EINVAL;
        return -1;
    }
    return 0;
}