commit efd4d9c7502779bdaf4e8404f912c2078e961b04 Author: snarmph <1902881@uad.ac.uk> Date: Thu Sep 30 17:42:28 2021 +0200 first commit 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