Update networking layer w/ CURL and emscripten impl

This commit is contained in:
2025-11-08 01:50:36 +11:00
parent a17925904d
commit f6874dc55a
4105 changed files with 694617 additions and 179 deletions
+80
View File
@@ -0,0 +1,80 @@
#define DN_NET2_CURL_CPP
#include "../dn_base_inc.h"
#include "../dn_os_inc.h"
DN_NET2Response DN_NET2_MakeResponseFromFinishedRequest_(DN_NET2Request request, DN_Arena *arena)
{
DN_NET2Response result = {};
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
if (request_ptr && request_ptr->gen == request.gen) {
DN_NET2ResponseInternal const *response = &request_ptr->response;
// NOTE: Construct the response from the request
result.request = request;
result.state = response->state;
result.http_status = response->http_status;
result.body = DN_Str8BuilderBuild(&response->body, arena);
if (response->error_str8.size)
result.error_str8 = DN_Str8FromStr8Arena(arena, response->error_str8);
}
return result;
}
void DN_NET2_EndFinishedRequest_(DN_NET2Core *net, DN_NET2RequestInternal *request)
{
// NOTE: Deallocate the memory used in the request and reset the string builder
DN_ArenaPopTo(&request->arena, request->start_response_arena_pos);
request->response.body = DN_Str8BuilderFromArena(&request->arena);
// NOTE: Check that the request is completely detached
DN_Assert(request->next == nullptr);
}
void DN_NET2_BaseInit_(DN_NET2Core *net, char *base, DN_U64 base_size)
{
net->base = base;
net->base_size = base_size;
net->arena = DN_ArenaFromBuffer(net->base, net->base_size, DN_ArenaFlags_Nil);
net->completion_sem = DN_OS_SemaphoreInit(0);
}
DN_NET2Request DN_NET2_SetupRequest_(DN_NET2RequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type)
{
// NOTE: Setup request
DN_Assert(request);
DN_NET2Request result = {};
if (request) {
if (!request->arena.curr)
request->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_ArenaFlags_Nil);
request->type = type;
request->gen = DN_Max(request->gen + 1, 1);
request->url = DN_Str8FromStr8Arena(&request->arena, url);
request->method = DN_Str8FromStr8Arena(&request->arena, DN_Str8TrimWhitespaceAround(method));
if (args) {
request->args.flags = args->flags;
request->args.username = DN_Str8FromStr8Arena(&request->arena, args->username);
request->args.password = DN_Str8FromStr8Arena(&request->arena, args->password);
if (type == DN_NET2RequestType_HTTP)
request->args.payload = DN_Str8FromStr8Arena(&request->arena, args->payload);
request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No);
DN_Assert(request->args.headers);
if (request->args.headers) {
for (DN_ForItSize(it, DN_Str8, args->headers, args->headers_size))
request->args.headers[it.index] = DN_Str8FromStr8Arena(&request->arena, *it.data);
request->args.headers_size = args->headers_size;
}
}
request->response.body = DN_Str8BuilderFromArena(&request->arena);
request->completion_sem = DN_OS_SemaphoreInit(0);
request->start_response_arena_pos = DN_ArenaPos(&request->arena);
result.handle = DN_Cast(DN_UPtr) request;
result.gen = request->gen;
}
return result;
}
+133
View File
@@ -0,0 +1,133 @@
#if !defined(DN_NET2_H)
#define DN_NET2_H
#include "../dn_base_inc.h"
#include "../dn_os_inc.h"
enum DN_NET2RequestType
{
DN_NET2RequestType_Nil,
DN_NET2RequestType_HTTP,
DN_NET2RequestType_WS,
};
enum DN_NET2ResponseState
{
DN_NET2ResponseState_Nil,
DN_NET2ResponseState_Error,
DN_NET2ResponseState_HTTP,
DN_NET2ResponseState_WSOpen,
DN_NET2ResponseState_WSText,
DN_NET2ResponseState_WSBinary,
DN_NET2ResponseState_WSClose,
DN_NET2ResponseState_WSPing,
DN_NET2ResponseState_WSPong,
};
enum DN_NET2WSSend
{
DN_NET2WSSend_Text,
DN_NET2WSSend_Binary,
DN_NET2WSSend_Close,
DN_NET2WSSend_Ping,
DN_NET2WSSend_Pong,
};
enum DN_NET2DoHTTPFlags
{
DN_NET2DoHTTPFlags_Nil = 0,
DN_NET2DoHTTPFlags_BasicAuth = 1 << 0,
};
struct DN_NET2DoHTTPArgs
{
// NOTE: WS and HTTP args
DN_NET2DoHTTPFlags flags;
DN_Str8 username;
DN_Str8 password;
DN_Str8 *headers;
DN_U16 headers_size;
// NOTE: HTTP args only
DN_Str8 payload;
};
struct DN_NET2Request
{
DN_UPtr handle;
DN_U64 gen;
};
struct DN_NET2ResponseInternal
{
DN_NET2ResponseState state;
bool ws_has_more;
DN_Str8Builder body;
DN_U32 http_status;
DN_Str8 error_str8;
};
struct DN_NET2Response
{
// NOTE: Common to WS and HTTP responses
DN_NET2ResponseState state;
DN_NET2Request request;
DN_Str8 error_str8;
DN_Str8 body;
// NOTE: HTTP responses only
DN_U32 http_status;
};
struct DN_NET2RequestInternal
{
// NOTE: Initialised in user thread, then read-only and shared to networking thread until reset
DN_Arena arena;
DN_USize start_response_arena_pos;
DN_NET2RequestType type;
DN_U64 gen;
DN_Str8 url;
DN_Str8 method;
DN_OSSemaphore completion_sem;
DN_NET2DoHTTPArgs args;
DN_NET2ResponseInternal response;
DN_NET2RequestInternal *next;
DN_NET2RequestInternal *prev;
DN_U64 context[2];
};
struct DN_NET2Core
{
char *base;
DN_U64 base_size;
DN_Arena arena;
DN_OSSemaphore completion_sem;
DN_NET2RequestInternal *done_list;
DN_NET2RequestInternal *free_list;
void *context;
};
typedef void (DN_NET2InitFunc) (DN_NET2Core *net, char *base, DN_U64 base_size);
typedef DN_NET2Request (DN_NET2DoHTTPFunc) (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args);
typedef DN_NET2Request (DN_NET2DoWSFunc) (DN_NET2Core *net, DN_Str8 url);
typedef void (DN_NET2DoWSSendFunc) (DN_NET2Request request, DN_Str8 data, DN_NET2WSSend send);
typedef DN_NET2Response (DN_NET2WaitForResponseFunc) (DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms);
typedef DN_NET2Response (DN_NET2WaitForAnyResponseFunc)(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms);
struct DN_NET2Interface
{
DN_NET2InitFunc* init;
DN_NET2DoHTTPFunc* do_http;
DN_NET2DoWSFunc* do_ws;
DN_NET2DoWSSendFunc* do_ws_send;
DN_NET2WaitForResponseFunc* wait_for_response;
DN_NET2WaitForAnyResponseFunc* wait_for_any_response;
};
// NOTE: Internal functions for different networking implementations to use
void DN_NET2_BaseInit_ (DN_NET2Core *net, char *base, DN_U64 base_size);
DN_NET2Request DN_NET2_SetupRequest_ (DN_NET2RequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type);
DN_NET2Response DN_NET2_MakeResponseFromFinishedRequest_(DN_NET2Request request, DN_Arena *arena);
void DN_NET2_EndFinishedRequest_ (DN_NET2Core *net, DN_NET2RequestInternal *request);
#endif // DN_NET2_H
+1
View File
@@ -147,4 +147,5 @@ static void DN_NET2_Init (DN_NET2Core *net, char *ring_b
static DN_NET2Request DN_NET2_DoHTTP (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args);
static DN_NET2Request DN_NET2_OpenWS (DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args);
static void DN_NET2_SendWS (DN_NET2Core *net, DN_NET2Request request, DN_Str8 payload, DN_NET2WSType type);
#endif // DN_NET2_CURL_H
+638
View File
@@ -0,0 +1,638 @@
#include "dn_net2.h"
#include "dn_net_curl.h"
struct DN_NET2CurlConn
{
void *curl;
struct curl_slist *curl_slist;
char error[CURL_ERROR_SIZE];
};
enum DN_NET2CurlRingEventType
{
DN_NET2CurlRingEventType_Nil,
DN_NET2CurlRingEventType_DoRequest,
DN_NET2CurlRingEventType_SendWS,
DN_NET2CurlRingEventType_ReceivedWSReceipt,
DN_NET2CurlRingEventType_DeinitRequest,
};
struct DN_NET2CurlRingEvent
{
DN_NET2CurlRingEventType type;
DN_NET2Request request;
DN_USize ws_send_size;
DN_NET2WSSend ws_send;
};
struct DN_NET2CurlCore
{
// NOTE: Shared w/ user and networking thread
DN_Ring ring;
DN_OSMutex ring_mutex;
DN_OSMutex free_or_done_mutex; // Lock for free list and done list
DN_OSThread thread;
// NOTE: Networking thread only
void *curlm;
DN_NET2RequestInternal *http_list;
DN_NET2RequestInternal *ws_list;
};
DN_NET2Interface DN_NET2_CurlInterface()
{
DN_NET2Interface result = {};
result.init = DN_NET2_CurlInit;
result.do_http = DN_NET2_CurlDoHTTP;
result.do_ws = DN_NET2_CurlDoWS;
result.do_ws_send = DN_NET2_CurlDoWSSend;
result.wait_for_response = DN_NET2_CurlWaitForResponse;
result.wait_for_any_response = DN_NET2_CurlWaitForAnyResponse;
return result;
}
static void DN_NET2_CurlMarkRequestDone_(DN_NET2Core *net, DN_NET2RequestInternal *request)
{
// NOTE: The done list in CURL is also used as a place to put websocket requests after removing it
// from the 'ws_list'. By doing this we are stopping the CURL thread from receiving more data on
// the socket as that thread ticks the list of 'ws_list' sockets for data.
//
// Once the caller waited and has received the data from the websocket, the request is put back
// into the 'ws_list' which then lets the CURL thread start receiving more data for that socket.
//
// Since CURL uses a background thread, we do this behind a mutex
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *)net->context;
for (DN_OS_MutexScope(&curl->free_or_done_mutex))
DN_DLList_Append(net->done_list, request);
DN_OS_SemaphoreIncrement(&net->completion_sem, 1);
DN_OS_SemaphoreIncrement(&request->completion_sem, 1);
}
static DN_USize DN_NET2_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data)
{
auto *request = DN_Cast(DN_NET2RequestInternal *) user_data;
DN_USize result = 0;
DN_USize payload_size = size * count;
if (DN_Str8BuilderAppendBytesCopy(&request->response.body, payload, payload_size))
result = payload_size;
return result;
}
static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread)
{
DN_NET2Core *net = DN_Cast(DN_NET2Core *) thread->user_context;
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context;
DN_OS_ThreadSetName(DN_Str8FromPtr(curl->thread.name.data, curl->thread.name.size));
for (;;) {
DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr);
for (bool dequeue_ring = true; dequeue_ring;) {
// NOTE: Dequeue user request
DN_NET2CurlRingEvent event = {};
for (DN_OS_MutexScope(&curl->ring_mutex)) {
if (DN_Ring_HasData(&curl->ring, sizeof(event)))
DN_Ring_Read(&curl->ring, &event, sizeof(event));
}
switch (event.type) {
case DN_NET2CurlRingEventType_Nil: dequeue_ring = false; break;
case DN_NET2CurlRingEventType_DoRequest: {
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *)event.request.handle;
DN_Assert(request->response.state != DN_NET2ResponseState_Error);
switch (request->type) {
case DN_NET2RequestType_Nil: {
DN_NET2_CurlMarkRequestDone_(net, request);
DN_InvalidCodePath;
} break;
case DN_NET2RequestType_HTTP: {
DN_Assert(request->response.state == DN_NET2ResponseState_Nil);
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
CURLMcode multi_add = curl_multi_add_handle(curl->curlm, conn->curl);
DN_Assert(multi_add == CURLM_OK);
DN_Assert(request->next == nullptr);
DN_Assert(request->prev == nullptr);
DN_DLList_Append(curl->http_list, request);
} break;
case DN_NET2RequestType_WS: {
DN_Assert(request->response.state == DN_NET2ResponseState_Nil);
DN_Assert(request->next == nullptr);
DN_Assert(request->prev == nullptr);
if (request->response.state == DN_NET2ResponseState_Nil) {
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
CURLMcode multi_add = curl_multi_add_handle(curl->curlm, conn->curl);
DN_Assert(multi_add == CURLM_OK);
DN_DLList_Append(curl->http_list, request); // Open the WS connection
} else {
DN_DLList_Append(curl->ws_list, request); // Ready to send data through the WS connection
}
} break;
}
} break;
case DN_NET2CurlRingEventType_SendWS: {
DN_Str8 payload = {};
for (DN_OS_MutexScope(&curl->ring_mutex)) {
DN_Assert(DN_Ring_HasData(&curl->ring, event.ws_send_size));
payload = DN_Str8FromArena(tmem.arena, event.ws_send_size, DN_ZMem_No);
DN_Ring_Read(&curl->ring, payload.data, payload.size);
}
DN_U32 curlws_flag = 0;
switch (event.ws_send) {
case DN_NET2WSSend_Text: curlws_flag = CURLWS_TEXT; break;
case DN_NET2WSSend_Binary: curlws_flag = CURLWS_BINARY; break;
case DN_NET2WSSend_Close: curlws_flag = CURLWS_CLOSE; break;
case DN_NET2WSSend_Ping: curlws_flag = CURLWS_PING; break;
case DN_NET2WSSend_Pong: curlws_flag = CURLWS_PONG; break;
}
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle;
DN_Assert(request->type == DN_NET2RequestType_WS);
DN_Assert(request->response.state == DN_NET2ResponseState_WSOpen);
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
DN_USize sent = 0;
CURLcode send_result = curl_ws_send(conn->curl, payload.data, payload.size, &sent, 0, curlws_flag);
DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result));
DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size);
} break;
case DN_NET2CurlRingEventType_ReceivedWSReceipt: {
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle;
DN_Assert(request->type == DN_NET2RequestType_WS);
DN_Assert(request->response.state >= DN_NET2ResponseState_WSOpen && request->response.state <= DN_NET2ResponseState_WSPong);
DN_Assert(request->next == nullptr);
DN_Assert(request->prev == nullptr);
request->response.state = DN_NET2ResponseState_WSOpen;
DN_DLList_Append(curl->ws_list, request);
} break;
case DN_NET2CurlRingEventType_DeinitRequest: {
if (event.request.handle != 0) {
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle;
// NOTE: Release resources
DN_ArenaClear(&request->arena);
DN_OS_SemaphoreDeinit(&request->completion_sem);
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
curl_multi_remove_handle(curl->curlm, conn->curl);
curl_easy_reset(conn->curl);
curl_slist_free_all(conn->curl_slist);
// NOTE: Zero the struct preserving just the data we need to retain
DN_NET2RequestInternal resetter = {};
resetter.arena = request->arena;
resetter.gen = request->gen;
DN_Memcpy(resetter.context, request->context, sizeof(resetter.context));
*request = resetter;
for (DN_OS_MutexScope(&curl->free_or_done_mutex))
DN_DLList_Append(net->free_list, request);
}
} break;
}
}
// NOTE: Pump handles
int running_handles = 0;
CURLMcode perform_result = curl_multi_perform(curl->curlm, &running_handles);
if (perform_result != CURLM_OK)
DN_InvalidCodePath;
// NOTE: Check pump result
for (;;) {
int msgs_in_queue = 0;
CURLMsg *msg = curl_multi_info_read(curl->curlm, &msgs_in_queue);
if (msg) {
// NOTE: Get request handle
DN_NET2RequestInternal *request = nullptr;
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & request);
DN_Assert(request);
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *)request->context[0];
DN_Assert(conn->curl == msg->easy_handle);
if (msg->data.result == CURLE_OK) {
// NOTE: Get HTTP response code
CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &request->response.http_status);
if (get_result == CURLE_OK) {
if (request->type == DN_NET2RequestType_HTTP) {
request->response.state = DN_NET2ResponseState_HTTP;
} else {
DN_Assert(request->type == DN_NET2RequestType_WS);
request->response.state = DN_NET2ResponseState_WSOpen;
}
} else {
request->response.error_str8 = DN_Str8FromFmtArena(&request->arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result));
request->response.state = DN_NET2ResponseState_Error;
}
} else {
DN_USize curl_extended_error_size = DN_CStr8Size(conn->error);
request->response.state = DN_NET2ResponseState_Error;
request->response.error_str8 = DN_Str8FromFmtArena(&request->arena,
"HTTP request '%.*s' failed (CURL %d): %s%s%s%s",
DN_Str8PrintFmt(request->url),
msg->data.result,
curl_easy_strerror(msg->data.result),
curl_extended_error_size ? " (" : "",
curl_extended_error_size ? conn->error : "",
curl_extended_error_size ? ")" : "");
}
if (request->type == DN_NET2RequestType_HTTP || request->response.state == DN_NET2ResponseState_Error) {
// NOTE: Remove the request from the multi handle if we're a HTTP request
// because it typically terminates the connection. In websockets the
// connection remains in the multi-handle to allow you to send and
// receive WS data from it.
//
// If there's an error (either websocket or HTTP) we will also remove the
// connection from the multi handle as it failed. One a connection has
// failed, curl will not poll that connection so there's no point keeping
// it attached to the multi handle.
curl_multi_remove_handle(curl->curlm, msg->easy_handle);
}
DN_NET2_CurlMarkRequestDone_(net, request);
}
if (msgs_in_queue == 0)
break;
}
// NOTE: Check websockets
DN_I32 sleep_time_ms = DN_DLList_HasItems(curl->ws_list) ? 100 : INT32_MAX;
for (DN_DLList_ForEach(request, curl->ws_list)) {
DN_Assert(request->type == DN_NET2RequestType_WS);
DN_Assert(request->response.state == DN_NET2ResponseState_WSOpen);
CURLcode receive_result = CURLE_OK;
const curl_ws_frame *meta = nullptr;
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
for (;;) {
// NOTE: Determine WS payload size received. Note that since we pass in a null pointer CURL
// will set meta->len to 0 and say that there's meta->bytesleft in the next chunk.
DN_USize bytes_read = 0;
receive_result = curl_ws_recv(conn->curl, nullptr, 0, &bytes_read, &meta);
if (receive_result != CURLE_OK)
break;
DN_Assert(meta->len == 0);
if (meta->flags & CURLWS_TEXT)
request->response.state = DN_NET2ResponseState_WSText;
if (meta->flags & CURLWS_BINARY)
request->response.state = DN_NET2ResponseState_WSBinary;
if (meta->flags & CURLWS_PING)
request->response.state = DN_NET2ResponseState_WSPing;
if (meta->flags & CURLWS_PONG)
request->response.state = DN_NET2ResponseState_WSPong;
if (meta->flags & CURLWS_CLOSE)
request->response.state = DN_NET2ResponseState_WSClose;
request->response.ws_has_more = meta->flags & CURLWS_CONT;
if (request->response.ws_has_more) {
bool is_text_or_binary = request->response.state == DN_NET2ResponseState_WSText ||
request->response.state == DN_NET2ResponseState_WSBinary;
DN_Assert(is_text_or_binary);
}
// NOTE: Allocate and read (we use meta->bytesleft as per comment from initial recv)
if (meta->bytesleft) {
DN_Str8 buffer = DN_Str8FromArena(&request->arena, meta->bytesleft, DN_ZMem_No);
DN_Assert(buffer.size == DN_Cast(DN_USize)meta->bytesleft);
receive_result = curl_ws_recv(conn->curl, buffer.data, buffer.size, &buffer.size, &meta);
DN_Assert(buffer.size == DN_Cast(DN_USize)meta->len);
DN_Str8BuilderAppendRef(&request->response.body, buffer);
}
// NOTE: There are more bytes coming if meta->bytesleft is set, (e.g. the next chunk. We
// just read the current chunk).
//
// > If this is not a complete fragment, the bytesleft field informs about how many
// additional bytes are expected to arrive before this fragment is complete.
request->response.ws_has_more |= meta && meta->bytesleft > 0;
break;
}
// NOTE: We read all the possible bytes that CURL has received for this
// socket, but, there are more bytes left that we will receive on subsequent
// calls. We will continue to the next request and return back to this one
// when PumpRequests is called again where hopefully that data has arrived.
if (request->response.ws_has_more || receive_result == CURLE_AGAIN) {
if (receive_result == CURLE_AGAIN)
sleep_time_ms = 100;
continue;
}
if (receive_result != CURLE_OK) {
DN_USize curl_extended_error_size = DN_CStr8Size(conn->error);
request->response.state = DN_NET2ResponseState_Error;
request->response.error_str8 = DN_Str8FromFmtArena(&request->arena,
"Websocket receive '%.*s' failed (CURL %d): %s%s%s%s",
DN_Str8PrintFmt(request->url),
receive_result,
curl_easy_strerror(receive_result),
curl_extended_error_size ? " (" : "",
curl_extended_error_size ? conn->error : "",
curl_extended_error_size ? ")" : "");
}
DN_NET2RequestInternal *request_copy = request;
request = request->prev;
DN_NET2_CurlMarkRequestDone_(net, request_copy);
}
curl_multi_poll(curl->curlm, nullptr, 0, sleep_time_ms, nullptr);
}
return 0;
}
void DN_NET2_CurlInit(DN_NET2Core *net, char *base, DN_U64 base_size)
{
DN_NET2_BaseInit_(net, base, base_size);
DN_NET2CurlCore *curl = DN_ArenaNew(&net->arena, DN_NET2CurlCore, DN_ZMem_Yes);
net->context = curl;
DN_USize arena_bytes_avail = (net->arena.curr->reserve - net->arena.curr->used);
curl->ring.size = arena_bytes_avail / 2;
curl->ring.base = DN_Cast(char *) DN_ArenaAlloc(&net->arena, curl->ring.size, /*align*/ 1, DN_ZMem_Yes);
DN_Assert(curl->ring.base);
curl->ring_mutex = DN_OS_MutexInit();
curl->free_or_done_mutex = DN_OS_MutexInit();
curl->curlm = DN_Cast(CURLM *) curl_multi_init();
// TODO: Free list should be initialised in the base
DN_DLList_InitArena(net->free_list, DN_NET2RequestInternal, &net->arena);
DN_DLList_InitArena(net->done_list, DN_NET2RequestInternal, &net->arena);
DN_DLList_InitArena(curl->http_list, DN_NET2RequestInternal, &net->arena);
DN_DLList_InitArena(curl->ws_list, DN_NET2RequestInternal, &net->arena);
DN_FmtAppend(curl->thread.name.data, &curl->thread.name.size, sizeof(curl->thread.name.data), "NET (CURL)");
DN_OS_ThreadInit(&curl->thread, DN_NET2_CurlThreadEntryPoint_, net);
}
static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type)
{
// NOTE: Allocate the request
DN_NET2CurlCore *curl_core = DN_Cast(DN_NET2CurlCore *) net->context;
DN_NET2RequestInternal *request = nullptr;
DN_NET2Request result = {};
{
// NOTE: The free list is modified by both the calling thread and the CURLM thread (which ticks
// all the requests in the background for us)
for (DN_OS_MutexScope(&curl_core->free_or_done_mutex))
DN_DLList_Dequeue(net->free_list, request);
// NOTE None if the free list so allocate one
if (!request) {
DN_U64 arena_pos = DN_ArenaPos(&net->arena);
request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes);
DN_NET2CurlConn *conn = DN_ArenaNew(&net->arena, DN_NET2CurlConn, DN_ZMem_Yes);
if (!request || !conn) {
DN_ArenaPopTo(&net->arena, arena_pos);
return result;
}
conn->curl = DN_Cast(CURL *) curl_easy_init();
request->context[0] = DN_Cast(DN_UPtr) conn;
}
}
// NOTE: Setup the request
result = DN_NET2_SetupRequest_(request, url, method, args, type);
request->context[1] = DN_Cast(DN_UPtr) net;
// NOTE: Setup the request for curl
{
DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0];
CURL *curl = conn->curl;
curl_easy_setopt(curl, CURLOPT_PRIVATE, request);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, conn->error);
// NOTE: Perform request and read all response headers before handing
// control back to app.
curl_easy_setopt(curl, CURLOPT_URL, request->url.data);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
// NOTE: Setup response handler
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET2_CurlHTTPCallback_);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, request);
// NOTE: Assign HTTP headers
for (DN_ForItSize(it, DN_Str8, request->args.headers, request->args.headers_size))
conn->curl_slist = curl_slist_append(conn->curl_slist, it.data->data);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, conn->curl_slist);
// NOTE: Setup handle for protocol
switch (request->type) {
case DN_NET2RequestType_Nil: DN_InvalidCodePath; break;
case DN_NET2RequestType_WS: {
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L);
} break;
case DN_NET2RequestType_HTTP: {
DN_Str8 const GET = DN_Str8Lit("GET");
DN_Str8 const POST = DN_Str8Lit("POST");
if (DN_Str8EqInsensitive(request->method, GET)) {
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
} else if (DN_Str8EqInsensitive(request->method, POST)) {
curl_easy_setopt(curl, CURLOPT_POST, 1);
if (request->args.payload.size > DN_Gigabytes(2))
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, request->args.payload.size);
else
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request->args.payload.size);
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request->args.payload.data);
} else {
DN_InvalidCodePathF("Unimplemented");
}
} break;
}
// NOTE: Handle basic auth
if (request->args.flags & DN_NET2DoHTTPFlags_BasicAuth) {
if (request->args.username.size && request->args.password.size) {
DN_Assert(request->args.username.data[request->args.username.size] == 0);
DN_Assert(request->args.password.data[request->args.password.size] == 0);
curl_easy_setopt(curl, CURLOPT_USERNAME, request->args.username.data);
curl_easy_setopt(curl, CURLOPT_PASSWORD, request->args.password.data);
}
}
}
// NOTE: Dispatch the request to the CURL thread. It will append to the CURLM
// instance and tick it in the background for us
{
DN_NET2CurlRingEvent event = {};
event.type = DN_NET2CurlRingEventType_DoRequest;
event.request = result;
for (DN_OS_MutexScope(&curl_core->ring_mutex))
DN_Ring_WriteStruct(&curl_core->ring, &event);
curl_multi_wakeup(curl_core->curlm);
}
return result;
}
DN_NET2Request DN_NET2_CurlDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args)
{
DN_NET2Request result = DN_NET2_CurlDoRequest_(net, url, method, args, DN_NET2RequestType_HTTP);
return result;
}
DN_NET2Request DN_NET2_CurlDoWSArgs(DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args)
{
DN_NET2Request result = DN_NET2_CurlDoRequest_(net, url, DN_Str8Lit(""), args, DN_NET2RequestType_WS);
return result;
}
DN_NET2Request DN_NET2_CurlDoWS(DN_NET2Core *net, DN_Str8 url)
{
DN_NET2Request result = DN_NET2_CurlDoWSArgs(net, url, nullptr);
return result;
}
void DN_NET2_CurlDoWSSend(DN_NET2Request request, DN_Str8 payload, DN_NET2WSSend send)
{
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
if (!request_ptr || request_ptr->gen != request.gen)
return;
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request_ptr->context[1];
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context;
DN_Assert(curl);
DN_NET2CurlRingEvent event = {};
event.type = DN_NET2CurlRingEventType_SendWS;
event.request = request;
event.ws_send_size = payload.size;
event.ws_send = send;
for (DN_OS_MutexScope(&curl->ring_mutex)) {
DN_Assert(DN_Ring_HasSpace(&curl->ring, payload.size));
DN_Ring_WriteStruct(&curl->ring, &event);
DN_Ring_Write(&curl->ring, payload.data, payload.size);
}
curl_multi_wakeup(curl->curlm);
}
static DN_NET2Response DN_NET2_CurlHandleFinishedRequest_(DN_NET2Core *net, DN_NET2CurlCore *curl, DN_NET2Request request, DN_NET2RequestInternal *request_ptr, DN_Arena *arena)
{
// NOTE: Process the response
DN_NET2Response result = DN_NET2_MakeResponseFromFinishedRequest_(request, arena);
DN_NET2_EndFinishedRequest_(net, request_ptr);
// NOTE: For websocket requests, notify the CURL thread we've read data from it and it can go
// back to polling the socket for more data. Over on the CURL thread when it receives the event
// it will append the request onto the 'ws_list' which it iterates to poll for data.
//
// The request was _just_ sitting in the done list (see above) so it was not being polled but
// it will be polled after this step.
//
// TODO: How do we unsticky the error? Right now we just deallocate/close the request entirely and move on
bool end_the_request = true;
if (request_ptr->type == DN_NET2RequestType_WS &&
request_ptr->response.state != DN_NET2ResponseState_Error &&
request_ptr->response.state != DN_NET2ResponseState_WSClose) {
DN_NET2CurlRingEvent event = {};
event.type = DN_NET2CurlRingEventType_ReceivedWSReceipt;
event.request = request;
for (DN_OS_MutexScope(&curl->ring_mutex))
DN_Ring_WriteStruct(&curl->ring, &event);
curl_multi_wakeup(curl->curlm);
end_the_request = false;
}
if (end_the_request) {
// NOTE: This _has_ to be sent to our CURL thread because we need to remove the CURL handle from
// the CURLM instance and the CURL thread uses the CURLM instance (e.g. not thread safe to do
// here)
DN_NET2CurlRingEvent event = {};
event.type = DN_NET2CurlRingEventType_DeinitRequest;
event.request = request;
for (DN_OS_MutexScope(&curl->ring_mutex))
DN_Ring_WriteStruct(&curl->ring, &event);
}
return result;
}
DN_NET2Response DN_NET2_CurlWaitForResponse(DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms)
{
DN_NET2Response result = {};
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
if (!request_ptr || request_ptr->gen != request.gen)
return result;
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request_ptr->context[1];
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context;
DN_Assert(curl);
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&request_ptr->completion_sem, timeout_ms);
if (wait != DN_OSSemaphoreWaitResult_Success)
return result;
// NOTE: Remove the request from the done list. Note it _has_ to be in the done list, anything
// sitting in the done list is guaranteed to not be modified by the CURL thread.
for (DN_OS_MutexScope(&curl->free_or_done_mutex)) {
bool is_in_done_list = false;
for (DN_DLList_ForEach(it, net->done_list)) {
if (it == request_ptr) {
is_in_done_list = true;
break;
}
}
DN_AssertF(is_in_done_list, "Any request that has signalled completion should be inserted into the done list");
DN_DLList_Detach(request_ptr);
}
// NOTE: Finish handling the response
result = DN_NET2_CurlHandleFinishedRequest_(net, curl, request, request_ptr, arena);
// NOTE: Decrement the global 'request done' completion semaphore since the user consumed the
// request individually.
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/);
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
return result;
}
DN_NET2Response DN_NET2_CurlWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms)
{
DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context;
DN_Assert(curl);
DN_NET2Response result = {};
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms);
if (wait != DN_OSSemaphoreWaitResult_Success)
return result;
// NOTE: Dequeue the request that is done from the done list
DN_NET2RequestInternal *request_ptr = nullptr;
for (DN_OS_MutexScope(&curl->free_or_done_mutex))
DN_DLList_Dequeue(net->done_list, request_ptr);
DN_Assert(request_ptr);
// NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/);
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
// NOTE: Finish handling the response
DN_NET2Request request = {};
request.handle = DN_Cast(DN_UPtr) request_ptr;
request.gen = request_ptr->gen;
result = DN_NET2_CurlHandleFinishedRequest_(net, curl, request, request_ptr, arena);
return result;
}
+15
View File
@@ -0,0 +1,15 @@
#if !defined(DN_NET_CURL_H)
#define DN_NET_CURL_H
#include "dn_net2.h"
DN_NET2Interface DN_NET2_CurlInterface();
void DN_NET2_CurlInit (DN_NET2Core *net, char *base, DN_U64 base_size);
DN_NET2Request DN_NET2_CurlDoHTTP (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args);
DN_NET2Request DN_NET2_CurlDoWSArgs (DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args);
DN_NET2Request DN_NET2_CurlDoWS (DN_NET2Core *net, DN_Str8 url);
void DN_NET2_CurlDoWSSend (DN_NET2Request request, DN_Str8 payload, DN_NET2WSSend send);
DN_NET2Response DN_NET2_CurlWaitForResponse (DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms);
DN_NET2Response DN_NET2_CurlWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms);
#endif // !defined(DN_NET_CURL_H)
+421
View File
@@ -0,0 +1,421 @@
#include <emscripten.h>
#include <emscripten/fetch.h>
#include <emscripten/websocket.h>
#include "dn_net2.h"
#include "dn_net_emscripten.h"
struct DN_NET2EmcWSEvent
{
DN_NET2ResponseState state;
DN_Str8 payload;
DN_NET2EmcWSEvent *next;
};
struct DN_NET2EmcCore
{
DN_Pool pool;
};
struct DN_NET2EmcRequest
{
int socket;
DN_NET2EmcWSEvent *first_event;
DN_NET2EmcWSEvent *last_event;
};
DN_NET2Interface DN_NET2_EmcInterface()
{
DN_NET2Interface result = {};
result.init = DN_NET2_EmcInit;
result.do_http = DN_NET2_EmcDoHTTP;
result.do_ws = DN_NET2_EmcDoWS;
result.do_ws_send = DN_NET2_EmcDoWSSend;
result.wait_for_response = DN_NET2_EmcWaitForResponse;
result.wait_for_any_response = DN_NET2_EmcWaitForAnyResponse;
return result;
}
static DN_NET2EmcWSEvent *DN_NET2_EmcAllocWSEvent_(DN_NET2RequestInternal *request)
{
// NOTE: Allocate the event and attach to the request
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request->context[1];
DN_NET2EmcWSEvent *result = DN_ArenaNew(&request->arena, DN_NET2EmcWSEvent, DN_ZMem_Yes);
if (result) {
if (!emc_request->first_event)
emc_request->first_event = result;
if (emc_request->last_event)
emc_request->last_event->next = result;
emc_request->last_event = result;
}
return result;
}
static void DN_NET2_EmcOnRequestDone_(DN_NET2Core *net, DN_NET2RequestInternal *request)
{
// NOTE: This may be call multiple times if we get multiple responses when we yield to the javascript event loop
if (!request->next) {
request->next = net->done_list;
net->done_list = request;
}
DN_OS_SemaphoreIncrement(&net->completion_sem, 1);
DN_OS_SemaphoreIncrement(&request->completion_sem, 1);
}
// TODO: Need to enqueue the results since they can accumulate when you yield to the javascript event loop
static bool DN_NET2_EmcWSOnOpen(int eventType, EmscriptenWebSocketOpenEvent const *event, void *user_data)
{
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data;
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request);
net_event->state = DN_NET2ResponseState_WSOpen;
DN_NET2_EmcOnRequestDone_(net, request);
return true;
}
static bool DN_NET2_EmcWSOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *user_data)
{
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data;
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request);
net_event->state = event->isText ? DN_NET2ResponseState_WSText : DN_NET2ResponseState_WSBinary;
if (event->numBytes > 0) {
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
net_event->payload = DN_Str8FromPtrPool(&emc->pool, event->data, event->numBytes);
}
DN_NET2_EmcOnRequestDone_(net, request);
return true;
}
static bool DN_NET2_EmcWSOnError(int eventType, EmscriptenWebSocketErrorEvent const *event, void *user_data)
{
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data;
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request);
net_event->state = DN_NET2ResponseState_Error;
DN_NET2_EmcOnRequestDone_(net, request);
return true;
}
static bool DN_NET2_EmcWSOnClose(int eventType, EmscriptenWebSocketCloseEvent const *event, void *user_data)
{
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data;
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request);
net_event->state = DN_NET2ResponseState_WSClose;
net_event->payload = DN_Str8FromFmtPool(&emc->pool, "Websocket closed '%.*s': (%u) %s (was %s close)", DN_Str8PrintFmt(request->url), event->code, event->reason, event->wasClean ? "clean" : "unclean");
DN_NET2_EmcOnRequestDone_(net, request);
return true;
}
static void DN_NET2_EmcHTTPSuccessCallback(emscripten_fetch_t *fetch)
{
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) fetch->userData;
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
request->response.http_status = fetch->status;
request->response.state = DN_NET2ResponseState_HTTP;
DN_Str8BuilderAppendCopy(&request->response.body, DN_Str8FromPtr(fetch->data, fetch->numBytes - 1));
DN_NET2_EmcOnRequestDone_(net, request);
}
static void DN_NET2_EmcHTTPFailCallback(emscripten_fetch_t *fetch)
{
DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) fetch->userData;
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0];
request->response.http_status = fetch->status;
request->response.state = DN_NET2ResponseState_Error;
DN_NET2_EmcOnRequestDone_(net, request);
}
static void DN_NET2_EmcHTTPProgressCallback(emscripten_fetch_t *fetch)
{
}
void DN_NET2_EmcInit(DN_NET2Core *net, char *base, DN_U64 base_size)
{
DN_NET2_BaseInit_(net, base, base_size);
DN_NET2EmcCore *emc = DN_ArenaNew(&net->arena, DN_NET2EmcCore, DN_ZMem_Yes);
emc->pool = DN_PoolFromArena(&net->arena, 0);
net->context = emc;
}
DN_NET2Request DN_NET2_EmcDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args)
{
// NOTE: Allocate request
DN_NET2RequestInternal *request = net->free_list;
if (request) {
net->free_list = net->free_list->next;
request->next = nullptr;
} else {
request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes);
}
DN_NET2Request result = DN_NET2_SetupRequest_(request, url, method, args, DN_NET2RequestType_HTTP);
// NOTE: Setup some emscripten specific data into our request context
request->context[0] = DN_Cast(DN_UPtr) net;
// NOTE: Setup the HTTP request via Emscripten
emscripten_fetch_attr_t fetch_attribs = {};
{
DN_Assert(request->args.payload.data[request->args.payload.size] == 0);
DN_Assert(request->url.data[request->url.size] == 0);
// NOTE: Setup request for emscripten
emscripten_fetch_attr_init(&fetch_attribs);
fetch_attribs.requestData = request->args.payload.data;
fetch_attribs.requestDataSize = request->args.payload.size;
DN_Assert(request->method.size < DN_ArrayCountU(fetch_attribs.requestMethod));
DN_Memcpy(fetch_attribs.requestMethod, request->method.data, request->method.size);
fetch_attribs.requestMethod[request->method.size] = 0;
// NOTE: Assign HTTP headers
if (request->args.headers_size) {
char **headers = DN_ArenaNewArray(&request->arena, char *, request->args.headers_size + 1, DN_ZMem_Yes);
for (DN_ForItSize(it, DN_Str8, request->args.headers, request->args.headers_size)) {
DN_Assert(it.data->data[it.data->size] == 0);
headers[it.index] = it.data->data;
}
fetch_attribs.requestHeaders = headers;
}
// NOTE: Handle basic auth
if (request->args.flags & DN_NET2DoHTTPFlags_BasicAuth) {
if (request->args.username.size && request->args.password.size) {
DN_Assert(request->args.username.data[request->args.username.size] == 0);
DN_Assert(request->args.password.data[request->args.password.size] == 0);
fetch_attribs.withCredentials = true;
fetch_attribs.userName = request->args.username.data;
fetch_attribs.password = request->args.password.data;
}
}
// NOTE: It would be nice to use EMSCRIPTEN_FETCH_STREAM_DATA however
// emscripten has this note on the current version I'm using that this is
// only supported in Firefox so this is a no-go.
//
// > If passed, the intermediate streamed bytes will be passed in to the
// > onprogress() handler. If not specified, the onprogress() handler will still
// > be called, but without data bytes. Note: Firefox only as it depends on
// > 'moz-chunked-arraybuffer'.
fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
fetch_attribs.onsuccess = DN_NET2_EmcHTTPSuccessCallback;
fetch_attribs.onerror = DN_NET2_EmcHTTPFailCallback;
fetch_attribs.onprogress = DN_NET2_EmcHTTPProgressCallback;
fetch_attribs.userData = request;
}
// NOTE: Update the pop to position for the request
request->start_response_arena_pos = DN_ArenaPos(&request->arena);
// NOTE: Dispatch the asynchronous fetch
emscripten_fetch(&fetch_attribs, request->url.data);
return result;
}
DN_NET2Request DN_NET2_EmcDoWS(DN_NET2Core *net, DN_Str8 url)
{
DN_Assert(emscripten_websocket_is_supported());
// NOTE: Allocate request
DN_NET2RequestInternal *request = net->free_list;
if (request) {
net->free_list = net->free_list->next;
request->next = nullptr;
} else {
request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes);
}
DN_NET2Request result = DN_NET2_SetupRequest_(request, url, /*method=*/ DN_Str8Lit(""), /*args=*/nullptr, DN_NET2RequestType_WS);
if (!request)
return result;
// NOTE: Setup some emscripten specific data into our request context
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
request->context[0] = DN_Cast(DN_UPtr) net;
request->context[1] = DN_Cast(DN_UPtr) DN_ArenaNew(&request->arena, DN_NET2EmcRequest, DN_ZMem_Yes);
request->start_response_arena_pos = DN_ArenaPos(&request->arena);
// NOTE: Create the websocket request and dispatch it via emscripten
EmscriptenWebSocketCreateAttributes attr;
emscripten_websocket_init_create_attributes(&attr);
attr.url = request->url.data;
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request->context[1];
emc_request->socket = emscripten_websocket_new(&attr);
DN_Assert(emc_request->socket > 0);
emscripten_websocket_set_onopen_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnOpen);
emscripten_websocket_set_onmessage_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnMessage);
emscripten_websocket_set_onerror_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnError);
emscripten_websocket_set_onclose_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnClose);
return result;
}
void DN_NET2_EmcDoWSSend(DN_NET2Request request, DN_Str8 data, DN_NET2WSSend send)
{
DN_AssertF(send == DN_NET2WSSend_Binary || send == DN_NET2WSSend_Text || send == DN_NET2WSSend_Close,
"Unimplemented, Emscripten only supports some of the available operations");
bool result = false;
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
if (request_ptr && request_ptr->gen == request.gen) {
DN_Assert(request_ptr->type == DN_NET2RequestType_WS);
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request_ptr->context[1];
switch (send) {
default: DN_InvalidCodePath; break;
case DN_NET2WSSend_Text: {
DN_U64 pos = DN_ArenaPos(&request_ptr->arena);
DN_Str8 data_null_terminated = DN_Str8FromStr8Arena(&request_ptr->arena, data);
result = emscripten_websocket_send_utf8_text(emc_request->socket, data_null_terminated.data);
DN_ArenaPopTo(&request_ptr->arena, pos);
} break;
case DN_NET2WSSend_Binary: {
result = emscripten_websocket_send_binary(emc_request->socket, data.data, data.size);
} break;
case DN_NET2WSSend_Close: {
result = emscripten_websocket_close(emc_request->socket, 0, nullptr);
} break;
}
}
// TODO: Handle result
}
static DN_NET2Response DN_NET2_EmcHandleFinishedRequest_(DN_NET2Core *net, DN_NET2EmcCore *emc, DN_NET2Request request, DN_NET2RequestInternal *request_ptr, DN_Arena *arena)
{
DN_NET2Response result = {};
bool end_request = true;
if (request_ptr->type == DN_NET2RequestType_HTTP) {
result = DN_NET2_MakeResponseFromFinishedRequest_(request, arena);
} else {
// NOTE: Get emscripten contexts
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request_ptr->context[1];
DN_NET2EmcWSEvent *emc_event = emc_request->first_event;
emc_request->first_event = emc_event->next; // Advance the list pointer
DN_Assert(emc_event);
DN_Assert((emc_event->state >= DN_NET2ResponseState_WSOpen && emc_event->state <= DN_NET2ResponseState_WSPong) ||
emc_event->state == DN_NET2ResponseState_Error);
// NOTE: Build the result
result.state = emc_event->state;
result.request = request;
result.body = DN_Str8FromStr8Arena(arena, emc_event->payload);
// NOTE: Free the payload which is stored in the Emscripten pool
if (emc_event->payload.size) {
DN_PoolDealloc(&emc->pool, emc_event->payload.data);
emc_event->payload = {};
}
if (result.state != DN_NET2ResponseState_WSClose)
end_request = false;
}
// NOTE: Deallocate the memory used in the request and reset the string builder
DN_ArenaPopTo(&request_ptr->arena, 0);
request_ptr->response.body = DN_Str8BuilderFromArena(&request_ptr->arena);
if (end_request) {
DN_NET2_EndFinishedRequest_(net, request_ptr);
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request_ptr->context[1];
emscripten_websocket_delete(emc_request->socket);
request_ptr->next = net->free_list;
net->free_list = request_ptr;
}
return result;
}
static DN_OSSemaphoreWaitResult DN_NET2_EmcSemaphoreWait_(DN_OSSemaphore *sem, DN_U32 timeout_ms)
{
// NOTE: In emscripten you can't just block on the semaphore with 'timeout_ms' because it needs
// to yield to the javascript's event loop otherwise the fetching step cannot progress. Instead
// we use a timeout of 0 to just immediately check if the semaphore has been signalled, if not,
// then we yield to the event loop by calling sleep.
//
// Once yielded, fetch will execute and eventually in the callback it will signal the semaphore
// where it'll return and we can break out of the simulated "timeout".
DN_OSSemaphoreWaitResult result = {};
DN_U32 timeout_remaining_ms = timeout_ms;
DN_F64 begin_ms = emscripten_get_now();
for (;;) {
result = DN_OS_SemaphoreWait(sem, 0);
if (result == DN_OSSemaphoreWaitResult_Success)
break;
if (timeout_remaining_ms <= 0)
break;
emscripten_sleep(100 /*ms*/);
DN_F64 end_ms = emscripten_get_now();
DN_USize duration_ms = DN_Cast(DN_USize)(end_ms - begin_ms);
timeout_remaining_ms = timeout_remaining_ms >= duration_ms ? timeout_remaining_ms - duration_ms : 0;
begin_ms = end_ms;
}
return result;
}
DN_NET2Response DN_NET2_EmcWaitForResponse(DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms)
{
DN_NET2Response result = {};
DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle;
if (request_ptr && request_ptr->gen == request.gen) {
DN_NET2Core *net = DN_Cast(DN_NET2Core *) request_ptr->context[0];
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
DN_Assert(emc);
DN_OSSemaphoreWaitResult wait = DN_NET2_EmcSemaphoreWait_(&request_ptr->completion_sem, timeout_ms);
if (wait != DN_OSSemaphoreWaitResult_Success)
return result;
// NOTE: Remove request from the done list
request_ptr->next = nullptr;
net->done_list = net->done_list->next;
result = DN_NET2_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena);
// NOTE: Decrement the global 'request done' completion semaphore since the user consumed the
// request individually.
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/);
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
}
return result;
}
DN_NET2Response DN_NET2_EmcWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms)
{
DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context;
DN_Assert(emc);
DN_NET2Response result = {};
DN_OSSemaphoreWaitResult wait = DN_NET2_EmcSemaphoreWait_(&net->completion_sem, timeout_ms);
if (wait != DN_OSSemaphoreWaitResult_Success)
return result;
// NOTE: Dequeue the request that is done from the done list
DN_AssertF(net->done_list,
"This should be set otherwise we bumped the completion sem without queueing into the "
"done list or we forgot to wait on the global semaphore after a request finished");
DN_NET2RequestInternal *request_ptr = net->done_list;
DN_Assert(request_ptr == net->done_list);
request_ptr->next = nullptr;
net->done_list = net->done_list->next;
// NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/);
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
DN_NET2Request request = {};
request.handle = DN_Cast(DN_UPtr) request_ptr;
request.gen = request_ptr->gen;
result = DN_NET2_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena);
return result;
}
+14
View File
@@ -0,0 +1,14 @@
#if !defined(DN_NET_EMSCRIPTEN_H)
#define DN_NET_EMSCRIPTEN_H
#include "dn_net2.h"
DN_NET2Interface DN_NET2_EmcInterface();
void DN_NET2_EmcInit (DN_NET2Core *net, char *base, DN_U64 base_size);
DN_NET2Request DN_NET2_EmcDoHTTP (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args);
DN_NET2Request DN_NET2_EmcDoWS (DN_NET2Core *net, DN_Str8 url);
void DN_NET2_EmcDoWSSend (DN_NET2Request request, DN_Str8 data, DN_NET2WSSend send);
DN_NET2Response DN_NET2_EmcWaitForResponse (DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms);
DN_NET2Response DN_NET2_EmcWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms);
#endif // DN_NET_EMSCRIPTEN_H
+162 -88
View File
@@ -241,8 +241,8 @@ static DN_UTCore DN_Tests_Base()
DN_UTCore result = DN_UT_Init();
DN_UT_LogF(&result, "DN_Base\n");
{
#if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC)
DN_RefImplCPUReport ref_cpu_report = DN_RefImplCPUReport_Init();
#if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC)
DN_RefImplCPUReport ref_cpu_report = DN_RefImplCPUReport_Init();
for (DN_UT_Test(&result, "Query CPUID")) {
DN_CPUReport cpu_report = DN_CPUGetReport();
@@ -279,67 +279,34 @@ static DN_UTCore DN_Tests_Base()
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSSE3) == ref_cpu_report.SSSE3());
// NOTE: Feature flags we haven't bothered detecting yet but are in MSDN's example /////////////
#if 0
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ADX) == DN_RefImplCPUReport::ADX());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI1) == DN_RefImplCPUReport::BMI1());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI2) == DN_RefImplCPUReport::BMI2());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CLFSH) == DN_RefImplCPUReport::CLFSH());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CX8) == DN_RefImplCPUReport::CX8());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ERMS) == DN_RefImplCPUReport::ERMS());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FSGSBASE) == DN_RefImplCPUReport::FSGSBASE());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FXSR) == DN_RefImplCPUReport::FXSR());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_HLE) == DN_RefImplCPUReport::HLE());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_INVPCID) == DN_RefImplCPUReport::INVPCID());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LAHF) == DN_RefImplCPUReport::LAHF());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LZCNT) == DN_RefImplCPUReport::LZCNT());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MSR) == DN_RefImplCPUReport::MSR());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_OSXSAVE) == DN_RefImplCPUReport::OSXSAVE());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PREFETCHWT1) == DN_RefImplCPUReport::PREFETCHWT1());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RTM) == DN_RefImplCPUReport::RTM());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SEP) == DN_RefImplCPUReport::SEP());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SYSCALL) == DN_RefImplCPUReport::SYSCALL());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_TBM) == DN_RefImplCPUReport::TBM());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XOP) == DN_RefImplCPUReport::XOP());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XSAVE) == DN_RefImplCPUReport::XSAVE());
#endif
/*
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ADX) == DN_RefImplCPUReport::ADX());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI1) == DN_RefImplCPUReport::BMI1());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI2) == DN_RefImplCPUReport::BMI2());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CLFSH) == DN_RefImplCPUReport::CLFSH());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CX8) == DN_RefImplCPUReport::CX8());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ERMS) == DN_RefImplCPUReport::ERMS());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FSGSBASE) == DN_RefImplCPUReport::FSGSBASE());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FXSR) == DN_RefImplCPUReport::FXSR());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_HLE) == DN_RefImplCPUReport::HLE());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_INVPCID) == DN_RefImplCPUReport::INVPCID());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LAHF) == DN_RefImplCPUReport::LAHF());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LZCNT) == DN_RefImplCPUReport::LZCNT());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MSR) == DN_RefImplCPUReport::MSR());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_OSXSAVE) == DN_RefImplCPUReport::OSXSAVE());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PREFETCHWT1) == DN_RefImplCPUReport::PREFETCHWT1());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RTM) == DN_RefImplCPUReport::RTM());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SEP) == DN_RefImplCPUReport::SEP());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SYSCALL) == DN_RefImplCPUReport::SYSCALL());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_TBM) == DN_RefImplCPUReport::TBM());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XOP) == DN_RefImplCPUReport::XOP());
DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XSAVE) == DN_RefImplCPUReport::XSAVE());
*/
}
#endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC)
for (DN_UT_Test(&result, "String")) {
char buffer[512];
DN_Arena arena = DN_ArenaFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_NoPoison | DN_ArenaFlags_AllocCanLeak);
// NOTE: CStr8Size
{
DN_USize size = DN_CStr8Size("hello");
DN_UT_AssertF(&result, size == 5, "size=%zu", size);
}
// NOTE: Str8FromFmtArena
{
DN_Str8 str8 = DN_Str8FromFmtArena(&arena, "Foo Bar %d", 5);
DN_Str8 expect = DN_Str8Lit("Foo Bar 5");
DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
}
// NOTE: Str8FromFmtPool
{
DN_Pool pool = DN_PoolFromArena(&arena, 0);
DN_Str8 str8 = DN_Str8FromFmtPool(&pool, "Foo Bar %d", 5);
DN_Str8 expect = DN_Str8Lit("Foo Bar 5");
DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
}
#endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC)
// NOTE: Str8x32FromU64
{
DN_Str8x32 str8 = DN_Str8x32FromU64(123456, ' ');
DN_Str8 expect = DN_Str8Lit("123 456");
DN_UT_AssertF(&result, DN_Str8Eq(DN_Str8FromStruct(&str8), expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
}
}
for (DN_UT_Test(&result, "Age")) {
for (DN_UT_Test(&result, "DN_Age")) {
// NOTE: Seconds and milliseconds
{
DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_Sec | DN_AgeUnit_Ms);
@@ -356,14 +323,12 @@ static DN_UTCore DN_Tests_Base()
}
for (DN_UT_Test(&result, "Misc")) {
{
char buf[8] = {};
DN_USize buf_size = 0;
DN_FmtAppendResult buf_str8 = DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "This string is longer than %d characters", DN_Cast(int)(sizeof(buf) - 1));
DN_Str8 expect = DN_Str8Lit("This..."); // 7 characters long, 1 byte reserved for null-terminator
DN_UT_Assert(&result, buf_str8.truncated);
DN_UT_AssertF(&result, DN_Str8Eq(buf_str8.str8, expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(buf_str8.str8), DN_Str8PrintFmt(expect));
}
char buf[8] = {};
DN_USize buf_size = 0;
DN_FmtAppendResult buf_str8 = DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "This string is longer than %d characters", DN_Cast(int)(sizeof(buf) - 1));
DN_Str8 expect = DN_Str8Lit("This..."); // 7 characters long, 1 byte reserved for null-terminator
DN_UT_Assert(&result, buf_str8.truncated);
DN_UT_AssertF(&result, DN_Str8Eq(buf_str8.str8, expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(buf_str8.str8), DN_Str8PrintFmt(expect));
}
}
return result;
@@ -1789,29 +1754,31 @@ void DN_Tests_KeccakDispatch_(DN_UTCore *test, int hash_type, DN_Str8 input)
} break;
}
}
#endif // defined(DN_UNIT_TESTS_WITH_KECCAK)
DN_UTCore DN_Tests_Keccak()
{
DN_UTCore test = DN_UT_Init();
DN_UTCore result = DN_UT_Init();
#if defined(DN_UNIT_TESTS_WITH_KECCAK)
DN_Str8 const INPUTS[] = {
DN_Str8Lit("abc"),
DN_Str8Lit(""),
DN_Str8Lit("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
DN_Str8Lit("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmno"
"pqrstnopqrstu"),
"pqrstnopqrstu"),
};
DN_UT_LogF(&test, "DN_KC\n");
DN_UT_LogF(&result, "DN_KC\n");
{
for (int hash_type = 0; hash_type < Hash_Count; hash_type++) {
DN_PCG32 rng = DN_PCG32_Init(0xd48e'be21'2af8'733d);
for (DN_Str8 input : INPUTS) {
DN_UT_BeginF(&test, "%.*s - Input: %.*s", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type]), DN_Cast(int) DN_Min(input.size, 54), input.data);
DN_Tests_KeccakDispatch_(&test, hash_type, input);
DN_UT_End(&test);
DN_UT_BeginF(&result, "%.*s - Input: %.*s", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type]), DN_Cast(int) DN_Min(input.size, 54), input.data);
DN_Tests_KeccakDispatch_(&result, hash_type, input);
DN_UT_End(&result);
}
DN_UT_BeginF(&test, "%.*s - Deterministic random inputs", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type]));
DN_UT_BeginF(&result, "%.*s - Deterministic random inputs", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type]));
for (DN_USize index = 0; index < 128; index++) {
char src[4096] = {};
uint32_t src_size = DN_PCG32_Range(&rng, 0, sizeof(src));
@@ -1820,14 +1787,14 @@ DN_UTCore DN_Tests_Keccak()
src[src_index] = DN_Cast(char) DN_PCG32_Range(&rng, 0, 255);
DN_Str8 input = DN_Str8FromPtr(src, src_size);
DN_Tests_KeccakDispatch_(&test, hash_type, input);
DN_Tests_KeccakDispatch_(&result, hash_type, input);
}
DN_UT_End(&test);
DN_UT_End(&result);
}
}
return test;
#endif
return result;
}
#endif // defined(DN_UNIT_TESTS_WITH_KECCAK)
static DN_UTCore DN_Tests_M4()
{
@@ -2140,13 +2107,40 @@ static DN_UTCore DN_Tests_Str8()
DN_UTCore result = DN_UT_Init();
DN_UT_LogF(&result, "DN_Str8\n");
{
for (DN_UT_Test(&result, "Initialise with string literal w/ macro")) {
for (DN_UT_Test(&result, "Str8 literal")) {
DN_Str8 string = DN_Str8Lit("AB");
DN_UT_AssertF(&result, string.size == 2, "size: %zu", string.size);
DN_UT_AssertF(&result, string.data[0] == 'A', "string[0]: %c", string.data[0]);
DN_UT_AssertF(&result, string.data[1] == 'B', "string[1]: %c", string.data[1]);
}
for (DN_UT_Test(&result, "C-string length")) {
DN_USize size = DN_CStr8Size("hello");
DN_UT_AssertF(&result, size == 5, "size=%zu", size);
}
char arena_base[256];
for (DN_UT_Test(&result, "Str8 format from arena")) {
DN_Arena arena = DN_ArenaFromBuffer(arena_base, sizeof(arena_base), DN_ArenaFlags_Nil);
DN_Str8 str8 = DN_Str8FromFmtArena(&arena, "Foo Bar %d", 5);
DN_Str8 expect = DN_Str8Lit("Foo Bar 5");
DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
}
for (DN_UT_Test(&result, "Str8 format from pool")) {
DN_Arena arena = DN_ArenaFromBuffer(arena_base, sizeof(arena_base), DN_ArenaFlags_Nil);
DN_Pool pool = DN_PoolFromArena(&arena, 0);
DN_Str8 str8 = DN_Str8FromFmtPool(&pool, "Foo Bar %d", 5);
DN_Str8 expect = DN_Str8Lit("Foo Bar 5");
DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
}
for (DN_UT_Test(&result, "Str8x32 from U64")) {
DN_Str8x32 str8 = DN_Str8x32FromU64(123456, ' ');
DN_Str8 expect = DN_Str8Lit("123 456");
DN_UT_AssertF(&result, DN_Str8Eq(DN_Str8FromStruct(&str8), expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect));
}
for (DN_UT_Test(&result, "Initialise with format string")) {
DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr);
DN_Str8 string = DN_Str8FromFmtArena(tmem.arena, "%s", "AB");
@@ -2443,10 +2437,10 @@ static DN_UTCore DN_Tests_TicketMutex()
return result;
}
#if defined(DN_PLATFORM_WIN32)
static DN_UTCore DN_Tests_Win()
{
DN_UTCore result = DN_UT_Init();
#if defined(DN_PLATFORM_WIN32)
DN_UT_LogF(&result, "OS Win32\n");
{
DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr);
@@ -2488,9 +2482,90 @@ static DN_UTCore DN_Tests_Win()
DN_UT_Assert(&result, DN_Memcmp(EXPECTED, string8.data, sizeof(EXPECTED)) == 0);
}
}
#endif // DN_PLATFORM_WIN32
return result;
}
static DN_UTCore DN_Tests_Net()
{
DN_Str8 label = {};
DN_NET2Interface net_interface = {};
#if defined(DN_PLATFORM_EMSCRIPTEN)
net_interface = DN_NET2_EmcInterface();
label = DN_Str8Lit("Emscripten");
#elif defined(DN_UNIT_TESTS_WITH_CURL)
net_interface = DN_NET2_CurlInterface();
label = DN_Str8Lit("CURL");
#endif
DN_UTCore result = DN_UT_Init();
if (label.size) {
DN_UT_LogF(&result, "DN_NET\n");
DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(4), DN_ArenaFlags_Nil);
DN_Str8 remote_ws_server_url = DN_Str8Lit("wss://echo.websocket.org");
DN_Str8 remote_http_server_url = DN_Str8Lit("https://google.com");
char net_base[DN_Kilobytes(16)] = {};
DN_NET2Core net = {};
net_interface.init(&net, net_base, sizeof(net_base));
for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP GET request", DN_Str8PrintFmt(label))) {
DN_NET2Request request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr);
DN_NET2Response response = net_interface.wait_for_response(request, &arena, UINT32_MAX);
DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status);
DN_UT_AssertF(&result, response.state == DN_NET2ResponseState_HTTP, "state=%u", response.state);
DN_UT_AssertF(&result, response.error_str8.size == 0, "%.*s", DN_Str8PrintFmt(response.error_str8));
DN_UT_Assert(&result, response.body.size);
}
for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP POST request", DN_Str8PrintFmt(label))) {
DN_NET2Request request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr);
DN_NET2Response response = net_interface.wait_for_any_response(&net, &arena, UINT32_MAX);
DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status);
DN_UT_AssertF(&result, response.state == DN_NET2ResponseState_HTTP, "state=%u", response.state);
DN_UT_AssertF(&result, response.error_str8.size == 0, "error=%.*s", DN_Str8PrintFmt(response.error_str8));
DN_UT_Assert(&result, response.body.size);
}
for (DN_UT_Test(&result, "%.*s WaitForResponse WS request", DN_Str8PrintFmt(label))) {
DN_NET2Request request = net_interface.do_ws(&net, remote_ws_server_url);
DN_USize const WS_TIMEOUT_MS = 2000;
// NOTE: Wait for WS connection to open
for (bool done = false; !done; DN_ArenaPopTo(&arena, 0)) {
DN_NET2Response response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
if (response.state == DN_NET2ResponseState_Nil) // NOTE: Timeout
continue;
DN_UT_Assert(&result, response.state == DN_NET2ResponseState_WSOpen);
done = true;
}
// NOTE: Receive the initial text from the echo server
for (bool done = false; !done; DN_ArenaPopTo(&arena, 0)) {
DN_NET2Response response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
if (response.state == DN_NET2ResponseState_Nil) // NOTE: Timeout
continue;
DN_UT_Assert(&result, response.state == DN_NET2ResponseState_WSText);
// NOTE: Send the close signal
net_interface.do_ws_send(request, DN_Str8Lit(""), DN_NET2WSSend_Close);
done = true;
}
// NOTE: Expect to hear the close
for (bool done = false; !done; DN_ArenaPopTo(&arena, 0)) {
DN_NET2Response response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS);
if (response.state == DN_NET2ResponseState_Nil) // NOTE: Timeout
continue;
DN_UT_Assert(&result, response.state == DN_NET2ResponseState_WSClose);
done = true;
}
}
DN_ArenaDeinit(&arena);
}
return result;
}
#endif // DN_PLATFORM_WIN32
DN_TestsResult DN_Tests_RunSuite(DN_TestsPrint print)
{
@@ -2498,21 +2573,18 @@ DN_TestsResult DN_Tests_RunSuite(DN_TestsPrint print)
{
DN_Tests_Base(),
DN_Tests_Arena(),
DN_Tests_Str8(),
DN_Tests_Bin(),
DN_Tests_TicketMutex(),
DN_Tests_BinarySearch(),
DN_Tests_BaseContainers(),
DN_Tests_Intrinsics(),
#if defined(DN_UNIT_TESTS_WITH_KECCAK)
DN_Tests_Keccak(),
#endif
DN_Tests_M4(),
DN_Tests_OS(),
DN_Tests_Rect(),
DN_Tests_Str8(),
DN_Tests_TicketMutex(),
#if defined(DN_PLATFORM_WIN32)
DN_Tests_Win(),
#endif
DN_Tests_Net(),
};
DN_TestsResult result = {};
@@ -2524,6 +2596,8 @@ DN_TestsResult DN_Tests_RunSuite(DN_TestsPrint print)
bool print_summary = false;
for (DN_UTCore &test : tests) {
if (test.num_tests_in_group <= 0)
continue;
bool do_print = print == DN_TestsPrint_Yes;
if (print == DN_TestsPrint_OnFailure && test.num_tests_ok_in_group != test.num_tests_in_group)
do_print = true;
+21
View File
@@ -1,3 +1,7 @@
#if defined(DN_UNIT_TESTS_WITH_CURL)
#define DN_NO_WINDOWS_H_REPLACEMENT_HEADER
#endif
#include "../dn_base_inc.h"
#include "../dn_os_inc.h"
#include "../dn_core_inc.h"
@@ -12,6 +16,23 @@
#include "../Extra/dn_math.cpp"
#include "../Extra/dn_helpers.cpp"
#include "../Extra/dn_net2.h"
#include "../Extra/dn_net2.cpp"
#if defined(DN_UNIT_TESTS_WITH_CURL)
#define CURL_STATICLIB
#include <curl/curl.h>
#include "../Extra/dn_net_curl.h"
#include "../Extra/dn_net_curl.cpp"
#endif
#if defined(DN_PLATFORM_EMSCRIPTEN)
#include <emscripten/emscripten.h>
#include <emscripten/fetch.h>
#include "../Extra/dn_net_emscripten.h"
#include "../Extra/dn_net_emscripten.cpp"
#endif
#define DN_UT_IMPLEMENTATION
#include "../Standalone/dn_utest.h"
#include "../Extra/dn_tests.cpp"