/* Blinker
   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
   a blinkenarea.org project */

#include <fcntl.h>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include "Io.h"
#include "Udp6Addr.h"
#include "Udp6Sock.h"

namespace Blinker {

/// constructor
Udp6Sock::Udp6Sock()
{
  int flags, opt;

  // create socket
  m_fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
  if (m_fd == -1)
    return;

  // switch to nonblocking mode
  flags = fcntl(m_fd, F_GETFL);
  if (flags == -1 || fcntl(m_fd, F_SETFL, flags | O_NONBLOCK) != 0) {
    close(m_fd);
    m_fd = -1;
    return;
  }

  // enable address re-use
  opt = 1;
  if (setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
    close(m_fd);
    m_fd = -1;
    return;
  }
}

/// destructor
Udp6Sock::~Udp6Sock()
{
  // exit if not initialized
  if (m_fd == -1)
    return;

  // shutdown and close socket
  shutdown(m_fd, SHUT_RDWR);
  close(m_fd);
  m_fd = -1;
}

/**
 * @brief get address the socket is bound to
 * @param[out] addr local addess the socket is bound to
 * @return if local address could be obtained
 */
bool Udp6Sock::getAddr(Udp6Addr &addr)
{
  struct sockaddr * sockaddr;
  socklen_t socklen;

  // exit with error if not initialized
  if (m_fd == -1)
    return false;

  // get local name of socket
  sockaddr = (struct sockaddr *)&addr.m_addr;
  socklen = sizeof(addr.m_addr);
  if (getsockname(m_fd, sockaddr, &socklen) != 0)
    return false;
  return socklen <= sizeof(addr.m_addr); // check if not truncated
}

/**
 * @brief bind socket
 * @param[in] addr local address to bind to
 * @return if binding succeeded
 */
bool Udp6Sock::bind(const Udp6Addr &addr)

{
  const struct sockaddr * sockaddr;
  socklen_t socklen;

  // exit with error if not initialized
  if (m_fd == -1)
    return false;

  // bind
  sockaddr = (const struct sockaddr *)&addr.m_addr;
  socklen = sizeof(addr.m_addr);
  return ::bind(m_fd, sockaddr, socklen) == 0;
}

/**
 * @brief send to socket
 * @param[in] data data to send in message
 * @param[in] addr address the message should be sent to
 * @return if the message could be sent
 */
bool Udp6Sock::send(const std::string &data, const Udp6Addr &addr)
{
  const struct sockaddr * sockaddr;
  socklen_t socklen;
  ssize_t len;

  // exit with error if not initialized
  if (m_fd == -1)
    return false;

  // send message
  sockaddr = (const struct sockaddr *)&addr.m_addr;
  socklen = sizeof(addr.m_addr);
  len = sendto(m_fd, data.c_str(), data.size(), 0, sockaddr, socklen);
  return len == (ssize_t)data.size();
}

/**
 * @brief receive from socket
 * @param[out] data data in received message
 * @param[out] addr address the message has been received from
 * @return if a message has been received
 */
bool Udp6Sock::recv(std::string &data, Udp6Addr &addr)
{
  char buf[65536];
  struct sockaddr * sockaddr;
  socklen_t socklen;
  ssize_t len;

  // exit with error if not initialized
  if (m_fd == -1)
    return false;

  // receive message
  sockaddr = (struct sockaddr *)&addr.m_addr;
  socklen = sizeof(addr.m_addr);
  len = recvfrom(m_fd, buf, sizeof(buf), 0, sockaddr, &socklen);
  if (len < 0 || socklen > sizeof(addr.m_addr)) // error or address truncated
    return false;
  data.assign(buf, len);
  return true;
}

} // namespace Blinker

