Newer
Older
Import / applications / HighwayDash / ports / Framework / Socket.h
#ifndef SOCKET_H
#define SOCKET_H


#include "Device.h"
#include <functional>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>


#ifdef _WIN32
// impl using WinSocks
#else
/*
class File : public Device
{
};
*/
class Socket : public Device
{
public:
  Socket() : m_fd(0) {}
  // for either letting user do their own opening, or for the server socket case when calling accept
  Socket(DeviceHandle fd) : m_fd(fd) {}

  ~Socket() {
    close();
  }
  bool open() override {
    m_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (m_fd <= 0)
      close();
    return m_fd != 0;
  }
  void close() override {
    if (m_fd > 0)
      ::close(m_fd);
    m_fd = 0;
  }
  bool makeNonBlocking() {
    if (m_fd) {
      int flags = fcntl(m_fd, F_GETFL, 0);
      flags = (flags == -1) ? 0 : flags;
      if (fcntl(m_fd, F_SETFL, flags | O_NONBLOCK) != -1)
        return true;
    }
    return false;
  }

  bool errorStatus()
  {
    int err = 0;
    socklen_t len = sizeof(int);
    if (getsockopt(request.sockfd, SOL_SOCKET, SO_ERROR, &err, &len) != 0 || err != 0)
      return request.HandleError("Could not make connection to '%s'\n", request.url.c_str());
  }

  size_t read(vector<uint8_t>& bytes, size_t count, size_t offset=0) override {
    bytes.reserve(offset + count);
    if (m_fd && bytes.capacity() >= (offset + count))
      return ::recv(m_fd, bytes.data() + offset, count, 0);
    return -1;
  }

  size_t write(const vector<uint8_t>& bytes, size_t offset=0) override {
    if (m_fd && bytes.size() > offset)
      return ::send(m_fd, bytes.data() + offset, bytes.size() - offset, 0);
    return 0;
  }

  DeviceHandle getDeviceHandle() override { return m_fd; }
private:
  DeviceHandle m_fd = 0;
};


class AddressResolver
{
public:
  const AddressResolver& get() { return s_resolver; }

  // The continuation in the callback could be same or different thread
  // Need some way to dispatch callbacks to other threads?
  void lookup(const char* hostname, std::function<void(bool okay, uint32_t ip)> callback) const {
    uint32_t ipAddress = 0;
    if (inet_aton(hostname, &ipAddres) == 0) // hostname is of the form "123.456.123.456"
      // trivial ip address as a string
      callback(true, ipAddress);
    else if (nameCache.contains(hostname))
      // return from the cache if we already looked it up before
      callback(true, nameCache[hostname]);
    else {
      // else we have to request it
      std::async([hostname, callback]() {
        struct addrinfo *res = nullptr;
        if (::getaddrinfo(hostname, "80", nullptr, &res) == 0 && res) { // All was good
          nameCache[hostname] = res->ai_addr.sin_addr.s_addr;
          callback(true, nameCache[hostname]);
        } else {
          printf("Could not resolve host: %s  error: %s\n", hostname, gai_strerror(status));
          callback(false, 0);
        }
        if (res)
          ::freeaddrinfo(res);
      });
    }
  }

private:
  AddressResolver();
  mutable std::map<std::string, uint32_t> nameCache;
  static AddressResolver s_resolver;
};


// Goes in C++ file
AddressResolver AddressResolver::s_resolver;


class ClientSocket : public Socket
{
public:
  void connect(const char* hostname, uint16_t port) {
    connectFinished = false;
    connectOkay = false;
    //std::thread thread(syncConnect, hostname, port);
    AddressResolver::get().lookup(hostname, [this, port](bool okay, uint32_t ipAddress) {
      if (okay) {
        struct sockaddr_in dest = { 0 }; 
        dest.sin_family = AF_INET;
        dest.sin_port = htons(port);
        dest.sin_addr.s_addr = ipAddress;
        if (::connect(m_fd, (struct sockaddr *)&dest, sizeof(struct sockaddr)) == 0 || errno == EINPROGRESS)
          connectOkay = true;
      }
      connectFinished = true;
    });
  }
  // 'Okay' here just means that there was no errors. The connect might still be in progress.
  // 'Finished' here just means the connect has been started (if 'Okay' is true)
  void connectStatus(bool& finished, bool& okay) {
    finished = connectFinished.load();
    okay = connectOkay.load();
  }
private:
  std::atomic<bool> connectFinished = false;
  std::atomic<bool> connectOkay = false;
};


class ServerSocket : public Socket
{
public:
  bool listen(uint16_t port) {
    struct sockaddr_in serv = { AF_INET, htons(port), { htonl(INADDR_ANY) }, { 0 } };
    ::bind(m_fd, (struct sockaddr *)&serv, sizeof(struct sockaddr));
    ::listen(m_fd, 1); // start listening, queue up to 1 pending connection
  }
  Socket accept() {
    struct sockaddr_in dest; /* socket info about the machine connecting to us */
    socklen_t socksize = sizeof(struct sockaddr_in);
    return Socket(accept(m_fd, (struct sockaddr *)&dest, &socksize));
  }
};

#endif


#endif // SOCKET_H