From efd4d9c7502779bdaf4e8404f912c2078e961b04 Mon Sep 17 00:00:00 2001 From: snarmph <1902881@uad.ac.uk> Date: Thu, 30 Sep 2021 17:42:28 +0200 Subject: [PATCH] first commit --- CMakeLists.txt | 14 ++ http.c | 359 ++++++++++++++++++++++++++++++++++ http.h | 128 +++++++++++++ socket.c | 277 +++++++++++++++++++++++++++ socket.h | 82 ++++++++ strstream.c | 511 +++++++++++++++++++++++++++++++++++++++++++++++++ strstream.h | 97 ++++++++++ strutils.c | 31 +++ strutils.h | 31 +++ strview.c | 250 ++++++++++++++++++++++++ strview.h | 67 +++++++ tracelog.c | 75 ++++++++ tracelog.h | 28 +++ 13 files changed, 1950 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 http.c create mode 100644 http.h create mode 100644 socket.c create mode 100644 socket.h create mode 100644 strstream.c create mode 100644 strstream.h create mode 100644 strutils.c create mode 100644 strutils.h create mode 100644 strview.c create mode 100644 strview.h create mode 100644 tracelog.c create mode 100644 tracelog.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e7412e2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(Colla STATIC + socket.c + tracelog.c + http.c + strstream.c + strview.c + strutils.c +) + +IF (WIN32) + target_link_libraries(Common ws2_32.lib) +ELSE() + # posix +ENDIF() \ No newline at end of file diff --git a/http.c b/http.c new file mode 100644 index 0000000..f75b5a6 --- /dev/null +++ b/http.c @@ -0,0 +1,359 @@ +#include "http.h" + +#include +#include +#include + +#include "strutils.h" +#include "tracelog.h" + +// == INTERNAL ================================================================ + +static void _setField(http_field_t **fields_ptr, int *fields_count_ptr, const char *key, const char *value) { + http_field_t *fields = *fields_ptr; + int fields_count = *fields_count_ptr; + + // search if the field already exists + for(int i = 0; i < fields_count; ++i) { + if(stricmp(fields[i].key, key) == 0) { + // replace value + char **curval = &fields[i].value; + size_t cur = strlen(*curval); + size_t new = strlen(value); + if(new > cur) { + *curval = realloc(*curval, new + 1); + } + memcpy(*curval, value, new); + (*curval)[new] = '\0'; + return; + } + } + // otherwise, add it to the list + (*fields_count_ptr)++;; + (*fields_ptr) = realloc(fields, sizeof(http_field_t) * (*fields_count_ptr)); + http_field_t *field = &(*fields_ptr)[(*fields_count_ptr) - 1]; + + size_t klen = strlen(key); + size_t vlen = strlen(value); + field->key = malloc(klen + 1); + field->value = malloc(vlen + 1); + memcpy(field->key, key, klen); + memcpy(field->value, value, vlen); + field->key[klen] = field->value[vlen] = '\0'; +} + +// == HTTP VERSION ============================================================ + +int httpVerNumber(http_version_t ver) { + return (ver.major * 10) + ver.minor; +} + +// == HTTP REQUEST ============================================================ + +http_request_t reqInit() { + http_request_t req; + memset(&req, 0, sizeof(req)); + reqSetUri(&req, "/"); + req.version = (http_version_t){1, 1}; + return req; +} + +void reqFree(http_request_t *ctx) { + for(int i = 0; i < ctx->field_count; ++i) { + free(ctx->fields[i].key); + free(ctx->fields[i].value); + } + free(ctx->fields); + free(ctx->uri); + free(ctx->body); + memset(ctx, 0, sizeof(http_request_t)); +} + +bool reqHasField(http_request_t *ctx, const char *key) { + for(int i = 0; i < ctx->field_count; ++i) { + if(stricmp(ctx->fields[i].key, key) == 0) { + return true; + } + } + return false; +} + +void reqSetField(http_request_t *ctx, const char *key, const char *value) { + _setField(&ctx->fields, &ctx->field_count, key, value); +} + +void reqSetUri(http_request_t *ctx, const char *uri) { + if(uri == NULL) return; + size_t len = strlen(uri); + if(uri[0] != '/') { + len += 1; + ctx->uri = realloc(ctx->uri, len + 1); + ctx->uri[0] = '/'; + memcpy(ctx->uri + 1, uri, len); + ctx->uri[len] = '\0'; + } + else { + ctx->uri = realloc(ctx->uri, len + 1); + memcpy(ctx->uri, uri, len); + ctx->uri[len] = '\0'; + } +} + +str_ostream_t reqPrepare(http_request_t *ctx) { + str_ostream_t out = ostrInitLen(1024); + + const char *method = NULL; + switch(ctx->method) { + case REQ_GET: method = "GET"; break; + case REQ_POST: method = "POST"; break; + case REQ_HEAD: method = "HEAD"; break; + case REQ_PUT: method = "PUT"; break; + case REQ_DELETE: method = "DELETE"; break; + default: err("unrecognized method: %d", method); goto error; + } + + ostrPrintf(&out, "%s %s HTTP/%hhu.%hhu\r\n", + method, ctx->uri, ctx->version.major, ctx->version.minor + ); + + for(int i = 0; i < ctx->field_count; ++i) { + ostrPrintf(&out, "%s: %s\r\n", ctx->fields[i].key, ctx->fields[i].value); + } + + ostrAppendview(&out, strvInit("\r\n")); + if(ctx->body) { + ostrAppendview(&out, strvInit(ctx->body)); + } + +error: + return out; +} + +size_t reqString(http_request_t *ctx, char **str) { + str_ostream_t out = reqPrepare(ctx); + return ostrMove(&out, str); +} + +// == HTTP RESPONSE =========================================================== + +http_response_t resInit() { + http_response_t res; + memset(&res, 0, sizeof(res)); + return res; +} + +void resFree(http_response_t *ctx) { + for(int i = 0; i < ctx->field_count; ++i) { + free(ctx->fields[i].key); + free(ctx->fields[i].value); + } + free(ctx->fields); + free(ctx->body); + memset(ctx, 0, sizeof(http_response_t)); +} + +bool resHasField(http_response_t *ctx, const char *key) { + for(int i = 0; i < ctx->field_count; ++i) { + if(stricmp(ctx->fields[i].key, key) == 0) { + return true; + } + } + return false; +} + +const char *resGetField(http_response_t *ctx, const char *field) { + for(int i = 0; i < ctx->field_count; ++i) { + if(stricmp(ctx->fields[i].key, field) == 0) { + return ctx->fields[i].value; + } + } + return NULL; +} + +void resParse(http_response_t *ctx, const char *data) { + str_istream_t in = istrInit(data); + + char hp[5]; + istrGetstringBuf(&in, hp, 5); + if(stricmp(hp, "http") != 0) { + err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]); + return; + } + istrSkip(&in, 1); // skip / + istrGetu8(&in, &ctx->version.major); + istrSkip(&in, 1); // skip . + istrGetu8(&in, &ctx->version.minor); + istrGeti32(&in, &ctx->status_code); + + istrIgnore(&in, '\n'); + istrSkip(&in, 1); // skip \n + + resParseFields(ctx, &in); + + const char *tran_encoding = resGetField(ctx, "transfer-encoding"); + if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) { + strview_t body = istrGetviewLen(&in, 0, SIZE_MAX); + free(ctx->body); + strvCopy(body, &ctx->body); + } + else { + fatal("chunked encoding not implemented yet"); + } +} + +void resParseFields(http_response_t *ctx, str_istream_t *in) { + strview_t line; + + do { + line = istrGetview(in, '\r'); + + size_t pos = strvFind(line, ':', 0); + if(pos != STRV_NOT_FOUND) { + strview_t key = strvSubstr(line, 0, pos); + strview_t value = strvSubstr(line, pos + 2, SIZE_MAX); + + char *key_str = NULL; + char *value_str = NULL; + + strvCopy(key, &key_str); + strvCopy(value, &value_str); + + _setField(&ctx->fields, &ctx->field_count, key_str, value_str); + + free(key_str); + free(value_str); + } + + istrSkip(in, 2); // skip \r\n + } while(line.size > 2); +} + +// == HTTP CLIENT ============================================================= + +http_client_t hcliInit() { + http_client_t client; + memset(&client, 0, sizeof(client)); + client.port = 80; + return client; +} + +void hcliFree(http_client_t *ctx) { + free(ctx->host_name); + memset(ctx, 0, sizeof(http_client_t)); +} + +void hcliSetHost(http_client_t *ctx, const char *hostname) { + strview_t hostview = strvInit(hostname); + // if the hostname starts with http:// (case insensitive) + if(strvICompare(strvSubstr(hostview, 0, 7), strvInit("http://")) == 0) { + strvCopy(strvSubstr(hostview, 7, SIZE_MAX), &ctx->host_name); + } + else if(strvICompare(strvSubstr(hostview, 0, 8), strvInit("https://")) == 0) { + err("HTTPS protocol not yet supported"); + return; + } + else { + // undefined protocol, use HTTP + strvCopy(hostview, &ctx->host_name); + } +} + +http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) { + if(!reqHasField(req, "Host")) { + reqSetField(req, "Host", ctx->host_name); + } + if(!reqHasField(req, "Content-Length")) { + if(req->body) { + str_ostream_t out = ostrInitLen(20); + ostrAppendu64(&out, strlen(req->body)); + reqSetField(req, "Content-Length", out.buf); + ostrFree(&out); + } + else { + reqSetField(req, "Content-Length", "0"); + } + } + if(req->method == REQ_POST && !reqHasField(req, "Content-Type")) { + reqSetField(req, "Content-Type", "application/x-www-form-urlencoded"); + } + if(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) { + reqSetField(req, "Connection", "close"); + } + + http_response_t res = resInit(); + char *request_str = NULL; + str_ostream_t received = ostrInit(1024); + + if(!skInit()) { + err("couldn't initialize sockets %s", skGetErrorString()); + goto skopen_error; + } + + ctx->socket = skOpen(); + if(ctx->socket == INVALID_SOCKET) { + err("couldn't open socket %s", skGetErrorString()); + goto error; + } + + if(skConnect(ctx->socket, ctx->host_name, ctx->port)) { + size_t len = reqString(req, &request_str); + if(len == 0) { + err("couldn't get string from request"); + goto error; + } + + if(skSend(ctx->socket, request_str, (int)len) == SOCKET_ERROR) { + err("couldn't send request to socket: %s", skGetErrorString()); + goto error; + } + + char buffer[1024]; + int read = 0; + do { + read = skReceive(ctx->socket, buffer, sizeof(buffer)); + if(read == -1) { + err("couldn't get the data from the server: %s", skGetErrorString()); + goto error; + } + ostrAppendview(&received, strvInitLen(buffer, read)); + } while(read != 0); + + // if the data received is not null terminated + if(*(received.buf + received.size) != '\0') { + ostrPutc(&received, '\0'); + received.size--; + } + + resParse(&res, received.buf); + } + + if(!skClose(ctx->socket)) { + err("Couldn't close socket"); + } + +error: + if(!skCleanup()) { + err("couldn't clean up sockets %s", skGetErrorString()); + } +skopen_error: + free(request_str); + ostrFree(&received); + return res; +} + +http_response_t httpGet(const char *hostname, const char *uri) { + http_request_t request = reqInit(); + request.method = REQ_GET; + reqSetUri(&request, uri); + + http_client_t client = hcliInit(); + hcliSetHost(&client, hostname); + + http_response_t res = hcliSendRequest(&client, &request); + + reqFree(&request); + hcliFree(&client); + + return res; +} + diff --git a/http.h b/http.h new file mode 100644 index 0000000..146bd73 --- /dev/null +++ b/http.h @@ -0,0 +1,128 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "strstream.h" +#include "strview.h" +#include "socket.h" + +enum { + REQ_GET, + REQ_POST, + REQ_HEAD, + REQ_PUT, + REQ_DELETE +}; + +enum { + // 2xx: success + STATUS_OK = 200, + STATUS_CREATED = 201, + STATUS_ACCEPTED = 202, + STATUS_NO_CONTENT = 204, + STATUS_RESET_CONTENT = 205, + STATUS_PARTIAL_CONTENT = 206, + + // 3xx: redirection + STATUS_MULTIPLE_CHOICES = 300, + STATUS_MOVED_PERMANENTLY = 301, + STATUS_MOVED_TEMPORARILY = 302, + STATUS_NOT_MODIFIED = 304, + + // 4xx: client error + STATUS_BAD_REQUEST = 400, + STATUS_UNAUTHORIZED = 401, + STATUS_FORBIDDEN = 403, + STATUS_NOT_FOUND = 404, + STATUS_RANGE_NOT_SATISFIABLE = 407, + + // 5xx: server error + STATUS_INTERNAL_SERVER_ERROR = 500, + STATUS_NOT_IMPLEMENTED = 501, + STATUS_BAD_GATEWAY = 502, + STATUS_SERVICE_NOT_AVAILABLE = 503, + STATUS_GATEWAY_TIMEOUT = 504, + STATUS_VERSION_NOT_SUPPORTED = 505, +}; + +typedef struct { + uint8_t major; + uint8_t minor; +} http_version_t; + +// transaltes a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc) +int httpVerNumber(http_version_t ver); + +typedef struct { + char *key; + char *value; +} http_field_t; + +// == HTTP REQUEST ============================================================ + +typedef struct { + int method; + http_version_t version; + http_field_t *fields; + int field_count; + char *uri; + char *body; +} http_request_t; + +http_request_t reqInit(); +void reqFree(http_request_t *ctx); + +bool reqHasField(http_request_t *ctx, const char *key); + +void reqSetField(http_request_t *ctx, const char *key, const char *value); +void reqSetUri(http_request_t *ctx, const char *uri); + +str_ostream_t reqPrepare(http_request_t *ctx); +size_t reqString(http_request_t *ctx, char **str); + +// == HTTP RESPONSE =========================================================== + +typedef struct { + int status_code; + http_field_t *fields; + int field_count; + http_version_t version; + char *body; +} http_response_t; + +http_response_t resInit(); +void resFree(http_response_t *ctx); + +bool resHasField(http_response_t *ctx, const char *key); +const char *resGetField(http_response_t *ctx, const char *field); + +void resParse(http_response_t *ctx, const char *data); +void resParseFields(http_response_t *ctx, str_istream_t *in); + +// == HTTP CLIENT ============================================================= + +typedef struct { + char *host_name; + uint16_t port; + socket_t socket; +} http_client_t; + +http_client_t hcliInit(); +void hcliFree(http_client_t *ctx); + +void hcliSetHost(http_client_t *ctx, const char *hostname); +http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request); + +// == HTTP ==================================================================== + +http_response_t httpGet(const char *hostname, const char *uri); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/socket.c b/socket.c new file mode 100644 index 0000000..798bb1e --- /dev/null +++ b/socket.c @@ -0,0 +1,277 @@ +#include "socket.h" + +#include + +#undef INVALID_SOCKET +#undef SOCKET_ERROR + +#if SOCK_WINDOWS +#include + +static bool _win_skInit(); +static bool _win_skCleanup(); +static int _win_skGetError(); +static const char *_win_skGetErrorString(); + +#define SOCK_CALL(fun) _win_##fun + +#elif SOCK_POSIX +#include +#include +#include +#include +#include +#include // strerror + +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) + +static bool _posix_skInit(); +static bool _posix_skCleanup(); +static int _posix_skGetError(); +static const char *_posix_skGetErrorString(); + +#define SOCK_CALL(fun) _posix_##fun + +#endif + +bool skInit() { + return SOCK_CALL(skInit()); +} + +bool skCleanup() { + return SOCK_CALL(skCleanup()); +} + +socket_t skOpen() { + return skOpenPro(AF_INET, SOCK_STREAM, 0); +} + +socket_t skOpenEx(const char *protocol) { + struct protoent *proto = getprotobyname(protocol); + if(!proto) { + return INVALID_SOCKET; + } + return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); +} + +socket_t skOpenPro(int af, int type, int protocol) { + return socket(af, type, protocol); +} + +bool skClose(socket_t sock) { +#if SOCK_WINDOWS + int error = closesocket(sock); +#elif SOCK_POSIX + int error = close(sock); +#endif + sock = INVALID_SOCKET; + return error != SOCKET_ERROR; +} + +bool skBind(socket_t sock, const char *ip, uint16_t port) { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(ip); + addr.sin_port = htons(port); + + return skBindPro(sock, (struct sockaddr *) &addr, sizeof(addr)); +} + +bool skBindPro(socket_t sock, const struct sockaddr *name, socket_len_t namelen) { + return bind(sock, name, namelen) != SOCKET_ERROR; +} + +bool skListen(socket_t sock) { + return skListenPro(sock, 1); +} + +bool skListenPro(socket_t sock, int backlog) { + return listen(sock, backlog) != SOCKET_ERROR; +} + +socket_t skAccept(socket_t sock) { + struct sockaddr_in addr; + socket_len_t addr_size = (socket_len_t)sizeof(addr); + return skAcceptPro(sock, (struct sockaddr *) &addr, &addr_size); +} + +socket_t skAcceptPro(socket_t sock, struct sockaddr *addr, socket_len_t *addrlen) { + return accept(sock, addr, addrlen); +} + +bool skConnect(socket_t sock, const char *server, unsigned short server_port) { + struct hostent *host = gethostbyname(server); + // if gethostbyname fails, inet_addr will also fail and return an easier to debug error + const char *address = server; + if(host) { + address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(address); + addr.sin_port = htons(server_port); + + return skConnectPro(sock, (struct sockaddr *) &addr, sizeof(addr)); +} + +bool skConnectPro(socket_t sock, const struct sockaddr *name, socket_len_t namelen) { + return connect(sock, name, namelen) != SOCKET_ERROR; +} + +int skSend(socket_t sock, char *buf, int len) { + return send(sock, buf, len, 0); +} + +int skReceive(socket_t sock, char *buf, int len) { + return recv(sock, buf, len, 0); +} + +bool skIsValid(socket_t sock) { + return sock != INVALID_SOCKET; +} + +int skGetError() { + return SOCK_CALL(skGetError()); +} + +const char *skGetErrorString() { + return SOCK_CALL(skGetErrorString()); +} + +#ifdef SOCK_WINDOWS +static bool _win_skInit() { + WSADATA w; + int error = WSAStartup(0x0202, &w); + return error == 0; +} + +static bool _win_skCleanup() { + return WSACleanup() == 0; +} + +static int _win_skGetError() { + return WSAGetLastError(); +} + +static const char *_win_skGetErrorString() { + switch(_win_skGetError()) { + case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; + case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; + case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; + case WSA_OPERATION_ABORTED: return "Overlapped operation aborted."; + case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state."; + case WSA_IO_PENDING: return "Overlapped operations will complete later."; + case WSAEINTR: return "Interrupted function call."; + case WSAEBADF: return "File handle is not valid."; + case WSAEACCES: return "Permission denied."; + case WSAEFAULT: return "Bad address."; + case WSAEINVAL: return "Invalid argument."; + case WSAEMFILE: return "Too many open files."; + case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; + case WSAEINPROGRESS: return "Operation now in progress."; + case WSAEALREADY: return "Operation already in progress."; + case WSAENOTSOCK: return "Socket operation on nonsocket."; + case WSAEDESTADDRREQ: return "Destination address required."; + case WSAEMSGSIZE: return "Message too long."; + case WSAEPROTOTYPE: return "Protocol wrong type for socket."; + case WSAENOPROTOOPT: return "Bad protocol option."; + case WSAEPROTONOSUPPORT: return "Protocol not supported."; + case WSAESOCKTNOSUPPORT: return "Socket type not supported."; + case WSAEOPNOTSUPP: return "Operation not supported."; + case WSAEPFNOSUPPORT: return "Protocol family not supported."; + case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; + case WSAEADDRINUSE: return "Address already in use."; + case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; + case WSAENETDOWN: return "Network is down."; + case WSAENETUNREACH: return "Network is unreachable."; + case WSAENETRESET: return "Network dropped connection on reset."; + case WSAECONNABORTED: return "Software caused connection abort."; + case WSAECONNRESET: return "Connection reset by peer."; + case WSAENOBUFS: return "No buffer space available."; + case WSAEISCONN: return "Socket is already connected."; + case WSAENOTCONN: return "Socket is not connected."; + case WSAESHUTDOWN: return "Cannot send after socket shutdown."; + case WSAETOOMANYREFS: return "Too many references."; + case WSAETIMEDOUT: return "Connection timed out."; + case WSAECONNREFUSED: return "Connection refused."; + case WSAELOOP: return "Cannot translate name."; + case WSAENAMETOOLONG: return "Name too long."; + case WSAEHOSTDOWN: return "Host is down."; + case WSAEHOSTUNREACH: return "No route to host."; + case WSAENOTEMPTY: return "Directory not empty."; + case WSAEPROCLIM: return "Too many processes."; + case WSAEUSERS: return "User quota exceeded."; + case WSAEDQUOT: return "Disk quota exceeded."; + case WSAESTALE: return "Stale file handle reference."; + case WSAEREMOTE: return "Item is remote."; + case WSASYSNOTREADY: return "Network subsystem is unavailable."; + case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range."; + case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; + case WSAEDISCON: return "Graceful shutdown in progress."; + case WSAENOMORE: return "No more results."; + case WSAECANCELLED: return "Call has been canceled."; + case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid."; + case WSAEINVALIDPROVIDER: return "Service provider is invalid."; + case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize."; + case WSASYSCALLFAILURE: return "System call failure."; + case WSASERVICE_NOT_FOUND: return "Service not found."; + case WSATYPE_NOT_FOUND: return "Class type not found."; + case WSA_E_NO_MORE: return "No more results."; + case WSA_E_CANCELLED: return "Call was canceled."; + case WSAEREFUSED: return "Database query was refused."; + case WSAHOST_NOT_FOUND: return "Host not found."; + case WSATRY_AGAIN: return "Nonauthoritative host not found."; + case WSANO_RECOVERY: return "This is a nonrecoverable error."; + case WSANO_DATA: return "Valid name, no data record of requested type."; + case WSA_QOS_RECEIVERS: return "QoS receivers."; + case WSA_QOS_SENDERS: return "QoS senders."; + case WSA_QOS_NO_SENDERS: return "No QoS senders."; + case WSA_QOS_NO_RECEIVERS: return "QoS no receivers."; + case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed."; + case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error."; + case WSA_QOS_POLICY_FAILURE: return "QoS policy failure."; + case WSA_QOS_BAD_STYLE: return "QoS bad style."; + case WSA_QOS_BAD_OBJECT: return "QoS bad object."; + case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error."; + case WSA_QOS_GENERIC_ERROR: return "QoS generic error."; + case WSA_QOS_ESERVICETYPE: return "QoS service type error."; + case WSA_QOS_EFLOWSPEC: return "QoS flowspec error."; + case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer."; + case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style."; + case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type."; + case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count."; + case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length."; + case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count."; + case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object."; + case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object."; + case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor."; + case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec."; + case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec."; + case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object."; + case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object."; + case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type."; + } + + return "(nothing)"; +} + +#else + +static bool _posix_skInit() { + return true; +} + +static bool _posix_skCleanup() { + return true; +} + +static int _posix_skGetError() { + return errno; +} + +static const char *_posix_skGetErrorString() { + return strerror(errno); +} +#endif \ No newline at end of file diff --git a/socket.h b/socket.h new file mode 100644 index 0000000..f9ed90b --- /dev/null +++ b/socket.h @@ -0,0 +1,82 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifdef _WIN32 + #define SOCK_WINDOWS 1 +#else + #define SOCK_POSIX 1 +#endif + +struct sockaddr; + +#if SOCK_WINDOWS + typedef uintptr_t socket_t; + typedef int socket_len_t; + #define INVALID_SOCKET (socket_t)(~0) + #define SOCKET_ERROR (-1) +#elif SOCK_POSIX + typedef int socket_t; + typedef uint32_t socket_len_t; + #define INVALID_SOCKET (-1) + #define SOCKET_ERROR (-1) +#endif + +// Initialize sockets, returns true on success +bool skInit(); +// Terminates sockets, returns true on success +bool skCleanup(); + +// Opens a socket, check socket_t with skValid +socket_t skOpen(); +// Opens a socket using 'protocol', options are +// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp +// check socket_t with skValid +socket_t skOpenEx(const char *protocol); +// Opens a socket, check socket_t with skValid +socket_t skOpenPro(int af, int type, int protocol); + +// Closes a socket, returns true on success +bool skClose(socket_t sock); + +// Associate a local address with a socket +bool skBind(socket_t sock, const char *ip, uint16_t port); +// Associate a local address with a socket +bool skBindPro(socket_t sock, const struct sockaddr *name, socket_len_t namelen); + +// Place a socket in a state in which it is listening for an incoming connection +bool skListen(socket_t sock); +// Place a socket in a state in which it is listening for an incoming connection +bool skListenPro(socket_t sock, int backlog); + +// Permits an incoming connection attempt on a socket +socket_t skAccept(socket_t sock); +// Permits an incoming connection attempt on a socket +socket_t skAcceptPro(socket_t sock, struct sockaddr *addr, socket_len_t *addrlen); + +// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success +bool skConnect(socket_t sock, const char *server, unsigned short server_port); +// Connects to a server, returns true on success +bool skConnectPro(socket_t sock, const struct sockaddr *name, socket_len_t namelen); + +// Sends data on a socket, returns true on success +int skSend(socket_t sock, char *buf, int len); +// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error +int skReceive(socket_t sock, char *buf, int len); + +// Checks that a opened socket is valid, returns true on success +bool skIsValid(socket_t sock); + +// Returns latest socket error, returns 0 if there is no error +int skGetError(); +// Returns a human-readable string from a skGetError +const char *skGetErrorString(); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/strstream.c b/strstream.c new file mode 100644 index 0000000..87daa11 --- /dev/null +++ b/strstream.c @@ -0,0 +1,511 @@ +#include "strstream.h" + +#include +#include +#include +#include +#include +#include // HUGE_VALF +#include "tracelog.h" + +/* == INPUT STREAM ============================================ */ + +str_istream_t istrInit(const char *str) { + return istrInitLen(str, strlen(str)); +} + +str_istream_t istrInitLen(const char *str, size_t len) { + str_istream_t res; + res.start = res.cur = str; + res.size = len; + return res; +} + +char istrGet(str_istream_t *ctx) { + return *ctx->cur++; +} + +void istrIgnore(str_istream_t *ctx, char delim) { + size_t position = ctx->cur - ctx->start; + size_t i; + for(i = position; + i < ctx->size && *ctx->cur != delim; + ++i, ++ctx->cur); +} + +char istrPeek(str_istream_t *ctx) { + return *ctx->cur; +} + +void istrSkip(str_istream_t *ctx, size_t n) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + if(n > remaining) { + warn("skipping more then remaining: %zu -> %zu", n, remaining); + return; + } + ctx->cur += n; +} + +void istrRead(str_istream_t *ctx, char *buf, size_t len) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + if(len > remaining) { + warn("istrRead: trying to read len %zu from remaining %zu", len, remaining); + return; + } + memcpy(buf, ctx->cur, len); + ctx->cur += len; +} + +size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + len = remaining < len ? remaining : len; + memcpy(buf, ctx->cur, len); + ctx->cur += len; + return len; +} + +void istrRewind(str_istream_t *ctx) { + ctx->cur = ctx->start; +} + +size_t istrTell(str_istream_t *ctx) { + return ctx->cur - ctx->start; +} + +bool istrIsFinished(str_istream_t *ctx) { + return ctx->cur == (ctx->start + ctx->size + 1); +} + +bool istrGetbool(str_istream_t *ctx, bool *val) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + if(strncmp(ctx->cur, "true", remaining) == 0) { + *val = true; + return true; + } + if(strncmp(ctx->cur, "false", remaining) == 0) { + *val = false; + return true; + } + return false; +} + +bool istrGetu8(str_istream_t *ctx, uint8_t *val) { + char *end = NULL; + *val = (uint8_t) strtoul(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetu8: no valid conversion could be performed"); + return false; + } + else if(*val == UINT8_MAX) { + warn("istrGetu8: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetu16(str_istream_t *ctx, uint16_t *val) { + char *end = NULL; + *val = (uint16_t) strtoul(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetu16: no valid conversion could be performed"); + return false; + } + else if(*val == UINT16_MAX) { + warn("istrGetu16: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetu32(str_istream_t *ctx, uint32_t *val) { + char *end = NULL; + *val = (uint32_t) strtoul(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetu32: no valid conversion could be performed"); + return false; + } + else if(*val == UINT32_MAX) { + warn("istrGetu32: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetu64(str_istream_t *ctx, uint64_t *val) { + char *end = NULL; + *val = strtoull(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetu64: no valid conversion could be performed"); + return false; + } + else if(*val == ULLONG_MAX) { + warn("istrGetu64: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGeti8(str_istream_t *ctx, int8_t *val) { + char *end = NULL; + *val = (int8_t) strtol(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGeti8: no valid conversion could be performed"); + return false; + } + else if(*val == INT8_MAX || *val == INT8_MIN) { + warn("istrGeti8: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGeti16(str_istream_t *ctx, int16_t *val) { + char *end = NULL; + *val = (int16_t) strtol(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGeti16: no valid conversion could be performed"); + return false; + } + else if(*val == INT16_MAX || *val == INT16_MIN) { + warn("istrGeti16: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGeti32(str_istream_t *ctx, int32_t *val) { + char *end = NULL; + *val = (int32_t) strtol(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGeti32: no valid conversion could be performed"); + return false; + } + else if(*val == INT32_MAX || *val == INT32_MIN) { + warn("istrGeti32: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGeti64(str_istream_t *ctx, int64_t *val) { + char *end = NULL; + *val = strtoll(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGeti64: no valid conversion could be performed"); + return false; + } + else if(*val == INT64_MAX || *val == INT64_MIN) { + warn("istrGeti64: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetfloat(str_istream_t *ctx, float *val) { + char *end = NULL; + *val = strtof(ctx->cur, &end); + + if(ctx->cur == end) { + warn("istrGetfloat: no valid conversion could be performed"); + return false; + } + else if(*val == HUGE_VALF || *val == -HUGE_VALF) { + warn("istrGetfloat: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetdouble(str_istream_t *ctx, double *val) { + char *end = NULL; + *val = strtod(ctx->cur, &end); + + if(ctx->cur == end) { + warn("istrGetdouble: no valid conversion could be performed"); + return false; + } + else if(*val == HUGE_VAL || *val == -HUGE_VAL) { + warn("istrGetdouble: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +size_t istrGetstring(str_istream_t *ctx, char **val, char delim) { + const char *from = ctx->cur; + istrIgnore(ctx, delim); + // if it didn't actually find it, it just reached the end of the string + if(*ctx->cur != delim) { + *val = NULL; + return 0; + } + size_t len = ctx->cur - from; + *val = malloc(len + 1); + memcpy(*val, from, len); + (*val)[len] = '\0'; + return len; +} + +size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + len -= 1; + len = remaining < len ? remaining : len; + memcpy(val, ctx->cur, len); + val[len] = '\0'; + ctx->cur += len; + return len; +} + +strview_t istrGetview(str_istream_t *ctx, char delim) { + const char *from = ctx->cur; + istrIgnore(ctx, delim); + // if it didn't actually find it, it just reached the end of the string + if(*ctx->cur != delim) { + return strvInitLen(NULL, 0); + } + size_t len = ctx->cur - from; + return strvInitLen(from, len); +} + +strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len) { + size_t remaining = ctx->size - (ctx->cur - ctx->start) - off; + if(len > remaining) len = remaining; + return strvInitLen(ctx->cur + off, len); +} + +/* == OUTPUT STREAM =========================================== */ + +static void _ostrRealloc(str_ostream_t *ctx, size_t atlest) { + ctx->allocated = (ctx->allocated * 2) + atlest; + ctx->buf = realloc(ctx->buf, ctx->allocated); +} + +str_ostream_t ostrInit() { + return ostrInitLen(1); +} + +str_ostream_t ostrInitLen(size_t initial_alloc) { + str_ostream_t stream; + stream.buf = calloc(initial_alloc, 1); + stream.size = 0; + stream.allocated = initial_alloc; + return stream; +} + +str_ostream_t ostrInitStr(const char *cstr, size_t len) { + str_ostream_t stream; + stream.buf = malloc(len + 1); + memcpy(stream.buf, cstr, len); + return stream; +} + +char ostrBack(str_ostream_t *ctx) { + return ctx->buf[ctx->size - 1]; +} + +void ostrFree(str_ostream_t *ctx) { + free(ctx->buf); + ctx->size = 0; + ctx->allocated = 0; +} + +size_t ostrMove(str_ostream_t *ctx, char **str) { + *str = ctx->buf; + ctx->buf = NULL; + size_t sz = ctx->size; + ostrFree(ctx); + return sz; +} + +void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) { + va_list va; + va_start(va, fmt); + + // vsnprintf returns the length of the formatted string, even if truncated + // we use this to get the actual length of the formatted string + char buf[1]; + va_list vtemp; + va_copy(vtemp, va); + int len = vsnprintf(buf, sizeof(buf), fmt, vtemp); + va_end(vtemp); + if(len < 0) { + err("couldn't format string \"%s\"", fmt); + goto error; + } + + size_t remaining = ctx->allocated - ctx->size; + if(remaining < (size_t)len) { + _ostrRealloc(ctx, len + 1); + remaining = ctx->allocated - ctx->size; + } + + // actual formatting here + len = vsnprintf(ctx->buf + ctx->size, remaining, fmt, va); + if(len < 0) { + err("couldn't format stringh \"%s\"", fmt); + goto error; + } + ctx->size += len; + +error: + va_end(va); +} + +#define APPEND_BUF_LEN 20 + +void ostrPutc(str_ostream_t *ctx, char c) { + if(ctx->size >= ctx->allocated) { + _ostrRealloc(ctx, 1); + } + ctx->buf[ctx->size++] = c; + ctx->buf[ctx->size] = '\0'; +} + +void ostrAppendbool(str_ostream_t *ctx, bool val) { + ostrAppendview(ctx, strvInit(val ? "true" : "false")); +} + +void ostrAppendu8(str_ostream_t *ctx, uint8_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%hhu", val); + if(len <= 0) { + err("ostrAppendu8: couldn't write %hhu", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendu16(str_ostream_t *ctx, uint16_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%hu", val); + if(len <= 0) { + err("ostrAppendu16: couldn't write %hu", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendu32(str_ostream_t *ctx, uint32_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%u", val); + if(len <= 0) { + err("ostrAppendu32: couldn't write %u", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendu64(str_ostream_t *ctx, uint64_t val) { + char buf[APPEND_BUF_LEN]; +#if _WIN32 + int len = snprintf(buf, sizeof(buf), "%llu", val); +#else + int len = snprintf(buf, sizeof(buf), "%lu", val); +#endif + if(len <= 0) { + err("ostrAppendu64: couldn't write %lu", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendi8(str_ostream_t *ctx, int8_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%hhi", val); + if(len <= 0) { + err("ostrAppendi8: couldn't write %hhi", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendi16(str_ostream_t *ctx, int16_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%hi", val); + if(len <= 0) { + err("ostrAppendi16: couldn't write %hi", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendi32(str_ostream_t *ctx, int32_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%i", val); + if(len <= 0) { + err("ostrAppendi32: couldn't write %i", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendi64(str_ostream_t *ctx, int64_t val) { + char buf[APPEND_BUF_LEN]; +#if _WIN32 + int len = snprintf(buf, sizeof(buf), "%lli", val); +#else + int len = snprintf(buf, sizeof(buf), "%li", val); +#endif + if(len <= 0) { + err("ostrAppendi64: couldn't write %li", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendfloat(str_ostream_t *ctx, float val) { + char buf[APPEND_BUF_LEN * 3]; + int len = snprintf(buf, sizeof(buf), "%f", (double)val); + if(len <= 0) { + err("ostrAppendfloat: couldn't write %f", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppenddouble(str_ostream_t *ctx, double val) { + char buf[APPEND_BUF_LEN * 3]; + int len = snprintf(buf, sizeof(buf), "%f", val); + if(len <= 0) { + err("ostrAppenddouble: couldn't write %f", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendview(str_ostream_t *ctx, strview_t view) { + if((ctx->allocated - ctx->size) < view.size) { + _ostrRealloc(ctx, view.size + 1); + } + memcpy(ctx->buf + ctx->size, view.buf, view.size); + ctx->size += view.size; + ctx->buf[ctx->size] = '\0'; +} \ No newline at end of file diff --git a/strstream.h b/strstream.h new file mode 100644 index 0000000..ece2293 --- /dev/null +++ b/strstream.h @@ -0,0 +1,97 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "strview.h" + +/* == INPUT STREAM ============================================ */ + +typedef struct { + const char *start; + const char *cur; + size_t size; +} str_istream_t; + +// initialize with null-terminated string +str_istream_t istrInit(const char *str); +str_istream_t istrInitLen(const char *str, size_t len); + +// get the current character and advance +char istrGet(str_istream_t *ctx); +// get the current character but don't advance +char istrPeek(str_istream_t *ctx); +// ignore characters until the delimiter +void istrIgnore(str_istream_t *ctx, char delim); +// skip n characters +void istrSkip(str_istream_t *ctx, size_t n); +// read len bytes into buffer, the buffer will not be null terminated +void istrRead(str_istream_t *ctx, char *buf, size_t len); +// read a maximum of len bytes into buffer, the buffer will not be null terminated +// returns the number of bytes read +size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len); +// return to the beginning of the stream +void istrRewind(str_istream_t *ctx); +// return the number of bytes read from beginning of stream +size_t istrTell(str_istream_t *ctx); +// return true if the stream doesn't have any new bytes to read +bool istrIsFinished(str_istream_t *ctx); + +bool istrGetbool(str_istream_t *ctx, bool *val); +bool istrGetu8(str_istream_t *ctx, uint8_t *val); +bool istrGetu16(str_istream_t *ctx, uint16_t *val); +bool istrGetu32(str_istream_t *ctx, uint32_t *val); +bool istrGetu64(str_istream_t *ctx, uint64_t *val); +bool istrGeti8(str_istream_t *ctx, int8_t *val); +bool istrGeti16(str_istream_t *ctx, int16_t *val); +bool istrGeti32(str_istream_t *ctx, int32_t *val); +bool istrGeti64(str_istream_t *ctx, int64_t *val); +bool istrGetfloat(str_istream_t *ctx, float *val); +bool istrGetdouble(str_istream_t *ctx, double *val); +// get a string until a delimiter, the string is allocated by the function and should be freed +size_t istrGetstring(str_istream_t *ctx, char **val, char delim); +// get a string of maximum size len, the string is not allocated by the function and will be null terminated +size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len); +strview_t istrGetview(str_istream_t *ctx, char delim); +strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len); + +/* == OUTPUT STREAM =========================================== */ + +typedef struct { + char *buf; + size_t size; + size_t allocated; +} str_ostream_t; + +str_ostream_t ostrInit(); +str_ostream_t ostrInitLen(size_t initial_alloc); +str_ostream_t ostrInitStr(const char *buf, size_t len); + +void ostrFree(str_ostream_t *ctx); +size_t ostrMove(str_ostream_t *ctx, char **str); + +void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...); + +void ostrPutc(str_ostream_t *ctx, char c); + +void ostrAppendbool(str_ostream_t *ctx, bool val); +void ostrAppendu8(str_ostream_t *ctx, uint8_t val); +void ostrAppendu16(str_ostream_t *ctx, uint16_t val); +void ostrAppendu32(str_ostream_t *ctx, uint32_t val); +void ostrAppendu64(str_ostream_t *ctx, uint64_t val); +void ostrAppendi8(str_ostream_t *ctx, int8_t val); +void ostrAppendi16(str_ostream_t *ctx, int16_t val); +void ostrAppendi32(str_ostream_t *ctx, int32_t val); +void ostrAppendi64(str_ostream_t *ctx, int64_t val); +void ostrAppendfloat(str_ostream_t *ctx, float val); +void ostrAppenddouble(str_ostream_t *ctx, double val); +void ostrAppendview(str_ostream_t *ctx, strview_t view); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/strutils.c b/strutils.c new file mode 100644 index 0000000..f727800 --- /dev/null +++ b/strutils.c @@ -0,0 +1,31 @@ +#include "strutils.h" + +#include +#include +#include + +void strToLower(char *str) { + for(char *beg = str; *beg; ++beg) { + *beg = tolower(*beg); + } +} + +void strnToLower(char *str, size_t len) { + for(size_t i = 0; i < len; ++i) { + str[i] = tolower(str[i]); + } +} + +char *cstrdup(const char *str) { + size_t len = strlen(str); + char *buf = malloc(len + 1); + memcpy(buf, str, len); + buf[len] = '\0'; + return buf; +} + +char *cstrToLower(const char *str) { + char *buf = cstrdup(str); + strToLower(buf); + return buf; +} \ No newline at end of file diff --git a/strutils.h b/strutils.h new file mode 100644 index 0000000..0474775 --- /dev/null +++ b/strutils.h @@ -0,0 +1,31 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#if _WIN32 +#define stricmp _stricmp +#elif __unix__ || __APPLE__ +#define stricmp strcasecmp +#else +#error "stricmp is not supported" +#endif + +// prefix str -> changes string +// prefix cstr -> allocates new string and returns it + +// int str + +void strToLower(char *str); +void strnToLower(char *str, size_t len); + +char *cstrdup(const char *str); +char *cstrToLower(const char *str); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/strview.c b/strview.c new file mode 100644 index 0000000..30430d2 --- /dev/null +++ b/strview.c @@ -0,0 +1,250 @@ +#include "strview.h" + +#include +#include +#include +#include + +#include "tracelog.h" +#include + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + + +strview_t strvInit(const char *cstr) { + return strvInitLen(cstr, strlen(cstr)); +} + +strview_t strvInitLen(const char *buf, size_t size) { + strview_t view; + view.buf = buf; + view.size = size; + return view; +} + +char strvFront(strview_t ctx) { + return ctx.buf[0]; +} + +char strvBack(strview_t ctx) { + return ctx.buf[ctx.size - 1]; +} + +const char *strvBegin(strview_t *ctx) { + return ctx->buf; +} + +const char *strvEnd(strview_t *ctx) { + return ctx->buf + ctx->size; +} + +bool strvIsEmpty(strview_t ctx) { + return ctx.size == 0; +} + +void strvRemovePrefix(strview_t *ctx, size_t n) { + ctx->buf += n; + ctx->size -= n; +} + +void strvRemoveSuffix(strview_t *ctx, size_t n) { + ctx->size -= n; +} + +size_t strvCopy(strview_t ctx, char **buf) { + *buf = malloc(ctx.size + 1); + memcpy(*buf, ctx.buf, ctx.size); + (*buf)[ctx.size] = '\0'; + return ctx.size; +} + +size_t strvCopyN(strview_t ctx, char **buf, size_t count, size_t from) { + size_t sz = ctx.size + 1 - from; + count = min(count, sz); + *buf = malloc(count + 1); + memcpy(*buf, ctx.buf + from, count); + (*buf)[count] = '\0'; + return count; +} + +size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from) { + size_t sz = ctx.size + 1 - from; + len = min(len, sz); + memcpy(buf, ctx.buf + from, len); + buf[len - 1] = '\0'; + return len - 1; +} + +strview_t strvSubstr(strview_t ctx, size_t from, size_t len) { + if(from > ctx.size) from = ctx.size - len; + size_t sz = ctx.size - from; + return strvInitLen(ctx.buf + from, min(len, sz)); +} + +int strvCompare(strview_t ctx, strview_t other) { + if(ctx.size < other.size) return -1; + if(ctx.size > other.size) return 1; + return memcmp(ctx.buf, other.buf, ctx.size); +} + +int strvICompare(strview_t ctx, strview_t other) { + if(ctx.size < other.size) return -1; + if(ctx.size > other.size) return 1; + for(size_t i = 0; i < ctx.size; ++i) { + char a = tolower(ctx.buf[i]); + char b = tolower(other.buf[i]); + if(a != b) return a - b; + } + return 0; +} + +bool strvStartsWith(strview_t ctx, char c) { + return strvFront(ctx) == c; +} + +bool strvStartsWithView(strview_t ctx, strview_t view) { + if(ctx.size < view.size) return false; + return memcmp(ctx.buf, view.buf, view.size) == 0; +} + +bool strvEndsWith(strview_t ctx, char c) { + return strvBack(ctx) == c; +} + +bool strvEndsWithView(strview_t ctx, strview_t view) { + if(ctx.size < view.size) return false; + return memcmp(ctx.buf + ctx.size - view.size, view.buf, view.size) == 0; +} + +bool strvContains(strview_t ctx, char c) { + for(size_t i = 0; i < ctx.size; ++i) { + if(ctx.buf[i] == c) return true; + } + return false; +} + +bool strvContainsView(strview_t ctx, strview_t view) { + if(ctx.size < view.size) return false; + size_t end = ctx.size - view.size; + for(size_t i = 0; i < end; ++i) { + if(memcmp(ctx.buf + i, view.buf, view.size) == 0) return true; + } + return false; +} + +size_t strvFind(strview_t ctx, char c, size_t from) { + for(size_t i = from; i < ctx.size; ++i) { + if(ctx.buf[i] == c) return i; + } + return SIZE_MAX; +} + +size_t strvFindView(strview_t ctx, strview_t view, size_t from) { + if(ctx.size < view.size) return SIZE_MAX; + size_t end = ctx.size - view.size; + for(size_t i = from; i < end; ++i) { + if(memcmp(ctx.buf + i, view.buf, view.size) == 0) return i; + } + return SIZE_MAX; +} + +size_t strvRFind(strview_t ctx, char c, size_t from) { + if(from >= ctx.size) { + from = ctx.size - 1; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + if(*buf == c) return (buf - ctx.buf); + } + + return SIZE_MAX; +} + +size_t strvRFindView(strview_t ctx, strview_t view, size_t from) { + from = min(from, ctx.size); + + if(view.size > ctx.size) { + from -= view.size; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + if(memcmp(buf, view.buf, view.size) == 0) return (buf - ctx.buf); + } + return SIZE_MAX; +} + +size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from) { + if(ctx.size < view.size) return SIZE_MAX; + for(size_t i = from; i < ctx.size; ++i) { + for(size_t j = 0; j < view.size; ++j) { + if(ctx.buf[i] == view.buf[j]) return i; + } + } + return SIZE_MAX; +} + +size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from) { + if(from >= ctx.size) { + from = ctx.size - 1; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + for(size_t j = 0; j < view.size; ++j) { + if(*buf == view.buf[j]) return (buf - ctx.buf); + } + } + + return SIZE_MAX; +} + +size_t strvFindFirstNot(strview_t ctx, char c, size_t from) { + size_t end = ctx.size - 1; + for(size_t i = from; i < end; ++i) { + if(ctx.buf[i] != c) return i; + } + return SIZE_MAX; +} + +size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from) { + for(size_t i = from; i < ctx.size; ++i) { + if(!strvContains(view, ctx.buf[i])) { + return i; + } + } + return SIZE_MAX; +} + +size_t strvFindLastNot(strview_t ctx, char c, size_t from) { + if(from >= ctx.size) { + from = ctx.size - 1; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + if(*buf != c) { + return buf - ctx.buf; + } + } + + return SIZE_MAX; +} + +size_t strvFindLastNotOf(strview_t ctx, strview_t view, size_t from) { + if(from >= ctx.size) { + from = ctx.size - 1; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + if(!strvContains(view, *buf)) { + return buf - ctx.buf; + } + } + + return SIZE_MAX; +} diff --git a/strview.h b/strview.h new file mode 100644 index 0000000..8e25fe1 --- /dev/null +++ b/strview.h @@ -0,0 +1,67 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#define STRV_NOT_FOUND SIZE_MAX + +typedef struct { + const char *buf; + size_t size; +} strview_t; + +strview_t strvInit(const char *cstr); +strview_t strvInitLen(const char *buf, size_t size); + +char strvFront(strview_t ctx); +char strvBack(strview_t ctx); +const char *strvBegin(strview_t *ctx); +const char *strvEnd(strview_t *ctx); +// move view forward by n characters +void strvRemovePrefix(strview_t *ctx, size_t n); +// move view backwards by n characters +void strvRemoveSuffix(strview_t *ctx, size_t n); + +bool strvIsEmpty(strview_t ctx); + +size_t strvCopy(strview_t ctx, char **buf); +size_t strvCopyN(strview_t ctx, char **buf, size_t count, size_t from); +size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from); + +strview_t strvSubstr(strview_t ctx, size_t from, size_t len); +int strvCompare(strview_t ctx, strview_t other); +int strvICompare(strview_t ctx, strview_t other); + +bool strvStartsWith(strview_t ctx, char c); +bool strvStartsWithView(strview_t ctx, strview_t view); + +bool strvEndsWith(strview_t ctx, char c); +bool strvEndsWithView(strview_t ctx, strview_t view); + +bool strvContains(strview_t ctx, char c); +bool strvContainsView(strview_t ctx, strview_t view); + +size_t strvFind(strview_t ctx, char c, size_t from); +size_t strvFindView(strview_t ctx, strview_t view, size_t from); + +size_t strvRFind(strview_t ctx, char c, size_t from); +size_t strvRFindView(strview_t ctx, strview_t view, size_t from); + +// Finds the first occurrence of any of the characters of 'view' in this view +size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from); +size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from); + +size_t strvFindFirstNot(strview_t ctx, char c, size_t from); +size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from); +size_t strvFindLastNot(strview_t ctx, char c, size_t from); +size_t strvFindLastNotOf(strview_t ctx, strview_t view, size_t from); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/tracelog.c b/tracelog.c new file mode 100644 index 0000000..c55bb3c --- /dev/null +++ b/tracelog.c @@ -0,0 +1,75 @@ +#include "tracelog.h" + +#include +#include +#include +#include + +#ifdef TLOG_VS + #ifdef _WIN32 + #ifndef TLOG_NO_COLOURS + #define TLOG_NO_COLOURS + #endif + + #define VC_EXTRALEAN + #include + #else + #error "can't use TLOG_VS if not on windows" + #endif +#endif + +#ifdef TLOG_NO_COLOURS + #define BLACK "" + #define RED "" + #define GREEN "" + #define YELLOW "" + #define BLUE "" + #define MAGENTA "" + #define CYAN "" + #define WHITE "" + #define RESET "" + #define BOLD "" +#else + #define BLACK "\x1B[30m" + #define RED "\x1B[31m" + #define GREEN "\x1B[32m" + #define YELLOW "\x1B[33m" + #define BLUE "\x1B[34m" + #define MAGENTA "\x1B[35m" + #define CYAN "\x1B[36m" + #define WHITE "\x1B[37m" + #define RESET "\x1B[0m" + #define BOLD "\x1B[1m" +#endif + +#define MAX_TRACELOG_MSG_LENGTH 1024 + +void traceLog(LogLevel level, const char *fmt, ...) { + char buffer[MAX_TRACELOG_MSG_LENGTH]; + + switch (level) { + case LogTrace: strcpy(buffer, BOLD WHITE "[TRACE]: " RESET); break; + case LogDebug: strcpy(buffer, BOLD BLUE "[DEBUG]: " RESET); break; + case LogInfo: strcpy(buffer, BOLD GREEN "[INFO]: " RESET); break; + case LogWarning: strcpy(buffer, BOLD YELLOW "[WARNING]: " RESET); break; + case LogError: strcpy(buffer, BOLD RED "[ERROR]: " RESET); break; + case LogFatal: strcpy(buffer, BOLD RED "[FATAL]: " RESET); break; + default: break; + } + + va_list args; + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + +#ifdef TLOG_VS + OutputDebugStringA(buffer); + OutputDebugStringA("\n"); +#else + puts(buffer); +#endif + +#ifndef TLOG_DONT_EXIT_ON_FATAL + if (level == LogFatal) exit(1); +#endif +} \ No newline at end of file diff --git a/tracelog.h b/tracelog.h new file mode 100644 index 0000000..7549c81 --- /dev/null +++ b/tracelog.h @@ -0,0 +1,28 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Define any of this to turn on the option + * -> TLOG_NO_COLOURS: print without using colours + * -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS + * -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal +*/ + +typedef enum { + LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal +} LogLevel; + +void traceLog(LogLevel level, const char *fmt, ...); + +#define trace(...) traceLog(LogTrace, __VA_ARGS__) +#define debug(...) traceLog(LogDebug, __VA_ARGS__) +#define info(...) traceLog(LogInfo, __VA_ARGS__) +#define warn(...) traceLog(LogWarning, __VA_ARGS__) +#define err(...) traceLog(LogError, __VA_ARGS__) +#define fatal(...) traceLog(LogFatal, __VA_ARGS__) + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file