From 9b6ede374444183066de16d522d2a1a880b53e73 Mon Sep 17 00:00:00 2001 From: 1fbff5f83b23d39d38b1dfcb4cac8d9b <0a02d0db@opayq.com> Date: Fri, 10 Jan 2020 23:46:49 +0100 Subject: [PATCH] sockets --- byond-extools/src/core/byond_structures.cpp | 15 ++ byond-extools/src/core/byond_structures.h | 5 +- byond-extools/src/core/socket/socket.cpp | 111 +++++++++----- byond-extools/src/core/socket/socket.h | 14 +- .../src/datum_socket/datum_socket.cpp | 143 ++++++++++++++++++ byond-extools/src/datum_socket/datum_socket.h | 31 ++++ .../src/debug_server/debug_server.cpp | 2 +- 7 files changed, 281 insertions(+), 40 deletions(-) create mode 100644 byond-extools/src/datum_socket/datum_socket.cpp create mode 100644 byond-extools/src/datum_socket/datum_socket.h diff --git a/byond-extools/src/core/byond_structures.cpp b/byond-extools/src/core/byond_structures.cpp index ada0ea7e..a29816e1 100644 --- a/byond-extools/src/core/byond_structures.cpp +++ b/byond-extools/src/core/byond_structures.cpp @@ -143,6 +143,21 @@ List::List(Value v) list = GetListPointerById(id); } +Container::Container(char type, int id) : type(type), id(id) +{ + IncRefCount(type, id); +} + +Container::Container(Value val) : type(val.type), id(val.value) +{ + IncRefCount(type, id); +} + +Container::~Container() +{ + DecRefCount(type, id); +} + unsigned int Container::length() { return Length(type, id); diff --git a/byond-extools/src/core/byond_structures.h b/byond-extools/src/core/byond_structures.h index 6d1fd6cf..57f1a974 100644 --- a/byond-extools/src/core/byond_structures.h +++ b/byond-extools/src/core/byond_structures.h @@ -175,8 +175,9 @@ struct ContainerProxy struct Container //All kinds of lists, including magical snowflake lists like contents { - Container(char type, int id) : type(type), id(id) {} - Container(Value val) : type(val.type), id(val.value) {} + Container(char type, int id); + Container(Value val); + ~Container(); char type; int id; diff --git a/byond-extools/src/core/socket/socket.cpp b/byond-extools/src/core/socket/socket.cpp index 75b9410d..9533e493 100644 --- a/byond-extools/src/core/socket/socket.cpp +++ b/byond-extools/src/core/socket/socket.cpp @@ -58,19 +58,13 @@ bool Socket::create(int family, int socktype, int protocol) return raw_socket != INVALID_SOCKET; } -// Listening. -bool TcpListener::listen(const char* port, const char* iface) +bool connect_socket(Socket& socket, const char* port, const char* remote) { + struct addrinfo* result = NULL; struct addrinfo hints; int iResult; - // Initialize Winsock - if (!InitOnce()) - { - return false; - } - ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; @@ -78,7 +72,7 @@ bool TcpListener::listen(const char* port, const char* iface) hints.ai_flags = AI_PASSIVE; // Resolve the server address and port - iResult = getaddrinfo(iface, port, &hints, &result); + iResult = getaddrinfo(remote, port, &hints, &result); if (iResult != 0) { Core::Alert("getaddrinfo failed with error: " + std::to_string(iResult)); @@ -93,40 +87,21 @@ bool TcpListener::listen(const char* port, const char* iface) return false; } - // Set SO_REUSEADDR so if the process is killed, the port becomes reusable - // immediately rather than after a 60-second delay. - int opt = 1; - setsockopt(socket.raw(), SOL_SOCKET, SO_REUSEADDR, (const char*) &opt, sizeof(int)); - // Setup the TCP listening socket - iResult = bind(socket.raw(), result->ai_addr, (int)result->ai_addrlen); + iResult = ::connect(socket.raw(), result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { - Core::Alert("bind failed with error: " + std::to_string(WSAGetLastError())); + Core::Alert("connect failed with error: " + std::to_string(WSAGetLastError())); freeaddrinfo(result); socket.close(); return false; } - freeaddrinfo(result); - - iResult = ::listen(socket.raw(), SOMAXCONN); - if (iResult == SOCKET_ERROR) - { - Core::Alert("listen failed with error: " + std::to_string(WSAGetLastError())); - socket.close(); - return false; - } - return true; } -JsonStream TcpListener::accept() -{ - return JsonStream(Socket(::accept(socket.raw(), NULL, NULL))); -} - -bool JsonStream::connect(const char* port, const char* remote) +// Listening. +bool JsonListener::listen(const char* port, const char* iface) { struct addrinfo* result = NULL; struct addrinfo hints; @@ -145,7 +120,7 @@ bool JsonStream::connect(const char* port, const char* remote) hints.ai_flags = AI_PASSIVE; // Resolve the server address and port - iResult = getaddrinfo(remote, port, &hints, &result); + iResult = getaddrinfo(iface, port, &hints, &result); if (iResult != 0) { Core::Alert("getaddrinfo failed with error: " + std::to_string(iResult)); @@ -160,19 +135,50 @@ bool JsonStream::connect(const char* port, const char* remote) return false; } + // Set SO_REUSEADDR so if the process is killed, the port becomes reusable + // immediately rather than after a 60-second delay. + int opt = 1; + setsockopt(socket.raw(), SOL_SOCKET, SO_REUSEADDR, (const char*) &opt, sizeof(int)); + // Setup the TCP listening socket - iResult = ::connect(socket.raw(), result->ai_addr, (int)result->ai_addrlen); + iResult = bind(socket.raw(), result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { - Core::Alert("connect failed with error: " + std::to_string(WSAGetLastError())); + Core::Alert("bind failed with error: " + std::to_string(WSAGetLastError())); freeaddrinfo(result); socket.close(); return false; } + freeaddrinfo(result); + + iResult = ::listen(socket.raw(), SOMAXCONN); + if (iResult == SOCKET_ERROR) + { + Core::Alert("listen failed with error: " + std::to_string(WSAGetLastError())); + socket.close(); + return false; + } + return true; } +JsonStream JsonListener::accept() +{ + return JsonStream(Socket(::accept(socket.raw(), NULL, NULL))); +} + +bool JsonStream::connect(const char* port, const char* remote) +{ + // Initialize Winsock + if (!InitOnce()) + { + return false; + } + + return connect_socket(socket, port, remote); +} + bool JsonStream::send(const char* type, nlohmann::json content) { nlohmann::json j = { @@ -218,3 +224,38 @@ nlohmann::json JsonStream::recv_message() recv_buffer.append(data.begin(), data.begin() + received_bytes); } } + +bool TcpStream::connect(const char* port, const char* remote) +{ + if (!InitOnce()) + { + return false; + } + + return connect_socket(socket, port, remote); +} + +std::string TcpStream::recv() +{ + std::vector data(1024); + int received_bytes = ::recv(socket.raw(), data.data(), data.size(), 0); + if (received_bytes <= 0) + { + return ""; + } + return std::string(data.begin(), data.begin() + received_bytes); +} + +bool TcpStream::send(std::string data) +{ + while (!data.empty()) + { + int sent_bytes = ::send(socket.raw(), data.c_str(), data.size(), 0); + if (sent_bytes == SOCKET_ERROR) + { + return false; + } + data.erase(data.begin(), data.begin() + sent_bytes); + } + return true; +} \ No newline at end of file diff --git a/byond-extools/src/core/socket/socket.h b/byond-extools/src/core/socket/socket.h index e9bb5771..cbfaad19 100644 --- a/byond-extools/src/core/socket/socket.h +++ b/byond-extools/src/core/socket/socket.h @@ -49,12 +49,22 @@ class JsonStream void close() { socket.close(); } }; -class TcpListener +class JsonListener { Socket socket; public: - TcpListener() {} + JsonListener() {} bool listen(const char* port = DBG_DEFAULT_PORT, const char* iface = "127.0.0.1"); JsonStream accept(); void close() { socket.close(); } }; + +class TcpStream +{ + Socket socket; +public: + bool connect(const char* port, const char* remote); //augh, why port first?! damn it spaceman + bool send(std::string data); + std::string recv(); + void close() { socket.close(); } +}; diff --git a/byond-extools/src/datum_socket/datum_socket.cpp b/byond-extools/src/datum_socket/datum_socket.cpp new file mode 100644 index 00000000..f34d7885 --- /dev/null +++ b/byond-extools/src/datum_socket/datum_socket.cpp @@ -0,0 +1,143 @@ +#include "datum_socket.h" +#include "../core/core.h" +#include "../core/proc_management.h" + +std::unordered_map> sockets; +unsigned int recv_sleep_opcode = -1; + +DatumSocket::DatumSocket() +{ +} + +DatumSocket::DatumSocket(const DatumSocket& other) +{ +} + +DatumSocket::~DatumSocket() +{ + close(); +} + +bool DatumSocket::connect(std::string addr, std::string port) +{ + stream = TcpStream(); + bool connected = stream.connect(port.c_str(), addr.c_str()); + if (connected) + { + std::thread(&DatumSocket::recv_loop, this).detach(); + } + return connected; +} + +bool DatumSocket::send(std::string data) +{ + return stream.send(data); +} + +std::string DatumSocket::recv(int len) +{ + std::lock_guard lk(buffer_lock); + size_t nom = min(len, buffer.size()); + std::string sub = buffer.substr(0, nom); + buffer.erase(buffer.begin(), buffer.begin() + nom); + return sub; +} + +void DatumSocket::close() +{ + stream.close(); + open = false; +} + +void DatumSocket::recv_loop() +{ + while (open) + { + std::string data = stream.recv(); + if (data.empty()) + { + close(); + return; + } + buffer_lock.lock(); + buffer += data; + buffer_lock.unlock(); + if (data_awaiter) + { + data_awaiter->time_to_resume = 0; + data_awaiter = nullptr; + } + } +} + +trvh register_socket(unsigned int args_len, Value* args, Value src) +{ + //Core::Alert("register"); + sockets[src.value] = std::make_unique(); + return Value::Null(); +} + +trvh connect_socket(unsigned int args_len, Value* args, Value src) +{ + //Core::Alert("connect"); + return sockets[src.value]->connect(args[0], std::to_string((int)args[1].valuef)) ? Value::True() : Value::False(); +} + +trvh send_socket(unsigned int args_len, Value* args, Value src) +{ + //Core::Alert("send"); + return sockets[src.value]->send(args[0]) ? Value::True() : Value::False(); +} + +trvh check_socket(unsigned int args_len, Value* args, Value src) +{ + //Core::Alert("check"); + return sockets[src.value]->has_data() ? Value::True() : Value::False(); +} + +trvh retrieve_socket(unsigned int args_len, Value* args, Value src) +{ + //Core::Alert("retrieve"); + return Value(sockets[src.value]->recv(1024)); +} + +trvh deregister_socket(unsigned int args_len, Value* args, Value src) +{ + if (sockets.find(src.value) == sockets.end()) + { + return Value::Null(); + } + sockets[src.value].reset(); + sockets.erase(src.value); + return Value::Null(); +} + +void recv_suspend(ExecutionContext* ctx) +{ + ctx->current_opcode++; + SuspendedProc* proc = Suspend(ctx, 0); + proc->time_to_resume = 0x7FFFFF; + StartTiming(proc); + int datum_id = ctx->constants->src.value; + sockets[datum_id]->set_awaiter(proc); + ctx->current_opcode--; +} + +bool enable_sockets() +{ + Core::get_proc("/datum/socket/proc/__register_socket").hook(register_socket); + Core::get_proc("/datum/socket/proc/__check_has_data").hook(check_socket); + Core::get_proc("/datum/socket/proc/__retrieve_data").hook(retrieve_socket); + Core::get_proc("/datum/socket/proc/connect").hook(connect_socket); + Core::get_proc("/datum/socket/proc/send").hook(send_socket); + Core::get_proc("/datum/socket/proc/__deregister_socket").hook(deregister_socket); + recv_sleep_opcode = Core::register_opcode("RECV_SLEEP", recv_suspend); + Core::get_proc("/datum/socket/proc/__wait_for_data").set_bytecode(new std::vector({ recv_sleep_opcode, 0, 0, 0 })); + return true; +} + +extern "C" __declspec(dllexport) const char* init_sockets(int a, const char** b) +{ + enable_sockets(); + return "ok"; +} \ No newline at end of file diff --git a/byond-extools/src/datum_socket/datum_socket.h b/byond-extools/src/datum_socket/datum_socket.h new file mode 100644 index 00000000..f7afb126 --- /dev/null +++ b/byond-extools/src/datum_socket/datum_socket.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include "../core/socket/socket.h" +#include + +class DatumSocket +{ +public: + DatumSocket(); + DatumSocket(const DatumSocket& other); + ~DatumSocket(); + + bool connect(std::string addr, std::string port); + bool send(std::string data); + std::string recv(int len); + void close(); + bool has_data() { std::lock_guard lk(buffer_lock); return !buffer.empty() || !open; } + void set_awaiter(SuspendedProc* proc) { data_awaiter = proc; } +protected: + void recv_loop(); + +#ifdef _WIN32 + TcpStream stream; +#endif + std::string buffer; + std::mutex buffer_lock; + SuspendedProc* data_awaiter = nullptr; + bool open = true; +}; + +extern std::unordered_map> sockets; \ No newline at end of file diff --git a/byond-extools/src/debug_server/debug_server.cpp b/byond-extools/src/debug_server/debug_server.cpp index d36d6db2..13b0c5fe 100644 --- a/byond-extools/src/debug_server/debug_server.cpp +++ b/byond-extools/src/debug_server/debug_server.cpp @@ -18,7 +18,7 @@ RuntimePtr oRuntime; bool DebugServer::listen(const char* port) { - TcpListener listener; + JsonListener listener; if (!listener.listen(port)) { Core::Alert("couldn't listen");