/* SPDX-License-Identifier: MPL-2.0 */

#include "precompiled.hpp"
#include <string>
#include <sstream>

#include "macros.hpp"
#include "udp_address.hpp"
#include "stdint.hpp"
#include "err.hpp"
#include "ip.hpp"

#ifndef ZMQ_HAVE_WINDOWS
#include <sys/types.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include <ctype.h>
#endif

zmq::udp_address_t::udp_address_t () :
    _bind_interface (-1), _is_multicast (false)
{
    _bind_address = ip_addr_t::any (AF_INET);
    _target_address = ip_addr_t::any (AF_INET);
}

zmq::udp_address_t::~udp_address_t ()
{
}

int zmq::udp_address_t::resolve (const char *name_, bool bind_, bool ipv6_)
{
    //  No IPv6 support yet
    bool has_interface = false;

    _address = name_;

    //  If we have a semicolon then we should have an interface specifier in the
    //  URL
    const char *src_delimiter = strrchr (name_, ';');
    if (src_delimiter) {
        const std::string src_name (name_, src_delimiter - name_);

        ip_resolver_options_t src_resolver_opts;

        src_resolver_opts
          .bindable (true)
          //  Restrict hostname/service to literals to avoid any DNS
          //  lookups or service-name irregularity due to
          //  indeterminate socktype.
          .allow_dns (false)
          .allow_nic_name (true)
          .ipv6 (ipv6_)
          .expect_port (false);

        ip_resolver_t src_resolver (src_resolver_opts);

        const int rc = src_resolver.resolve (&_bind_address, src_name.c_str ());

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

        if (_bind_address.is_multicast ()) {
            //  It doesn't make sense to have a multicast address as a source
            errno = EINVAL;
            return -1;
        }

        //  This is a hack because we need the interface index when binding
        //  multicast IPv6, we can't do it by address. Unfortunately for the
        //  time being we don't have a generic platform-independent function to
        //  resolve an interface index from an address, so we only support it
        //  when an actual interface name is provided.
        if (src_name == "*") {
            _bind_interface = 0;
        } else {
#ifdef HAVE_IF_NAMETOINDEX
            _bind_interface = if_nametoindex (src_name.c_str ());
            if (_bind_interface == 0) {
                //  Error, probably not an interface name.
                _bind_interface = -1;
            }
#endif
        }

        has_interface = true;
        name_ = src_delimiter + 1;
    }

    ip_resolver_options_t resolver_opts;

    resolver_opts.bindable (bind_)
      .allow_dns (true)
      .allow_nic_name (bind_)
      .expect_port (true)
      .ipv6 (ipv6_);

    ip_resolver_t resolver (resolver_opts);

    const int rc = resolver.resolve (&_target_address, name_);
    if (rc != 0) {
        return -1;
    }

    _is_multicast = _target_address.is_multicast ();
    const uint16_t port = _target_address.port ();

    if (has_interface) {
        //  If we have an interface specifier then the target address must be a
        //  multicast address
        if (!_is_multicast) {
            errno = EINVAL;
            return -1;
        }

        _bind_address.set_port (port);
    } else {
        //  If we don't have an explicit interface specifier then the URL is
        //  ambiguous: if the target address is multicast then it's the
        //  destination address and the bind address is ANY, if it's unicast
        //  then it's the bind address when 'bind_' is true and the destination
        //  otherwise
        if (_is_multicast || !bind_) {
            _bind_address = ip_addr_t::any (_target_address.family ());
            _bind_address.set_port (port);
            _bind_interface = 0;
        } else {
            //  If we were asked for a bind socket and the address
            //  provided was not multicast then it was really meant as
            //  a bind address and the target_address is useless.
            _bind_address = _target_address;
        }
    }

    if (_bind_address.family () != _target_address.family ()) {
        errno = EINVAL;
        return -1;
    }

    //  For IPv6 multicast we *must* have an interface index since we can't
    //  bind by address.
    if (ipv6_ && _is_multicast && _bind_interface < 0) {
        errno = ENODEV;
        return -1;
    }

    return 0;
}

int zmq::udp_address_t::family () const
{
    return _bind_address.family ();
}

bool zmq::udp_address_t::is_mcast () const
{
    return _is_multicast;
}

const zmq::ip_addr_t *zmq::udp_address_t::bind_addr () const
{
    return &_bind_address;
}

int zmq::udp_address_t::bind_if () const
{
    return _bind_interface;
}

const zmq::ip_addr_t *zmq::udp_address_t::target_addr () const
{
    return &_target_address;
}

int zmq::udp_address_t::to_string (std::string &addr_)
{
    // XXX what do (factor TCP code?)
    addr_ = _address;
    return 0;
}
