first commit
This commit is contained in:
commit
efd4d9c750
13 changed files with 1950 additions and 0 deletions
14
CMakeLists.txt
Normal file
14
CMakeLists.txt
Normal file
|
|
@ -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()
|
||||||
359
http.c
Normal file
359
http.c
Normal file
|
|
@ -0,0 +1,359 @@
|
||||||
|
#include "http.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
128
http.h
Normal file
128
http.h
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#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
|
||||||
277
socket.c
Normal file
277
socket.c
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#undef INVALID_SOCKET
|
||||||
|
#undef SOCKET_ERROR
|
||||||
|
|
||||||
|
#if SOCK_WINDOWS
|
||||||
|
#include <winsock2.h>
|
||||||
|
|
||||||
|
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 <sys/socket.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h> // 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
|
||||||
82
socket.h
Normal file
82
socket.h
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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
|
||||||
511
strstream.c
Normal file
511
strstream.c
Normal file
|
|
@ -0,0 +1,511 @@
|
||||||
|
#include "strstream.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h> // 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';
|
||||||
|
}
|
||||||
97
strstream.h
Normal file
97
strstream.h
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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
|
||||||
31
strutils.c
Normal file
31
strutils.c
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include "strutils.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
31
strutils.h
Normal file
31
strutils.h
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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
|
||||||
250
strview.c
Normal file
250
strview.c
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
#include "strview.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "tracelog.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
67
strview.h
Normal file
67
strview.h
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#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
|
||||||
75
tracelog.c
Normal file
75
tracelog.c
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
#include "tracelog.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifdef TLOG_VS
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifndef TLOG_NO_COLOURS
|
||||||
|
#define TLOG_NO_COLOURS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define VC_EXTRALEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#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
|
||||||
|
}
|
||||||
28
tracelog.h
Normal file
28
tracelog.h
Normal file
|
|
@ -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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue