/*
 *  JsonRpc-Cpp - JSON-RPC implementation.
 *  Copyright (C) 2008-2011 Sebastien Vincent <sebastien.vincent@cppextrem.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

/**
 * \file networking.cpp
 * \brief Networking utils.
 * \author Sebastien Vincent
 */

#include <cstdio>
#include <cstring>

#include "networking.h"

namespace networking
{
#ifdef _WIN32
  /**
   * \var wsaData
   * \brief MS Windows object to start
   * networking stuff.
   */
  static WSAData wsaData;
#endif

  bool init()
  {
    bool ret = false;

#ifdef _WIN32
    ret = (WSAStartup(MAKEWORD(2, 0), &wsaData) == 0);
#else
    /* unix-like */
    ret = true;
#endif

    return ret;
  }

  void cleanup()
  {
#ifdef _WIN32
    WSACleanup();
#endif
  }

  int connect(enum TransportProtocol protocol, const std::string& address,
      uint16_t port, struct sockaddr_storage* sockaddr, socklen_t* addrlen)
  {
    struct addrinfo hints;
    struct addrinfo* res = NULL;
    struct addrinfo* p = NULL;
    char service[8];
    int sock = -1;

    if(!port || address == "")
    {
      return -1;
    }

    snprintf(service, sizeof(service), "%u", port);
    service[sizeof(service)-1] = 0x00;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = protocol == UDP ? SOCK_DGRAM : SOCK_STREAM;
    hints.ai_protocol = protocol;
    hints.ai_flags = 0;

    if(getaddrinfo(address.c_str(), service, &hints, &res) != 0)
    {
      return -1;
    }

    for(p = res ; p ; p = p->ai_next)
    {
      sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);

      if(sock == -1)
      {
        continue;
      }

      if(protocol == TCP && ::connect(sock, (struct sockaddr*)p->ai_addr,
            p->ai_addrlen) == -1)
      {
        ::close(sock);
        sock = -1;
        continue;
      }

      if(sockaddr)
      {
        memcpy(sockaddr, p->ai_addr, p->ai_addrlen);
      }

      if(addrlen)
      {
        *addrlen = p->ai_addrlen;
      }

      /* ok so now we have a socket bound, break the loop */
      break;
    }

    freeaddrinfo(res);
    p = NULL;

    return sock;
  }

  int bind(enum TransportProtocol protocol, const std::string& address,
      uint16_t port, struct sockaddr_storage* sockaddr, socklen_t* addrlen)
  {
    struct addrinfo hints;
    struct addrinfo* res = NULL;
    struct addrinfo* p = NULL;
    char service[8];
    int sock = -1;

    if(!port || address == "")
    {
      return -1;
    }

    snprintf(service, sizeof(service), "%u", port);
    service[sizeof(service)-1] = 0x00;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = protocol == UDP ? SOCK_DGRAM : SOCK_STREAM;
    hints.ai_protocol = protocol;
    hints.ai_flags = AI_PASSIVE;

    if(getaddrinfo(address.c_str(), service, &hints, &res) != 0)
    {
      return -1;
    }

    for(p = res ; p ; p = p->ai_next)
    {
      int on = 1;

      sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);

      if(sock == -1)
      {
        continue;
      }

#ifndef _WIN32
      setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int));

      /* accept IPv6 OR IPv4 on the same socket */
      on = 1;
      setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
#else
      (void)on;
#endif

      if(::bind(sock, p->ai_addr, p->ai_addrlen) == -1)
      {
        ::close(sock);
        sock = -1;
        continue;
      }

      if(sockaddr)
      {
        memcpy(sockaddr, p->ai_addr, p->ai_addrlen);
      }
        
      if(addrlen)
      {
        *addrlen = p->ai_addrlen;
      }

      /* ok so now we have a socket bound, break the loop */
      break;
    }

    freeaddrinfo(res);
    p = NULL;

    return sock;
  }
} /* namespace networking */

