#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