Initial emsocket, emcurl stub

This commit is contained in:
paradust7 2022-03-29 23:10:44 +00:00
parent 9b398d0894
commit ad3eb4cb1e
8 changed files with 344 additions and 0 deletions

4
CMakeLists.txt Normal file
View File

@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.10)
add_subdirectory(src)

2
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,2 @@
add_subdirectory(emsocket)
add_subdirectory(emcurl)

View File

@ -0,0 +1,7 @@
add_library(emcurl curl.cpp)
set_target_properties(emcurl PROPERTIES PUBLIC_HEADER "curl.h")
INSTALL(TARGETS emcurl
LIBRARY DESTINATION lib
PUBLIC_HEADER DESTINATION include/curl
)

9
src/emcurl/curl.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "curl.h"
struct curl_t {
int abc;
};
CURL *curl_easy_init() {
return nullptr;
}

9
src/emcurl/curl.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
extern "C" {
typedef struct curl_t CURL;
CURL *curl_easy_init();
}

View File

@ -0,0 +1,7 @@
add_library(emsocket emsocket.cpp)
set_target_properties(emsocket PROPERTIES PUBLIC_HEADER "emsocket.h")
INSTALL(TARGETS emsocket
LIBRARY DESTINATION lib
PUBLIC_HEADER DESTINATION include
)

248
src/emsocket/emsocket.cpp Normal file
View File

@ -0,0 +1,248 @@
/*
MIT License
Copyright (c) 2022 paradust7
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <deque>
#include <netinet/in.h>
#include <cassert>
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <chrono>
#include <random>
#include <unordered_map>
#include <cerrno>
#include <mutex>
#include <condition_variable>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#define EMSOCKET_INTERNAL
#include "emsocket.h"
// Browsers don't allow websocket servers, so simulate servers on `localhost` with a buffer instead.
static bool is_localhost(const struct sockaddr *addr, socklen_t addrlen) {
assert(addr->sa_family == AF_INET);
if (((sockaddr_in*)addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
return true;
}
return false;
}
static uint16_t get_port(const struct sockaddr *addr, socklen_t addrlen) {
assert(addr->sa_family == AF_INET);
return ntohs(((sockaddr_in*)addr)->sin_port);
}
// sockfd -> receive packet buffer
struct pkt {
uint16_t source_port;
std::vector<char> data;
pkt() = delete;
pkt(const pkt &) = delete;
pkt& operator=(const pkt &) = delete;
pkt(uint16_t port, const char *buf, size_t len)
: source_port(port), data(&buf[0], &buf[len]) { }
};
struct VirtualSocket {
std::mutex mutex;
std::condition_variable cv;
bool open = true;
uint16_t sport = 0;
std::deque<pkt> recvbuf;
};
// TODO: Reuse socket ids to avoid blowing up select
#define BASE_SOCKET_ID 100
// Protects the maps and id generation
static std::mutex mutex;
static std::vector<VirtualSocket*> socket_map;
static std::unordered_map<uint16_t, VirtualSocket*> port_map;
static unsigned int seed = 0;
static std::default_random_engine generator;
static std::uniform_int_distribution<uint16_t> randport(4096, 32000);
// Must be called while holding mutex
static uint16_t random_port() {
if (seed == 0) {
seed = std::chrono::system_clock::now().time_since_epoch().count();
generator.seed(seed);
}
uint16_t port;
do {
port = randport(generator);
} while (port_map.count(port) != 0);
return port;
}
static VirtualSocket *getvs(int sockfd) {
assert(sockfd >= BASE_SOCKET_ID);
int id = sockfd - BASE_SOCKET_ID;
assert(id < socket_map.size());
VirtualSocket* vs = socket_map[id];
assert(vs && vs->open);
return vs;
}
int emsocket_socket(int domain, int type, int protocol) {
const std::lock_guard<std::mutex> lock(mutex);
VirtualSocket* vs = new VirtualSocket();
int id = socket_map.size();
socket_map.push_back(vs);
return BASE_SOCKET_ID + id;
}
int emsocket_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) {
return -1;
}
int emsocket_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
const std::lock_guard<std::mutex> lock(mutex);
auto vs = getvs(sockfd);
assert(vs->sport == 0);
uint16_t port = get_port(addr, addrlen);
if (port == 0) {
port = random_port();
}
if (port_map.count(port)) {
errno = EADDRINUSE;
return -1;
}
vs->sport = port;
port_map[port] = vs;
return 0;
}
ssize_t emsocket_sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen) {
if (!is_localhost(dest_addr, addrlen)) {
// Sending to other than localhost not yet implemented
return 0;
}
uint16_t source_port;
uint16_t dest_port = get_port(dest_addr, addrlen);
VirtualSocket* dest_vs = nullptr;
{
const std::lock_guard<std::mutex> lock(mutex);
source_port = getvs(sockfd)->sport;
auto it = port_map.find(dest_port);
if (it != port_map.end()) {
dest_vs = it->second;
}
}
assert(source_port && "sendto before bind()");
if (!dest_vs) {
// Nothing is listening on localhost?
return 0;
}
// Lock destination vs
{
const std::lock_guard<std::mutex> lock(dest_vs->mutex);
dest_vs->recvbuf.emplace_back(source_port, (const char*)buf, len);
}
dest_vs->cv.notify_all();
//std::cout << "sockfd=" << sockfd << " Sent packet of size " << len << std::endl;
return len;
}
ssize_t emsocket_recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen) {
VirtualSocket *vs;
{
const std::lock_guard<std::mutex> lock(mutex);
vs = getvs(sockfd);
}
// For now, this should never be called in a blocking situation.
assert(vs->recvbuf.size() > 0);
const std::lock_guard<std::mutex> lock(vs->mutex);
const pkt &p = vs->recvbuf.front();
ssize_t written = std::min(p.data.size(), len);
bool truncated = (written != (ssize_t)p.data.size());
memcpy(buf, &p.data[0], written);
if (src_addr) {
struct sockaddr_in ai = {0};
ai.sin_family = AF_INET;
ai.sin_port = htons(p.source_port);
ai.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
memcpy(src_addr, &ai, std::min((size_t)*addrlen, sizeof(ai)));
*addrlen = sizeof(ai);
}
vs->recvbuf.pop_front();
if (truncated) errno = EMSGSIZE;
//std::cout << "sockfd=" << sockfd << " Received packet of size " << written << std::endl;
return written;
}
// Cheap hack
// Only supports select on a single fd
int emsocket_select(
int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout) {
int sockfd = nfds - 1;
assert(FD_ISSET(sockfd, readfds));
VirtualSocket *vs;
{
const std::lock_guard<std::mutex> lock(mutex);
vs = getvs(sockfd);
}
std::unique_lock<std::mutex> lock(vs->mutex);
if (timeout == NULL) {
vs->cv.wait(lock, [&]{ return vs->recvbuf.size() > 0; });
} else {
long ms = (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000);
auto cvtimeout = std::chrono::milliseconds(ms);
vs->cv.wait_for(lock, cvtimeout, [&]{ return vs->recvbuf.size() > 0; });
}
if (vs->recvbuf.size() == 0) {
return 0;
}
return 1;
}
int emsocket_close(int sockfd) {
const std::lock_guard<std::mutex> lock(mutex);
auto vs = getvs(sockfd);
if (vs->sport) {
port_map.erase(vs->sport);
vs->sport = 0;
}
vs->open = false;
vs->recvbuf.clear();
return 0;
}

58
src/emsocket/emsocket.h Normal file
View File

@ -0,0 +1,58 @@
/*
MIT License
Copyright (c) 2022 paradust7
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <sys/socket.h>
#include <sys/select.h>
extern "C" {
int emsocket_socket(int domain, int type, int protocol);
int emsocket_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen);
int emsocket_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
ssize_t emsocket_sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t emsocket_recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
int emsocket_select(
int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
int emsocket_close(int sockfd);
}
#ifndef EMSOCKET_INTERNAL
#define socket emsocket_socket
#define setsockopt emsocket_setsockopt
#define bind emsocket_bind
#define recvfrom emsocket_recvfrom
#define sendto emsocket_sendto
#define select emsocket_select
#define close emsocket_close
#endif