added:
dir -> directory walker similar to dirent
dirwatch -> lets you watch a directory for changes in another thread
vec -> generic vector
This commit is contained in:
parent
aa08240ec9
commit
59b55c7f6c
26 changed files with 4037 additions and 2726 deletions
|
|
@ -1,18 +1,22 @@
|
|||
add_library(Colla STATIC
|
||||
socket.c
|
||||
tracelog.c
|
||||
http.c
|
||||
strstream.c
|
||||
strview.c
|
||||
str.c
|
||||
coroutine.c
|
||||
os.c
|
||||
fs.c
|
||||
file.c
|
||||
)
|
||||
|
||||
IF (WIN32)
|
||||
target_link_libraries(Colla ws2_32.lib)
|
||||
ELSE()
|
||||
# posix
|
||||
ENDIF()
|
||||
add_library(Colla STATIC
|
||||
socket.c
|
||||
tracelog.c
|
||||
http.c
|
||||
strstream.c
|
||||
str.c
|
||||
coroutine.c
|
||||
os.c
|
||||
fs.c
|
||||
file.c
|
||||
dir.c
|
||||
dirwatch.c
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_link_libraries(Colla ws2_32.lib)
|
||||
target_compile_options(Colla PRIVATE /W4)
|
||||
else()
|
||||
target_link_libraries(Colla pthread)
|
||||
target_compile_options(Colla PRIVATE -Wall -Wextra -Wpedantic)
|
||||
target_compile_definitions(Colla PUBLIC _DEFAULT_SOURCE)
|
||||
endif()
|
||||
20
coroutine.c
20
coroutine.c
|
|
@ -1,11 +1,11 @@
|
|||
#include "coroutine.h"
|
||||
|
||||
coroutine_t coInit() {
|
||||
return (coroutine_t) {
|
||||
.state = 0
|
||||
};
|
||||
}
|
||||
|
||||
bool coIsDead(coroutine_t co) {
|
||||
return co.state == -1;
|
||||
#include "coroutine.h"
|
||||
|
||||
coroutine_t coInit() {
|
||||
return (coroutine_t) {
|
||||
.state = 0
|
||||
};
|
||||
}
|
||||
|
||||
bool coIsDead(coroutine_t co) {
|
||||
return co.state == -1;
|
||||
}
|
||||
264
coroutine.h
264
coroutine.h
|
|
@ -1,133 +1,133 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h> // bool
|
||||
#include <string.h> // memset
|
||||
#include "tracelog.h" // fatal
|
||||
|
||||
// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973
|
||||
|
||||
#if 0
|
||||
// Usage example:
|
||||
typedef struct {
|
||||
int result;
|
||||
coroutine_t co;
|
||||
} co_int_t;
|
||||
|
||||
bool coVoid(co_void_t *co) {
|
||||
costate(co_nostate);
|
||||
coroutine({
|
||||
printf("hello"); yield();
|
||||
printf("world"); yield();
|
||||
printf("how"); yield();
|
||||
printf("is"); yield();
|
||||
printf("it"); yield();
|
||||
printf("going?\n");
|
||||
});
|
||||
}
|
||||
|
||||
bool countToTen(co_int_t *co) {
|
||||
costate(int i; int ii;);
|
||||
coroutine({
|
||||
for(self.i = 0; self.i < 10; ++self.i) {
|
||||
self.ii += self.i;
|
||||
yieldVal(self.ii);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int main() {
|
||||
co_void_t covoid = {0};
|
||||
while(coVoid(&covoid)) {
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
co_int_t coint;
|
||||
coint.co = coInit();
|
||||
while(countToTen(&coint)) {
|
||||
printf("%d ", coint.result);
|
||||
}
|
||||
printf("\n");
|
||||
// reset coroutine for next call
|
||||
coint.co = coInit();
|
||||
while(countToTen(&coint)) {
|
||||
printf("%d ", coint.result);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int state;
|
||||
} coroutine_t;
|
||||
|
||||
typedef struct {
|
||||
coroutine_t co;
|
||||
} co_void_t;
|
||||
|
||||
coroutine_t coInit();
|
||||
bool coIsDead(coroutine_t co);
|
||||
|
||||
#define COVAR co
|
||||
#define COSELF self
|
||||
|
||||
#define costate(...) \
|
||||
typedef struct { bool init; __VA_ARGS__ } COSTATE; \
|
||||
static COSTATE self = {0}
|
||||
|
||||
#define co_nostate { char __dummy__; }
|
||||
|
||||
#define yieldBreak() \
|
||||
COVAR->co.state = -1; \
|
||||
self.init = false; \
|
||||
return false;
|
||||
|
||||
#define coroutine(...) \
|
||||
if(!self.init) { \
|
||||
if(COVAR->co.state != 0) { \
|
||||
fatal("coroutine not initialized in %s:%d,\n" \
|
||||
"did you forget to do '= {0};'?", \
|
||||
__FILE__, __LINE__); \
|
||||
} \
|
||||
memset(&self, 0, sizeof(self)); \
|
||||
self.init = true; \
|
||||
} \
|
||||
switch(COVAR->co.state) { \
|
||||
case 0:; \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
yieldBreak();
|
||||
|
||||
#define yield() _yield(__COUNTER__)
|
||||
#define _yield(count) \
|
||||
do { \
|
||||
COVAR->co.state = count; \
|
||||
return true; \
|
||||
case count:; \
|
||||
} while(0);
|
||||
|
||||
#define yieldVal(v) _yieldVal(v, __COUNTER__)
|
||||
#define _yieldVal(v, count) \
|
||||
do { \
|
||||
COVAR->co.state = count; \
|
||||
COVAR->result = v; \
|
||||
return true; \
|
||||
case count:; \
|
||||
} while(0);
|
||||
|
||||
// increment __COUNTER__ past 0 so we don't get double case errors
|
||||
#define CONCAT_IMPL(x, y) x##y
|
||||
#define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y)
|
||||
#define __INC_COUNTER int MACRO_CONCAT(__counter_tmp_, __COUNTER__)
|
||||
__INC_COUNTER;
|
||||
|
||||
#undef CONCAT_IMPL
|
||||
#undef MACRO_CONCAT
|
||||
#undef __INC_COUNTER
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h> // bool
|
||||
#include <string.h> // memset
|
||||
#include "tracelog.h" // fatal
|
||||
|
||||
// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973
|
||||
|
||||
#if 0
|
||||
// Usage example:
|
||||
typedef struct {
|
||||
int result;
|
||||
coroutine_t co;
|
||||
} co_int_t;
|
||||
|
||||
bool coVoid(co_void_t *co) {
|
||||
costate(co_nostate);
|
||||
coroutine({
|
||||
printf("hello"); yield();
|
||||
printf("world"); yield();
|
||||
printf("how"); yield();
|
||||
printf("is"); yield();
|
||||
printf("it"); yield();
|
||||
printf("going?\n");
|
||||
});
|
||||
}
|
||||
|
||||
bool countToTen(co_int_t *co) {
|
||||
costate(int i; int ii;);
|
||||
coroutine({
|
||||
for(self.i = 0; self.i < 10; ++self.i) {
|
||||
self.ii += self.i;
|
||||
yieldVal(self.ii);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int main() {
|
||||
co_void_t covoid = {0};
|
||||
while(coVoid(&covoid)) {
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
co_int_t coint;
|
||||
coint.co = coInit();
|
||||
while(countToTen(&coint)) {
|
||||
printf("%d ", coint.result);
|
||||
}
|
||||
printf("\n");
|
||||
// reset coroutine for next call
|
||||
coint.co = coInit();
|
||||
while(countToTen(&coint)) {
|
||||
printf("%d ", coint.result);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int state;
|
||||
} coroutine_t;
|
||||
|
||||
typedef struct {
|
||||
coroutine_t co;
|
||||
} co_void_t;
|
||||
|
||||
coroutine_t coInit();
|
||||
bool coIsDead(coroutine_t co);
|
||||
|
||||
#define COVAR co
|
||||
#define COSELF self
|
||||
|
||||
#define costate(...) \
|
||||
typedef struct { bool init; __VA_ARGS__ } COSTATE; \
|
||||
static COSTATE self = {0}
|
||||
|
||||
#define co_nostate { char __dummy__; }
|
||||
|
||||
#define yieldBreak() \
|
||||
COVAR->co.state = -1; \
|
||||
self.init = false; \
|
||||
return false;
|
||||
|
||||
#define coroutine(...) \
|
||||
if(!self.init) { \
|
||||
if(COVAR->co.state != 0) { \
|
||||
fatal("coroutine not initialized in %s:%d,\n" \
|
||||
"did you forget to do '= {0};'?", \
|
||||
__FILE__, __LINE__); \
|
||||
} \
|
||||
memset(&self, 0, sizeof(self)); \
|
||||
self.init = true; \
|
||||
} \
|
||||
switch(COVAR->co.state) { \
|
||||
case 0:; \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
yieldBreak();
|
||||
|
||||
#define yield() _yield(__COUNTER__)
|
||||
#define _yield(count) \
|
||||
do { \
|
||||
COVAR->co.state = count; \
|
||||
return true; \
|
||||
case count:; \
|
||||
} while(0);
|
||||
|
||||
#define yieldVal(v) _yieldVal(v, __COUNTER__)
|
||||
#define _yieldVal(v, count) \
|
||||
do { \
|
||||
COVAR->co.state = count; \
|
||||
COVAR->result = v; \
|
||||
return true; \
|
||||
case count:; \
|
||||
} while(0);
|
||||
|
||||
// increment __COUNTER__ past 0 so we don't get double case errors
|
||||
#define CONCAT_IMPL(x, y) x##y
|
||||
#define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y)
|
||||
#define __INC_COUNTER int MACRO_CONCAT(__counter_tmp_, __COUNTER__)
|
||||
__INC_COUNTER;
|
||||
|
||||
#undef CONCAT_IMPL
|
||||
#undef MACRO_CONCAT
|
||||
#undef __INC_COUNTER
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
147
dir.c
Normal file
147
dir.c
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#include "dir.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VC_EXTRALEAN
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "strstream.h"
|
||||
|
||||
typedef struct {
|
||||
dir_entry_t cur;
|
||||
dir_entry_t next;
|
||||
HANDLE handle;
|
||||
} _dir_internal_t;
|
||||
|
||||
static dir_entry_t _fillDirEntry(WIN32_FIND_DATAW *data) {
|
||||
return (dir_entry_t) {
|
||||
.type =
|
||||
data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ?
|
||||
FS_TYPE_DIR : FS_TYPE_FILE,
|
||||
.name = strFromWCHAR(data->cFileName, 0)
|
||||
};
|
||||
}
|
||||
|
||||
static _dir_internal_t _getFirst(const wchar_t *path) {
|
||||
_dir_internal_t res = {0};
|
||||
WIN32_FIND_DATAW data = {0};
|
||||
|
||||
res.handle = FindFirstFileW(path, &data);
|
||||
|
||||
if(res.handle != INVALID_HANDLE_VALUE) {
|
||||
res.next = _fillDirEntry(&data);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void _getNext(_dir_internal_t *ctx) {
|
||||
WIN32_FIND_DATAW data = {0};
|
||||
|
||||
BOOL result = FindNextFileW(ctx->handle, &data);
|
||||
if(!result) {
|
||||
if(GetLastError() == ERROR_NO_MORE_FILES) {
|
||||
FindClose(ctx->handle);
|
||||
ctx->handle = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ctx->next = _fillDirEntry(&data);
|
||||
}
|
||||
|
||||
dir_t dirOpen(const char *path) {
|
||||
DWORD n = GetFullPathName(path, 0, NULL, NULL);
|
||||
str_ostream_t out = ostrInitLen(n + 3);
|
||||
n = GetFullPathName(path, n, out.buf, NULL);
|
||||
assert(n > 0);
|
||||
out.size += n;
|
||||
switch(ostrBack(&out)) {
|
||||
case '\\':
|
||||
case '/':
|
||||
case ':':
|
||||
// directory ends in path separator
|
||||
// NOP
|
||||
break;
|
||||
default:
|
||||
ostrPutc(&out, '\\');
|
||||
}
|
||||
ostrPutc(&out, '*');
|
||||
|
||||
_dir_internal_t *dir = malloc(sizeof(_dir_internal_t));
|
||||
if(dir) {
|
||||
wchar_t *wpath = strToWCHAR(ostrAsStr(&out));
|
||||
assert(wpath);
|
||||
*dir = _getFirst(wpath);
|
||||
free(wpath);
|
||||
}
|
||||
ostrFree(&out);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
dir_entry_t *dirNext(dir_t ctx) {
|
||||
_dir_internal_t *dir = (_dir_internal_t*)ctx;
|
||||
strFree(&dir->cur.name);
|
||||
if(!dir->handle) return NULL;
|
||||
dir->cur = dir->next;
|
||||
_getNext(dir);
|
||||
return &dir->cur;
|
||||
}
|
||||
|
||||
void dirClose(dir_t ctx) {
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html
|
||||
// hopefully shouldn't be needed
|
||||
#ifndef DT_DIR
|
||||
#define DT_DIR 4
|
||||
#endif
|
||||
#ifndef DT_REG
|
||||
#define DT_REG 8
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
DIR *dir;
|
||||
dir_entry_t next;
|
||||
} _dir_internal_t;
|
||||
|
||||
dir_t dirOpen(const char *path) {
|
||||
_dir_internal_t *ctx = calloc(1, sizeof(_dir_internal_t));
|
||||
if(ctx) ctx->dir = opendir(path);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void dirClose(dir_t ctx) {
|
||||
if(ctx) {
|
||||
_dir_internal_t *in = (_dir_internal_t *)ctx;
|
||||
closedir(in->dir);
|
||||
free(in);
|
||||
}
|
||||
}
|
||||
|
||||
dir_entry_t *dirNext(dir_t ctx) {
|
||||
if(!ctx) return NULL;
|
||||
_dir_internal_t *in = (_dir_internal_t *)ctx;
|
||||
strFree(&in->next.name);
|
||||
struct dirent *dp = readdir(in->dir);
|
||||
if(!dp) return NULL;
|
||||
|
||||
switch(dp->d_type) {
|
||||
case DT_DIR: in->next.type = FS_TYPE_DIR; break;
|
||||
case DT_REG: in->next.type = FS_TYPE_FILE; break;
|
||||
default: in->next.type = FS_TYPE_UNKNOWN; break;
|
||||
}
|
||||
|
||||
in->next.name = strInitStr(dp->d_name);
|
||||
return &in->next;
|
||||
}
|
||||
|
||||
#endif
|
||||
29
dir.h
Normal file
29
dir.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "str.h"
|
||||
|
||||
typedef void *dir_t;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
str_t name;
|
||||
} dir_entry_t;
|
||||
|
||||
enum {
|
||||
FS_TYPE_UNKNOWN,
|
||||
FS_TYPE_FILE,
|
||||
FS_TYPE_DIR,
|
||||
};
|
||||
|
||||
dir_t dirOpen(const char *path);
|
||||
void dirClose(dir_t ctx);
|
||||
|
||||
dir_entry_t *dirNext(dir_t ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
300
dirwatch.c
Normal file
300
dirwatch.c
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
#include "dirwatch.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VC_EXTRALEAN
|
||||
#include <windows.h>
|
||||
#include "str.h"
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
dirwatch_cb_t on_event;
|
||||
BOOL should_continue;
|
||||
HANDLE stop_event;
|
||||
} __dirwatch_internal_t;
|
||||
|
||||
static DWORD watchDirThread(void *cdata) {
|
||||
__dirwatch_internal_t *desc = (__dirwatch_internal_t*)cdata;
|
||||
|
||||
// stop_event is called from another thread when watchDirThread should exit
|
||||
desc->stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
|
||||
HANDLE file = CreateFile(
|
||||
desc->path,
|
||||
FILE_LIST_DIRECTORY,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
||||
NULL
|
||||
);
|
||||
assert(file != INVALID_HANDLE_VALUE);
|
||||
OVERLAPPED overlapped = {0};
|
||||
overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL);
|
||||
|
||||
uint8_t change_buf[1024];
|
||||
BOOL success = ReadDirectoryChangesW(
|
||||
file, change_buf, sizeof(change_buf), TRUE,
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME |
|
||||
FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
FILE_NOTIFY_CHANGE_LAST_WRITE,
|
||||
NULL, &overlapped, NULL
|
||||
);
|
||||
assert(success);
|
||||
|
||||
HANDLE events[2] = {
|
||||
overlapped.hEvent,
|
||||
desc->stop_event
|
||||
};
|
||||
|
||||
WCHAR old_name[MAX_PATH];
|
||||
dirwatch_file_t data = {0};
|
||||
|
||||
// if the variable is 32bit aligned, a read/write is already atomice
|
||||
// https://docs.microsoft.com/en-us/windows/win32/sync/interlocked-variable-access
|
||||
while(desc->should_continue) {
|
||||
DWORD result = WaitForMultipleObjects(2, events, FALSE, INFINITE);
|
||||
|
||||
if(result == WAIT_OBJECT_0) {
|
||||
DWORD bytes_transferred;
|
||||
GetOverlappedResult(file, &overlapped, &bytes_transferred, FALSE);
|
||||
|
||||
FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION*)change_buf;
|
||||
|
||||
for(;;) {
|
||||
DWORD name_len = event->FileNameLength / sizeof(wchar_t);
|
||||
|
||||
switch(event->Action) {
|
||||
case FILE_ACTION_ADDED:
|
||||
data.name = strFromWCHAR(event->FileName, name_len).buf;
|
||||
data.oldname = NULL;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_ADDED, data);
|
||||
break;
|
||||
case FILE_ACTION_REMOVED:
|
||||
data.name = strFromWCHAR(event->FileName, name_len).buf;
|
||||
data.oldname = NULL;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_REMOVED, data);
|
||||
break;
|
||||
case FILE_ACTION_RENAMED_OLD_NAME:
|
||||
memcpy(old_name, event->FileName, event->FileNameLength);
|
||||
old_name[event->FileNameLength] = '\0';
|
||||
break;
|
||||
case FILE_ACTION_RENAMED_NEW_NAME:
|
||||
data.name = strFromWCHAR(event->FileName, name_len).buf;
|
||||
data.oldname = strFromWCHAR(old_name, 0).buf;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_RENAMED, data);
|
||||
break;
|
||||
}
|
||||
|
||||
if(data.name) free(data.name);
|
||||
if(data.oldname) free(data.oldname);
|
||||
|
||||
data = (dirwatch_file_t){0};
|
||||
|
||||
if(event->NextEntryOffset) {
|
||||
*((uint8_t**)&event) += event->NextEntryOffset;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
success = ReadDirectoryChangesW(
|
||||
file, change_buf, sizeof(change_buf), TRUE,
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME |
|
||||
FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
FILE_NOTIFY_CHANGE_LAST_WRITE,
|
||||
NULL, &overlapped, NULL
|
||||
);
|
||||
assert(success);
|
||||
}
|
||||
// stop_event
|
||||
else if(result == WAIT_OBJECT_0 + 1) {
|
||||
warn("stopping");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
dirwatch_t watchDir(dirwatch_desc_t desc) {
|
||||
dirwatch_t dir = {0};
|
||||
|
||||
__dirwatch_internal_t *opts = HeapAlloc(
|
||||
GetProcessHeap(),
|
||||
0,
|
||||
sizeof(__dirwatch_internal_t)
|
||||
);
|
||||
|
||||
opts->path = desc.path;
|
||||
opts->on_event = desc.on_event;
|
||||
opts->should_continue = true;
|
||||
|
||||
dir.desc = (dirwatch_desc_t *)opts;
|
||||
|
||||
dir.handle = CreateThread(
|
||||
NULL,
|
||||
0,
|
||||
watchDirThread,
|
||||
(void *)dir.desc,
|
||||
0,
|
||||
NULL
|
||||
);
|
||||
|
||||
if(dir.handle) {
|
||||
info("watching %s", desc.path);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
void waitForWatchDir(dirwatch_t *ctx) {
|
||||
if(!ctx->handle) return;
|
||||
|
||||
WaitForSingleObject((HANDLE)ctx->handle, INFINITE);
|
||||
|
||||
HeapFree(GetProcessHeap(), 0, ctx->desc);
|
||||
}
|
||||
|
||||
void stopWatchDir(dirwatch_t *ctx, bool immediately) {
|
||||
(void)immediately;
|
||||
__dirwatch_internal_t *opts = (__dirwatch_internal_t *)ctx->desc;
|
||||
opts->should_continue = false;
|
||||
if(immediately) {
|
||||
if(!SetEvent(opts->stop_event)) {
|
||||
err("couldn't signal event stop_event: %d", GetLastError());
|
||||
}
|
||||
}
|
||||
WaitForSingleObject((HANDLE)ctx->handle, INFINITE);
|
||||
|
||||
HeapFree(GetProcessHeap(), 0, ctx->desc);
|
||||
}
|
||||
|
||||
#else
|
||||
#include <sys/inotify.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // malloc
|
||||
#include <unistd.h> // read
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <linux/limits.h> // MAX_PATH
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define EVENT_SIZE (sizeof(struct inotify_event))
|
||||
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
|
||||
|
||||
#define ERROR(str) { err(str ": %s", strerror(errno)); goto error; }
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
dirwatch_cb_t on_event;
|
||||
atomic_bool should_continue;
|
||||
int fd;
|
||||
int wd;
|
||||
} __dirwatch_internal_t;
|
||||
|
||||
static void *watchDirThread(void *cdata) {
|
||||
__dirwatch_internal_t *desc = (__dirwatch_internal_t *)cdata;
|
||||
info("watching %s", desc->path);
|
||||
|
||||
int length/*, fd, wd*/;
|
||||
char buffer[BUF_LEN];
|
||||
|
||||
desc->fd = inotify_init();
|
||||
|
||||
if(desc->fd < 0) {
|
||||
ERROR("inotify_init failed");
|
||||
}
|
||||
|
||||
desc->wd = inotify_add_watch(desc->fd, desc->path, IN_MOVE | IN_CREATE | IN_DELETE);
|
||||
|
||||
char old_path[PATH_MAX] = {0};
|
||||
dirwatch_file_t data = {0};
|
||||
|
||||
while(desc->should_continue) {
|
||||
length = (int)read(desc->fd, buffer, BUF_LEN);
|
||||
|
||||
if(length < 0) {
|
||||
ERROR("couldn't read");
|
||||
}
|
||||
|
||||
for(int i = 0; i < length;) {
|
||||
struct inotify_event *event = (struct inotify_event *) &buffer[i];
|
||||
if(event->len) {
|
||||
uint32_t e = event->mask;
|
||||
// bool is_dir = e & IN_ISDIR;
|
||||
|
||||
if(e & IN_CREATE) {
|
||||
data.name = event->name;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_ADDED, data);
|
||||
}
|
||||
else if(e & IN_DELETE) {
|
||||
data.name = event->name;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_REMOVED, data);
|
||||
}
|
||||
else if(e & IN_MOVED_FROM) {
|
||||
memcpy(old_path, event->name, event->len);
|
||||
old_path[event->len] = '\0';
|
||||
}
|
||||
else if(e & IN_MOVED_TO) {
|
||||
data.oldname = old_path;
|
||||
data.name = event->name;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_RENAMED, data);
|
||||
}
|
||||
}
|
||||
|
||||
i += EVENT_SIZE + event->len;
|
||||
}
|
||||
}
|
||||
|
||||
inotify_rm_watch(desc->fd, desc->wd);
|
||||
close(desc->fd);
|
||||
|
||||
return (void*)0;
|
||||
error:
|
||||
return (void*)1;
|
||||
}
|
||||
|
||||
dirwatch_t watchDir(dirwatch_desc_t desc) {
|
||||
dirwatch_t dir = {0};
|
||||
|
||||
__dirwatch_internal_t *opts = malloc(sizeof(__dirwatch_internal_t));
|
||||
opts->path = desc.path;
|
||||
opts->on_event = desc.on_event;
|
||||
opts->fd = 0;
|
||||
opts->wd = 0;
|
||||
opts->should_continue = true;
|
||||
|
||||
dir.desc = (dirwatch_desc_t *)opts;
|
||||
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, NULL, watchDirThread, opts);
|
||||
|
||||
dir.handle = (void *)thread;
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
void waitForWatchDir(dirwatch_t *ctx) {
|
||||
pthread_join((pthread_t)ctx->handle, NULL);
|
||||
free(ctx->desc);
|
||||
}
|
||||
|
||||
void stopWatchDir(dirwatch_t *ctx, bool immediately) {
|
||||
__dirwatch_internal_t *opts = (__dirwatch_internal_t *)ctx->desc;
|
||||
opts->should_continue = false;
|
||||
// this one might get called in the thread first, but it doesn't matter
|
||||
if(immediately) {
|
||||
inotify_rm_watch(opts->fd, opts->wd);
|
||||
close(opts->fd);
|
||||
}
|
||||
pthread_join((pthread_t)ctx->handle, NULL);
|
||||
free(opts);
|
||||
}
|
||||
|
||||
#endif
|
||||
62
dirwatch.h
Normal file
62
dirwatch.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Basic usage:
|
||||
*
|
||||
* // this function will be called from another thread every time
|
||||
* // something happens to 'path'
|
||||
* void onEvent(const char *path, int action, dirwatch_file_t file) {
|
||||
* switch(action) {
|
||||
* case DIRWATCH_FILE_ADDED: [do something] break;
|
||||
* case DIRWATCH_FILE_REMOVED: [do something] break;
|
||||
* case DIRWATCH_FILE_RENAMED: [do something] break;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* int main() {
|
||||
* dirwatch_t dir = watchDir((dirwatch_desc_t){
|
||||
* .dirname = "watch/",
|
||||
* .on_event = onEvent
|
||||
* });
|
||||
*
|
||||
* waitForWatchDir(&dir);
|
||||
* }
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
enum {
|
||||
DIRWATCH_FILE_ADDED,
|
||||
DIRWATCH_FILE_REMOVED,
|
||||
DIRWATCH_FILE_RENAMED,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *oldname;
|
||||
} dirwatch_file_t;
|
||||
|
||||
typedef void (*dirwatch_cb_t)(const char *path, int action, dirwatch_file_t data);
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
dirwatch_cb_t on_event;
|
||||
} dirwatch_desc_t;
|
||||
|
||||
typedef struct {
|
||||
void *handle;
|
||||
dirwatch_desc_t *desc;
|
||||
} dirwatch_t;
|
||||
|
||||
// Creates a thread and starts watching the folder specified by desc
|
||||
// if any action happend on_event will be called from this thread
|
||||
dirwatch_t watchDir(dirwatch_desc_t desc);
|
||||
// waits forever
|
||||
void waitForWatchDir(dirwatch_t *ctx);
|
||||
// stops dirwatch thread, if immediately is true, it will try to close it right away
|
||||
// otherwise it might wait for one last event
|
||||
void stopWatchDir(dirwatch_t *ctx, bool immediately);
|
||||
11
file.h
11
file.h
|
|
@ -1,8 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include "str.h"
|
||||
#include "strview.h"
|
||||
|
||||
enum {
|
||||
FILE_READ, FILE_WRITE, FILE_BOTH
|
||||
|
|
@ -28,4 +31,8 @@ size_t fileWrite(file_t *ctx, const void *buf, size_t len);
|
|||
bool fileSeekEnd(file_t *ctx);
|
||||
void fileRewind(file_t *ctx);
|
||||
|
||||
uint64_t fileTell(file_t *ctx);
|
||||
uint64_t fileTell(file_t *ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
254
fs.c
254
fs.c
|
|
@ -1,121 +1,135 @@
|
|||
#include "fs.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VC_EXTRALEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int _modeToType(unsigned int mode) {
|
||||
switch(mode & _S_IFMT) {
|
||||
case _S_IFDIR: return FS_MODE_DIR;
|
||||
case _S_IFCHR: return FS_MODE_CHARACTER_DEVICE;
|
||||
case _S_IFREG: return FS_MODE_FILE;
|
||||
case _S_IFIFO: return FS_MODE_FIFO;
|
||||
default: return FS_MODE_UKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
fs_stat_t fsStat(file_t fp) {
|
||||
TCHAR path[MAX_PATH];
|
||||
DWORD pathlen = GetFinalPathNameByHandle(
|
||||
(HANDLE)fp.handle,
|
||||
path,
|
||||
MAX_PATH,
|
||||
0);
|
||||
|
||||
struct stat statbuf;
|
||||
int res = stat(path, &statbuf);
|
||||
if(res == 0) {
|
||||
return (fs_stat_t) {
|
||||
.type = _modeToType(statbuf.st_mode),
|
||||
.size = statbuf.st_size,
|
||||
.last_access = statbuf.st_atime,
|
||||
.last_modif = statbuf.st_mtime
|
||||
};
|
||||
}
|
||||
else {
|
||||
return (fs_stat_t) { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
fs_time_t fsAsTime(int64_t timer) {
|
||||
struct tm t;
|
||||
errno_t error = localtime_s(&t, &timer);
|
||||
|
||||
if(error == 0) {
|
||||
return (fs_time_t) {
|
||||
.year = t.tm_year + 1900,
|
||||
.month = t.tm_mon + 1,
|
||||
.day = t.tm_mday,
|
||||
.hour = t.tm_hour,
|
||||
.minutes = t.tm_min,
|
||||
.seconds = t.tm_sec,
|
||||
.daylight_saving = t.tm_isdst > 0
|
||||
};
|
||||
}
|
||||
else {
|
||||
char buf[128];
|
||||
strerror_s(buf, sizeof(buf), error);
|
||||
err("%s", buf);
|
||||
return (fs_time_t) { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int _modeToType(unsigned int mode) {
|
||||
switch(mode & __S_IFMT) {
|
||||
case __S_IFDIR: return FS_MODE_DIR;
|
||||
case __S_IFCHR: return FS_MODE_CHARACTER_DEVICE;
|
||||
case __S_IFREG: return FS_MODE_FILE;
|
||||
case __S_IFIFO: return FS_MODE_FIFO;
|
||||
default: return FS_MODE_UKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
fs_stat_t fsStat(file_t fp) {
|
||||
int fd = fileno((FILE*)fp.handle);
|
||||
struct stat statbuf;
|
||||
int res = fstat(fd, &statbuf);
|
||||
if(res == 0) {
|
||||
return (fs_stat_t) {
|
||||
.type = _modeToType(statbuf.st_mode),
|
||||
.size = statbuf.st_size,
|
||||
.last_access = statbuf.st_atime,
|
||||
.last_modif = statbuf.st_mtime
|
||||
};
|
||||
}
|
||||
else {
|
||||
return (fs_stat_t) { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
fs_time_t fsAsTime(int64_t timer) {
|
||||
struct tm *t = localtime(&timer);
|
||||
|
||||
if(t) {
|
||||
return (fs_time_t) {
|
||||
.year = t->tm_year + 1900,
|
||||
.month = t->tm_mon + 1,
|
||||
.day = t->tm_mday,
|
||||
.hour = t->tm_hour,
|
||||
.minutes = t->tm_min,
|
||||
.seconds = t->tm_sec,
|
||||
.daylight_saving = t->tm_isdst > 0
|
||||
};
|
||||
}
|
||||
else {
|
||||
err("%s", strerror(errno));
|
||||
return (fs_time_t) { 0 };
|
||||
}
|
||||
}
|
||||
#include "fs.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VC_EXTRALEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int _modeToType(unsigned int mode) {
|
||||
switch(mode & _S_IFMT) {
|
||||
case _S_IFDIR: return FS_MODE_DIR;
|
||||
case _S_IFCHR: return FS_MODE_CHARACTER_DEVICE;
|
||||
case _S_IFREG: return FS_MODE_FILE;
|
||||
case _S_IFIFO: return FS_MODE_FIFO;
|
||||
default: return FS_MODE_UKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
fs_stat_t fsStat(file_t fp) {
|
||||
TCHAR path[MAX_PATH];
|
||||
GetFinalPathNameByHandle(
|
||||
(HANDLE)fp.handle,
|
||||
path,
|
||||
MAX_PATH,
|
||||
0
|
||||
);
|
||||
|
||||
struct stat statbuf;
|
||||
int res = stat(path, &statbuf);
|
||||
if(res == 0) {
|
||||
return (fs_stat_t) {
|
||||
.type = _modeToType(statbuf.st_mode),
|
||||
.size = statbuf.st_size,
|
||||
.last_access = statbuf.st_atime,
|
||||
.last_modif = statbuf.st_mtime
|
||||
};
|
||||
}
|
||||
else {
|
||||
return (fs_stat_t) { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
fs_time_t fsAsTime(int64_t timer) {
|
||||
struct tm t;
|
||||
errno_t error = localtime_s(&t, &timer);
|
||||
|
||||
if(error == 0) {
|
||||
return (fs_time_t) {
|
||||
.year = t.tm_year + 1900,
|
||||
.month = t.tm_mon + 1,
|
||||
.day = t.tm_mday,
|
||||
.hour = t.tm_hour,
|
||||
.minutes = t.tm_min,
|
||||
.seconds = t.tm_sec,
|
||||
.daylight_saving = t.tm_isdst > 0
|
||||
};
|
||||
}
|
||||
else {
|
||||
char buf[128];
|
||||
strerror_s(buf, sizeof(buf), error);
|
||||
err("%s", buf);
|
||||
return (fs_time_t) { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
bool fsIsDir(const char *path) {
|
||||
DWORD attr = GetFileAttributes(path);
|
||||
|
||||
return attr != INVALID_FILE_ATTRIBUTES &&
|
||||
attr & FILE_ATTRIBUTE_DIRECTORY;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int _modeToType(unsigned int mode) {
|
||||
switch(mode & __S_IFMT) {
|
||||
case __S_IFDIR: return FS_MODE_DIR;
|
||||
case __S_IFCHR: return FS_MODE_CHARACTER_DEVICE;
|
||||
case __S_IFREG: return FS_MODE_FILE;
|
||||
case __S_IFIFO: return FS_MODE_FIFO;
|
||||
default: return FS_MODE_UKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
fs_stat_t fsStat(file_t fp) {
|
||||
int fd = fileno((FILE*)fp.handle);
|
||||
struct stat statbuf;
|
||||
int res = fstat(fd, &statbuf);
|
||||
if(res == 0) {
|
||||
return (fs_stat_t) {
|
||||
.type = _modeToType(statbuf.st_mode),
|
||||
.size = statbuf.st_size,
|
||||
.last_access = statbuf.st_atime,
|
||||
.last_modif = statbuf.st_mtime
|
||||
};
|
||||
}
|
||||
else {
|
||||
return (fs_stat_t) { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
fs_time_t fsAsTime(int64_t timer) {
|
||||
struct tm *t = localtime(&timer);
|
||||
|
||||
if(t) {
|
||||
return (fs_time_t) {
|
||||
.year = t->tm_year + 1900,
|
||||
.month = t->tm_mon + 1,
|
||||
.day = t->tm_mday,
|
||||
.hour = t->tm_hour,
|
||||
.minutes = t->tm_min,
|
||||
.seconds = t->tm_sec,
|
||||
.daylight_saving = t->tm_isdst > 0
|
||||
};
|
||||
}
|
||||
else {
|
||||
err("%s", strerror(errno));
|
||||
return (fs_time_t) { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
bool fsIsDir(const char *path) {
|
||||
struct stat statbuf;
|
||||
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode);
|
||||
}
|
||||
|
||||
#endif
|
||||
78
fs.h
78
fs.h
|
|
@ -1,34 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "file.h"
|
||||
|
||||
enum {
|
||||
FS_MODE_FILE,
|
||||
FS_MODE_DIR,
|
||||
FS_MODE_CHARACTER_DEVICE,
|
||||
FS_MODE_FIFO,
|
||||
FS_MODE_UKNOWN,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
uint64_t size;
|
||||
int64_t last_access;
|
||||
int64_t last_modif;
|
||||
} fs_stat_t;
|
||||
|
||||
typedef struct {
|
||||
int year;
|
||||
int month;
|
||||
int day;
|
||||
int hour;
|
||||
int minutes;
|
||||
int seconds;
|
||||
bool daylight_saving;
|
||||
} fs_time_t;
|
||||
|
||||
fs_stat_t fsStat(file_t fp);
|
||||
fs_time_t fsAsTime(int64_t time);
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "file.h"
|
||||
|
||||
enum {
|
||||
FS_MODE_FILE,
|
||||
FS_MODE_DIR,
|
||||
FS_MODE_CHARACTER_DEVICE,
|
||||
FS_MODE_FIFO,
|
||||
FS_MODE_UKNOWN,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
uint64_t size;
|
||||
int64_t last_access;
|
||||
int64_t last_modif;
|
||||
} fs_stat_t;
|
||||
|
||||
typedef struct {
|
||||
int year;
|
||||
int month;
|
||||
int day;
|
||||
int hour;
|
||||
int minutes;
|
||||
int seconds;
|
||||
bool daylight_saving;
|
||||
} fs_time_t;
|
||||
|
||||
fs_stat_t fsStat(file_t fp);
|
||||
fs_time_t fsAsTime(int64_t time);
|
||||
|
||||
bool fsIsDir(const char *path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
720
http.c
720
http.c
|
|
@ -1,359 +1,361 @@
|
|||
#include "http.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "os.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.len > 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 = ostrInitLen(1024);
|
||||
|
||||
if(!skInit()) {
|
||||
err("couldn't initialize sockets %s", skGetErrorString());
|
||||
goto skopen_error;
|
||||
}
|
||||
|
||||
ctx->socket = skOpen(SOCK_TCP);
|
||||
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;
|
||||
}
|
||||
|
||||
#include "http.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "os.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
#define T http_field_t
|
||||
#define VEC_SHORT_NAME field_vec
|
||||
#define VEC_DISABLE_ERASE_WHEN
|
||||
#define VEC_NO_DECLARATION
|
||||
#include "vec.h"
|
||||
|
||||
// == INTERNAL ================================================================
|
||||
|
||||
static void _setField(field_vec_t *fields, const char *key, const char *value) {
|
||||
// search if the field already exists
|
||||
for(size_t i = 0; i < fields->size; ++i) {
|
||||
if(stricmp(fields->buf[i].key, key) == 0) {
|
||||
// replace value
|
||||
char **curval = &fields->buf[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
|
||||
http_field_t field;
|
||||
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';
|
||||
|
||||
field_vecPush(fields, field);
|
||||
}
|
||||
|
||||
// == 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(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
free(ctx->fields.buf[i].key);
|
||||
free(ctx->fields.buf[i].value);
|
||||
}
|
||||
field_vecFree(&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(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
if(stricmp(ctx->fields.buf[i].key, key) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void reqSetField(http_request_t *ctx, const char *key, const char *value) {
|
||||
_setField(&ctx->fields, 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(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
ostrPrintf(&out, "%s: %s\r\n", ctx->fields.buf[i].key, ctx->fields.buf[i].value);
|
||||
}
|
||||
|
||||
ostrAppendview(&out, strvInit("\r\n"));
|
||||
if(ctx->body) {
|
||||
ostrAppendview(&out, strvInit(ctx->body));
|
||||
}
|
||||
|
||||
error:
|
||||
return out;
|
||||
}
|
||||
|
||||
str_t reqString(http_request_t *ctx) {
|
||||
str_ostream_t out = reqPrepare(ctx);
|
||||
return ostrMove(&out);
|
||||
}
|
||||
|
||||
// == HTTP RESPONSE ===========================================================
|
||||
|
||||
http_response_t resInit() {
|
||||
http_response_t res;
|
||||
memset(&res, 0, sizeof(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
void resFree(http_response_t *ctx) {
|
||||
for(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
free(ctx->fields.buf[i].key);
|
||||
free(ctx->fields.buf[i].value);
|
||||
}
|
||||
field_vecFree(&ctx->fields);
|
||||
free(ctx->body);
|
||||
memset(ctx, 0, sizeof(http_response_t));
|
||||
}
|
||||
|
||||
bool resHasField(http_response_t *ctx, const char *key) {
|
||||
for(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
if(stricmp(ctx->fields.buf[i].key, key) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *resGetField(http_response_t *ctx, const char *field) {
|
||||
for(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
if(stricmp(ctx->fields.buf[i].key, field) == 0) {
|
||||
return ctx->fields.buf[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);
|
||||
ctx->body = strvCopy(body).buf;
|
||||
}
|
||||
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;
|
||||
|
||||
key_str = strvCopy(key).buf;
|
||||
value_str = strvCopy(value).buf;
|
||||
|
||||
_setField(&ctx->fields, key_str, value_str);
|
||||
|
||||
free(key_str);
|
||||
free(value_str);
|
||||
}
|
||||
|
||||
istrSkip(in, 2); // skip \r\n
|
||||
} while(line.len > 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) {
|
||||
ctx->host_name = strvCopy(strvSubstr(hostview, 7, SIZE_MAX)).buf;
|
||||
}
|
||||
else if(strvICompare(strvSubstr(hostview, 0, 8), strvInit("https://")) == 0) {
|
||||
err("HTTPS protocol not yet supported");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// undefined protocol, use HTTP
|
||||
ctx->host_name = strvCopy(hostview).buf;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
str_t req_str = strInit();
|
||||
str_ostream_t received = ostrInitLen(1024);
|
||||
|
||||
if(!skInit()) {
|
||||
err("couldn't initialize sockets %s", skGetErrorString());
|
||||
goto skopen_error;
|
||||
}
|
||||
|
||||
ctx->socket = skOpen(SOCK_TCP);
|
||||
if(ctx->socket == INVALID_SOCKET) {
|
||||
err("couldn't open socket %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
|
||||
if(skConnect(ctx->socket, ctx->host_name, ctx->port)) {
|
||||
req_str = reqString(req);
|
||||
if(req_str.len == 0) {
|
||||
err("couldn't get string from request");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if(skSend(ctx->socket, req_str.buf, (int)req_str.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:
|
||||
strFree(&req_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;
|
||||
}
|
||||
|
||||
|
|
|
|||
258
http.h
258
http.h
|
|
@ -1,128 +1,132 @@
|
|||
#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;
|
||||
|
||||
// translates 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);
|
||||
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);
|
||||
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);
|
||||
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"
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "str.h"
|
||||
#include "strstream.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;
|
||||
|
||||
// translates 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;
|
||||
|
||||
#define T http_field_t
|
||||
#define VEC_SHORT_NAME field_vec
|
||||
#define VEC_DISABLE_ERASE_WHEN
|
||||
#define VEC_NO_IMPLEMENTATION
|
||||
#include "vec.h"
|
||||
|
||||
// == HTTP REQUEST ============================================================
|
||||
|
||||
typedef struct {
|
||||
int method;
|
||||
http_version_t version;
|
||||
field_vec_t fields;
|
||||
char *uri;
|
||||
char *body;
|
||||
} http_request_t;
|
||||
|
||||
http_request_t reqInit(void);
|
||||
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);
|
||||
str_t reqString(http_request_t *ctx);
|
||||
|
||||
// == HTTP RESPONSE ===========================================================
|
||||
|
||||
typedef struct {
|
||||
int status_code;
|
||||
field_vec_t fields;
|
||||
http_version_t version;
|
||||
char *body;
|
||||
} http_response_t;
|
||||
|
||||
http_response_t resInit(void);
|
||||
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);
|
||||
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
|
||||
132
os.c
132
os.c
|
|
@ -1,67 +1,67 @@
|
|||
#include "os.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _BUFSZ 128
|
||||
|
||||
// modified from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getdelim.c?only_with_tag=MAIN
|
||||
ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) {
|
||||
char *ptr, *eptr;
|
||||
|
||||
if(*buf == NULL || *bufsz == 0) {
|
||||
*bufsz = _BUFSZ;
|
||||
if((*buf = malloc(*bufsz)) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t result = -1;
|
||||
// usually fgetc locks every read, using windows-specific
|
||||
// _lock_file and _unlock_file will be faster
|
||||
_lock_file(fp);
|
||||
|
||||
for(ptr = *buf, eptr = *buf + *bufsz;;) {
|
||||
int c = _getc_nolock(fp);
|
||||
if(c == -1) {
|
||||
if(feof(fp)) {
|
||||
ssize_t diff = (ssize_t)(ptr - *buf);
|
||||
if(diff != 0) {
|
||||
*ptr = '\0';
|
||||
result = diff;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
*ptr++ = c;
|
||||
if(c == delimiter) {
|
||||
*ptr = '\0';
|
||||
result = ptr - *buf;
|
||||
break;
|
||||
}
|
||||
if((ptr + 2) >= eptr) {
|
||||
char *nbuf;
|
||||
size_t nbufsz = *bufsz * 2;
|
||||
ssize_t d = ptr - *buf;
|
||||
if((nbuf = realloc(*buf, nbufsz)) == NULL) {
|
||||
break;
|
||||
}
|
||||
*buf = nbuf;
|
||||
*bufsz = nbufsz;
|
||||
eptr = nbuf + nbufsz;
|
||||
ptr = nbuf + d;
|
||||
}
|
||||
}
|
||||
|
||||
_unlock_file(fp);
|
||||
return result;
|
||||
}
|
||||
|
||||
// taken from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getline.c?only_with_tag=MAIN
|
||||
ssize_t getline(char **line_ptr, size_t *n, FILE *stream) {
|
||||
return getdelim(line_ptr, n, '\n', stream);
|
||||
}
|
||||
#include "os.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _BUFSZ 128
|
||||
|
||||
// modified from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getdelim.c?only_with_tag=MAIN
|
||||
ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) {
|
||||
char *ptr, *eptr;
|
||||
|
||||
if(*buf == NULL || *bufsz == 0) {
|
||||
*bufsz = _BUFSZ;
|
||||
if((*buf = malloc(*bufsz)) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t result = -1;
|
||||
// usually fgetc locks every read, using windows-specific
|
||||
// _lock_file and _unlock_file will be faster
|
||||
_lock_file(fp);
|
||||
|
||||
for(ptr = *buf, eptr = *buf + *bufsz;;) {
|
||||
int c = _getc_nolock(fp);
|
||||
if(c == -1) {
|
||||
if(feof(fp)) {
|
||||
ssize_t diff = (ssize_t)(ptr - *buf);
|
||||
if(diff != 0) {
|
||||
*ptr = '\0';
|
||||
result = diff;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
*ptr++ = (char)c;
|
||||
if(c == delimiter) {
|
||||
*ptr = '\0';
|
||||
result = ptr - *buf;
|
||||
break;
|
||||
}
|
||||
if((ptr + 2) >= eptr) {
|
||||
char *nbuf;
|
||||
size_t nbufsz = *bufsz * 2;
|
||||
ssize_t d = ptr - *buf;
|
||||
if((nbuf = realloc(*buf, nbufsz)) == NULL) {
|
||||
break;
|
||||
}
|
||||
*buf = nbuf;
|
||||
*bufsz = nbufsz;
|
||||
eptr = nbuf + nbufsz;
|
||||
ptr = nbuf + d;
|
||||
}
|
||||
}
|
||||
|
||||
_unlock_file(fp);
|
||||
return result;
|
||||
}
|
||||
|
||||
// taken from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getline.c?only_with_tag=MAIN
|
||||
ssize_t getline(char **line_ptr, size_t *n, FILE *stream) {
|
||||
return getdelim(line_ptr, n, '\n', stream);
|
||||
}
|
||||
#endif
|
||||
50
os.h
50
os.h
|
|
@ -1,26 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <stdio.h>
|
||||
#include <BaseTsd.h> // SSIZE_T
|
||||
typedef SSIZE_T ssize_t;
|
||||
ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp);
|
||||
ssize_t getline(char **line_ptr, size_t *n, FILE *stream);
|
||||
#define stricmp _stricmp
|
||||
#else
|
||||
#define stricmp strcasecmp
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h> // ssize_t
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <stdio.h>
|
||||
#include <BaseTsd.h> // SSIZE_T
|
||||
typedef SSIZE_T ssize_t;
|
||||
ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp);
|
||||
ssize_t getline(char **line_ptr, size_t *n, FILE *stream);
|
||||
#define stricmp _stricmp
|
||||
#else
|
||||
#define stricmp strcasecmp
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h> // ssize_t
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
5
slice.h
5
slice.h
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h> // size_t
|
||||
|
||||
#define slice_t(type) struct { type buf; size_t len; }
|
||||
674
socket.c
674
socket.c
|
|
@ -1,338 +1,338 @@
|
|||
#include "socket.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
// VERY MUCH NOT THREAD SAFE
|
||||
static int initialize_count = 0;
|
||||
#endif
|
||||
|
||||
#if SOCK_WINDOWS
|
||||
#include <ws2tcpip.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 <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() {
|
||||
#ifndef NDEBUG
|
||||
++initialize_count;
|
||||
#endif
|
||||
return SOCK_CALL(skInit());
|
||||
}
|
||||
|
||||
bool skCleanup() {
|
||||
#ifndef NDEBUG
|
||||
--initialize_count;
|
||||
#endif
|
||||
return SOCK_CALL(skCleanup());
|
||||
}
|
||||
|
||||
socket_t skOpen(skType type) {
|
||||
int sock_type;
|
||||
|
||||
switch(type) {
|
||||
case SOCK_TCP: sock_type = SOCK_STREAM; break;
|
||||
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
|
||||
default: fatal("skType not recognized: %d", type); break;
|
||||
}
|
||||
|
||||
return skOpenPro(AF_INET, sock_type, 0);
|
||||
}
|
||||
|
||||
socket_t skOpenEx(const char *protocol) {
|
||||
#ifndef NDEBUG
|
||||
if(initialize_count <= 0) {
|
||||
fatal("skInit has not been called");
|
||||
}
|
||||
#endif
|
||||
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) {
|
||||
#ifndef NDEBUG
|
||||
if(initialize_count <= 0) {
|
||||
fatal("skInit has not been called");
|
||||
}
|
||||
#endif
|
||||
return socket(af, type, protocol);
|
||||
}
|
||||
|
||||
sk_addrin_t skAddrinInit(const char *ip, uint16_t port) {
|
||||
sk_addrin_t addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = inet_addr(ip);
|
||||
return addr;
|
||||
}
|
||||
|
||||
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) {
|
||||
sk_addrin_t addr;
|
||||
addr.sin_family = AF_INET;
|
||||
// TODO use inet_pton instead
|
||||
addr.sin_addr.s_addr = inet_addr(ip);
|
||||
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
bool skBindPro(socket_t sock, const sk_addr_t *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) {
|
||||
sk_addrin_t addr;
|
||||
socket_len_t addr_size = (socket_len_t)sizeof(addr);
|
||||
return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size);
|
||||
}
|
||||
|
||||
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, socket_len_t *addrlen) {
|
||||
return accept(sock, addr, addrlen);
|
||||
}
|
||||
|
||||
bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
|
||||
// TODO use getaddrinfo insetad
|
||||
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]);
|
||||
}
|
||||
|
||||
sk_addrin_t addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = inet_addr(address);
|
||||
addr.sin_port = htons(server_port);
|
||||
|
||||
return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) {
|
||||
return connect(sock, name, namelen) != SOCKET_ERROR;
|
||||
}
|
||||
|
||||
int skSend(socket_t sock, const char *buf, int len) {
|
||||
return skSendPro(sock, buf, len, 0);
|
||||
}
|
||||
|
||||
int skSendPro(socket_t sock, const char *buf, int len, int flags) {
|
||||
return send(sock, buf, len, flags);
|
||||
}
|
||||
|
||||
int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to) {
|
||||
return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t));
|
||||
}
|
||||
|
||||
int skSendToPro(socket_t sock, const char *buf, int len, int flags, const sk_addr_t *to, int tolen) {
|
||||
return sendto(sock, buf, len, flags, to, tolen);
|
||||
}
|
||||
|
||||
int skReceive(socket_t sock, char *buf, int len) {
|
||||
return skReceivePro(sock, buf, len, 0);
|
||||
}
|
||||
|
||||
int skReceivePro(socket_t sock, char *buf, int len, int flags) {
|
||||
return recv(sock, buf, len, flags);
|
||||
}
|
||||
|
||||
int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from) {
|
||||
int fromlen = sizeof(sk_addr_t);
|
||||
return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen);
|
||||
}
|
||||
|
||||
int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, int *fromlen) {
|
||||
return recvfrom(sock, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
#include "socket.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
// VERY MUCH NOT THREAD SAFE
|
||||
static int initialize_count = 0;
|
||||
#endif
|
||||
|
||||
#if SOCK_WINDOWS
|
||||
#include <ws2tcpip.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 <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() {
|
||||
#ifndef NDEBUG
|
||||
++initialize_count;
|
||||
#endif
|
||||
return SOCK_CALL(skInit());
|
||||
}
|
||||
|
||||
bool skCleanup() {
|
||||
#ifndef NDEBUG
|
||||
--initialize_count;
|
||||
#endif
|
||||
return SOCK_CALL(skCleanup());
|
||||
}
|
||||
|
||||
socket_t skOpen(skType type) {
|
||||
int sock_type = 0;
|
||||
|
||||
switch(type) {
|
||||
case SOCK_TCP: sock_type = SOCK_STREAM; break;
|
||||
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
|
||||
default: fatal("skType not recognized: %d", type); break;
|
||||
}
|
||||
|
||||
return skOpenPro(AF_INET, sock_type, 0);
|
||||
}
|
||||
|
||||
socket_t skOpenEx(const char *protocol) {
|
||||
#ifndef NDEBUG
|
||||
if(initialize_count <= 0) {
|
||||
fatal("skInit has not been called");
|
||||
}
|
||||
#endif
|
||||
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) {
|
||||
#ifndef NDEBUG
|
||||
if(initialize_count <= 0) {
|
||||
fatal("skInit has not been called");
|
||||
}
|
||||
#endif
|
||||
return socket(af, type, protocol);
|
||||
}
|
||||
|
||||
sk_addrin_t skAddrinInit(const char *ip, uint16_t port) {
|
||||
sk_addrin_t addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = inet_addr(ip);
|
||||
return addr;
|
||||
}
|
||||
|
||||
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) {
|
||||
sk_addrin_t addr;
|
||||
addr.sin_family = AF_INET;
|
||||
// TODO use inet_pton instead
|
||||
addr.sin_addr.s_addr = inet_addr(ip);
|
||||
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
bool skBindPro(socket_t sock, const sk_addr_t *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) {
|
||||
sk_addrin_t addr;
|
||||
socket_len_t addr_size = (socket_len_t)sizeof(addr);
|
||||
return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size);
|
||||
}
|
||||
|
||||
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, socket_len_t *addrlen) {
|
||||
return accept(sock, addr, addrlen);
|
||||
}
|
||||
|
||||
bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
|
||||
// TODO use getaddrinfo insetad
|
||||
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]);
|
||||
}
|
||||
|
||||
sk_addrin_t addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = inet_addr(address);
|
||||
addr.sin_port = htons(server_port);
|
||||
|
||||
return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) {
|
||||
return connect(sock, name, namelen) != SOCKET_ERROR;
|
||||
}
|
||||
|
||||
int skSend(socket_t sock, const char *buf, int len) {
|
||||
return skSendPro(sock, buf, len, 0);
|
||||
}
|
||||
|
||||
int skSendPro(socket_t sock, const char *buf, int len, int flags) {
|
||||
return send(sock, buf, len, flags);
|
||||
}
|
||||
|
||||
int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to) {
|
||||
return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t));
|
||||
}
|
||||
|
||||
int skSendToPro(socket_t sock, const char *buf, int len, int flags, const sk_addr_t *to, int tolen) {
|
||||
return sendto(sock, buf, len, flags, to, tolen);
|
||||
}
|
||||
|
||||
int skReceive(socket_t sock, char *buf, int len) {
|
||||
return skReceivePro(sock, buf, len, 0);
|
||||
}
|
||||
|
||||
int skReceivePro(socket_t sock, char *buf, int len, int flags) {
|
||||
return recv(sock, buf, len, flags);
|
||||
}
|
||||
|
||||
int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from) {
|
||||
socket_len_t fromlen = sizeof(sk_addr_t);
|
||||
return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen);
|
||||
}
|
||||
|
||||
int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, socket_len_t *fromlen) {
|
||||
return recvfrom(sock, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
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
|
||||
228
socket.h
228
socket.h
|
|
@ -1,115 +1,115 @@
|
|||
#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
|
||||
|
||||
#if SOCK_WINDOWS
|
||||
#pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <winsock2.h>
|
||||
typedef SOCKET socket_t;
|
||||
typedef int socket_len_t;
|
||||
#elif SOCK_POSIX
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
typedef int socket_t;
|
||||
typedef uint32_t socket_len_t;
|
||||
#define INVALID_SOCKET (-1)
|
||||
#define SOCKET_ERROR (-1)
|
||||
#endif
|
||||
|
||||
typedef struct sockaddr sk_addr_t;
|
||||
typedef struct sockaddr_in sk_addrin_t;
|
||||
|
||||
typedef enum {
|
||||
SOCK_TCP,
|
||||
SOCK_UDP,
|
||||
} skType;
|
||||
|
||||
// == RAW SOCKETS ==========================================
|
||||
|
||||
// Initialize sockets, returns true on success
|
||||
bool skInit(void);
|
||||
// Terminates sockets, returns true on success
|
||||
bool skCleanup(void);
|
||||
|
||||
// Opens a socket, check socket_t with skValid
|
||||
socket_t skOpen(skType type);
|
||||
// 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);
|
||||
|
||||
// Fill out a sk_addrin_t structure with "ip" and "port"
|
||||
sk_addrin_t skAddrinInit(const char *ip, uint16_t port);
|
||||
|
||||
// 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 sk_addr_t *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, sk_addr_t *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 sk_addr_t *name, socket_len_t namelen);
|
||||
|
||||
// Sends data on a socket, returns true on success
|
||||
int skSend(socket_t sock, const char *buf, int len);
|
||||
// Sends data on a socket, returns true on success
|
||||
int skSendPro(socket_t sock, const char *buf, int len, int flags);
|
||||
// Sends data to a specific destination
|
||||
int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to);
|
||||
// Sends data to a specific destination
|
||||
int skSendToPro(socket_t sock, const char *buf, int len, int flags, const sk_addr_t *to, int tolen);
|
||||
// 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);
|
||||
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
||||
int skReceivePro(socket_t sock, char *buf, int len, int flags);
|
||||
// Receives a datagram and stores the source address.
|
||||
int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from);
|
||||
// Receives a datagram and stores the source address.
|
||||
int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, int *fromlen);
|
||||
|
||||
// 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(void);
|
||||
// Returns a human-readable string from a skGetError
|
||||
const char *skGetErrorString(void);
|
||||
|
||||
// == UDP SOCKETS ==========================================
|
||||
|
||||
typedef socket_t udpsock_t;
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#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
|
||||
|
||||
#if SOCK_WINDOWS
|
||||
#pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include <winsock2.h>
|
||||
typedef SOCKET socket_t;
|
||||
typedef int socket_len_t;
|
||||
#elif SOCK_POSIX
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
typedef int socket_t;
|
||||
typedef uint32_t socket_len_t;
|
||||
#define INVALID_SOCKET (-1)
|
||||
#define SOCKET_ERROR (-1)
|
||||
#endif
|
||||
|
||||
typedef struct sockaddr sk_addr_t;
|
||||
typedef struct sockaddr_in sk_addrin_t;
|
||||
|
||||
typedef enum {
|
||||
SOCK_TCP,
|
||||
SOCK_UDP,
|
||||
} skType;
|
||||
|
||||
// == RAW SOCKETS ==========================================
|
||||
|
||||
// Initialize sockets, returns true on success
|
||||
bool skInit(void);
|
||||
// Terminates sockets, returns true on success
|
||||
bool skCleanup(void);
|
||||
|
||||
// Opens a socket, check socket_t with skValid
|
||||
socket_t skOpen(skType type);
|
||||
// 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);
|
||||
|
||||
// Fill out a sk_addrin_t structure with "ip" and "port"
|
||||
sk_addrin_t skAddrinInit(const char *ip, uint16_t port);
|
||||
|
||||
// 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 sk_addr_t *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, sk_addr_t *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 sk_addr_t *name, socket_len_t namelen);
|
||||
|
||||
// Sends data on a socket, returns true on success
|
||||
int skSend(socket_t sock, const char *buf, int len);
|
||||
// Sends data on a socket, returns true on success
|
||||
int skSendPro(socket_t sock, const char *buf, int len, int flags);
|
||||
// Sends data to a specific destination
|
||||
int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to);
|
||||
// Sends data to a specific destination
|
||||
int skSendToPro(socket_t sock, const char *buf, int len, int flags, const sk_addr_t *to, int tolen);
|
||||
// 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);
|
||||
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
||||
int skReceivePro(socket_t sock, char *buf, int len, int flags);
|
||||
// Receives a datagram and stores the source address.
|
||||
int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from);
|
||||
// Receives a datagram and stores the source address.
|
||||
int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, socket_len_t *fromlen);
|
||||
|
||||
// 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(void);
|
||||
// Returns a human-readable string from a skGetError
|
||||
const char *skGetErrorString(void);
|
||||
|
||||
// == UDP SOCKETS ==========================================
|
||||
|
||||
typedef socket_t udpsock_t;
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
885
str.c
885
str.c
|
|
@ -1,267 +1,620 @@
|
|||
#include "str.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
str_t strInit(void) {
|
||||
return (str_t) {
|
||||
.buf = NULL,
|
||||
.len = 0
|
||||
};
|
||||
}
|
||||
|
||||
str_t strInitStr(const char *cstr) {
|
||||
return strInitBuf(cstr, strlen(cstr));
|
||||
}
|
||||
|
||||
str_t strInitView(strview_t view) {
|
||||
return strInitBuf(view.buf, view.len);
|
||||
}
|
||||
|
||||
str_t strInitBuf(const char *buf, size_t len) {
|
||||
str_t str;
|
||||
str.len = len;
|
||||
str.buf = malloc(len + 1);
|
||||
memcpy(str.buf, buf, len);
|
||||
str.buf[len] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
void strFree(str_t *ctx) {
|
||||
free(ctx->buf);
|
||||
ctx->buf = NULL;
|
||||
ctx->len = 0;
|
||||
}
|
||||
|
||||
str_t strMove(str_t *ctx) {
|
||||
str_t str = strInitBuf(ctx->buf, ctx->len);
|
||||
ctx->buf = NULL;
|
||||
ctx->len = 0;
|
||||
return str;
|
||||
}
|
||||
|
||||
str_t strDup(str_t ctx) {
|
||||
return strInitBuf(ctx.buf, ctx.len);
|
||||
}
|
||||
|
||||
strview_t strGetView(str_t *ctx) {
|
||||
return (strview_t) {
|
||||
.buf = ctx->buf,
|
||||
.len = ctx->len
|
||||
};
|
||||
}
|
||||
|
||||
char *strBegin(str_t *ctx) {
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
char *strEnd(str_t *ctx) {
|
||||
return ctx->buf ? ctx->buf + ctx->len : NULL;
|
||||
}
|
||||
|
||||
char strBack(str_t *ctx) {
|
||||
return ctx->buf ? ctx->buf[ctx->len - 1] : '\0';
|
||||
}
|
||||
|
||||
bool strIsEmpty(str_t *ctx) {
|
||||
return ctx->len == 0;
|
||||
}
|
||||
|
||||
void strAppend(str_t *ctx, const char *str) {
|
||||
strAppendBuf(ctx, str, strlen(str));
|
||||
}
|
||||
|
||||
void strAppendStr(str_t *ctx, str_t str) {
|
||||
strAppendBuf(ctx, str.buf, str.len);
|
||||
}
|
||||
|
||||
void strAppendView(str_t *ctx, strview_t view) {
|
||||
strAppendBuf(ctx, view.buf, view.len);
|
||||
}
|
||||
|
||||
void strAppendBuf(str_t *ctx, const char *buf, size_t len) {
|
||||
size_t oldlen = ctx->len;
|
||||
ctx->len += len;
|
||||
ctx->buf = realloc(ctx->buf, ctx->len + 1);
|
||||
memcpy(ctx->buf + oldlen, buf, len);
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
}
|
||||
|
||||
void strAppendChars(str_t *ctx, char c, size_t count) {
|
||||
size_t oldlen = ctx->len;
|
||||
ctx->len += count;
|
||||
ctx->buf = realloc(ctx->buf, ctx->len + 1);
|
||||
memset(ctx->buf + oldlen, c, count);
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
}
|
||||
|
||||
void strPush(str_t *ctx, char c) {
|
||||
strAppendChars(ctx, c, 1);
|
||||
}
|
||||
|
||||
char strPop(str_t *ctx) {
|
||||
char c = strBack(ctx);
|
||||
ctx->buf = realloc(ctx->buf, ctx->len);
|
||||
ctx->len -= 1;
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
return c;
|
||||
}
|
||||
|
||||
void strSwap(str_t *ctx, str_t *other) {
|
||||
char *buf = other->buf;
|
||||
size_t len = other->len;
|
||||
other->buf = ctx->buf;
|
||||
other->len = ctx->len;
|
||||
ctx->buf = buf;
|
||||
ctx->len = len;
|
||||
}
|
||||
|
||||
#include "tracelog.h"
|
||||
|
||||
str_t strSubstr(str_t *ctx, size_t pos, size_t len) {
|
||||
if(strIsEmpty(ctx)) return strInit();
|
||||
if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos;
|
||||
return strInitBuf(ctx->buf + pos, len);
|
||||
}
|
||||
|
||||
strview_t strSubview(str_t *ctx, size_t pos, size_t len) {
|
||||
if(strIsEmpty(ctx)) return strvInit(NULL);
|
||||
if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos;
|
||||
return (strview_t) {
|
||||
.buf = ctx->buf + pos,
|
||||
.len = len
|
||||
};
|
||||
}
|
||||
|
||||
void strLower(str_t *ctx) {
|
||||
for(size_t i = 0; i < ctx->len; ++i) {
|
||||
ctx->buf[i] = tolower(ctx->buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
str_t strToLower(str_t ctx) {
|
||||
str_t str = strDup(ctx);
|
||||
strLower(&str);
|
||||
return str;
|
||||
}
|
||||
|
||||
#ifdef STR_TESTING
|
||||
#include <stdio.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
void strTest(void) {
|
||||
str_t s;
|
||||
debug("== testing init =================");
|
||||
{
|
||||
s = strInit();
|
||||
printf("%s %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
s = strInitStr("hello world");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
uint8_t buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
|
||||
s = strInitBuf((char *)buf, sizeof(buf));
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing view =================");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
strview_t view = strGetView(&s);
|
||||
printf("\"%.*s\" %zu\n", (int)view.len, view.buf, view.len);
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing begin/end ============");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
char *beg = strBegin(&s);
|
||||
char *end = strEnd(&s);
|
||||
printf("[ ");
|
||||
for(; beg < end; ++beg) {
|
||||
printf("%c ", *beg);
|
||||
}
|
||||
printf("]\n");
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing back/isempty =========");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
printf("[ ");
|
||||
while(!strIsEmpty(&s)) {
|
||||
printf("%c ", strBack(&s));
|
||||
strPop(&s);
|
||||
}
|
||||
printf("]\n");
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing append ===============");
|
||||
{
|
||||
s = strInitStr("hello ");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strAppend(&s, "world");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strAppendView(&s, strvInit(", how is it "));
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
uint8_t buf[] = { 'g', 'o', 'i', 'n', 'g' };
|
||||
strAppendBuf(&s, (char*)buf, sizeof(buf));
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strAppendChars(&s, '?', 2);
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing push/pop =============");
|
||||
{
|
||||
s = strInit();
|
||||
str_t s2 = strInitStr("hello world");
|
||||
|
||||
printf("%-14s %-14s\n", "s", "s2");
|
||||
printf("----------------------------\n");
|
||||
while(!strIsEmpty(&s2)) {
|
||||
printf("%-14s %-14s\n", s.buf, s2.buf);
|
||||
strPush(&s, strPop(&s2));
|
||||
}
|
||||
printf("%-14s %-14s\n", s.buf, s2.buf);
|
||||
|
||||
strFree(&s);
|
||||
strFree(&s2);
|
||||
}
|
||||
debug("== testing swap =================");
|
||||
{
|
||||
s = strInitStr("hello");
|
||||
str_t s2 = strInitStr("world");
|
||||
printf("%-8s %-8s\n", "s", "s2");
|
||||
printf("----------------\n");
|
||||
printf("%-8s %-8s\n", s.buf, s2.buf);
|
||||
strSwap(&s, &s2);
|
||||
printf("%-8s %-8s\n", s.buf, s2.buf);
|
||||
|
||||
strFree(&s);
|
||||
strFree(&s2);
|
||||
}
|
||||
debug("== testing substr ===============");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
printf("s: %s\n", s.buf);
|
||||
|
||||
printf("-- string\n");
|
||||
str_t s2 = strSubstr(&s, 0, 5);
|
||||
printf("0..5: \"%s\"\n", s2.buf);
|
||||
strFree(&s2);
|
||||
|
||||
s2 = strSubstr(&s, 5, SIZE_MAX);
|
||||
printf("6..SIZE_MAX: \"%s\"\n", s2.buf);
|
||||
strFree(&s2);
|
||||
|
||||
printf("-- view\n");
|
||||
strview_t v = strSubview(&s, 0, 5);
|
||||
printf("0..5: \"%.*s\"\n", (int)v.len, v.buf);
|
||||
v = strSubview(&s, 5, SIZE_MAX);
|
||||
printf("6..SIZE_MAX: \"%.*s\"\n", (int)v.len, v.buf);
|
||||
|
||||
strFree(&s);
|
||||
}
|
||||
|
||||
strFree(&s);
|
||||
}
|
||||
#include "str.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VC_EXTRALEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <iconv.h>
|
||||
#endif
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
// == STR_T ========================================================
|
||||
|
||||
str_t strInit(void) {
|
||||
return (str_t) {
|
||||
.buf = NULL,
|
||||
.len = 0
|
||||
};
|
||||
}
|
||||
|
||||
str_t strInitStr(const char *cstr) {
|
||||
return strInitBuf(cstr, strlen(cstr));
|
||||
}
|
||||
|
||||
str_t strInitView(strview_t view) {
|
||||
return strInitBuf(view.buf, view.len);
|
||||
}
|
||||
|
||||
str_t strInitBuf(const char *buf, size_t len) {
|
||||
str_t str;
|
||||
str.len = len;
|
||||
str.buf = malloc(len + 1);
|
||||
memcpy(str.buf, buf, len);
|
||||
str.buf[len] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
void strFree(str_t *ctx) {
|
||||
free(ctx->buf);
|
||||
ctx->buf = NULL;
|
||||
ctx->len = 0;
|
||||
}
|
||||
|
||||
str_t strFromWCHAR(const wchar_t *src, size_t len) {
|
||||
if(len == 0) len = wcslen(src);
|
||||
|
||||
#ifdef _WIN32
|
||||
// TODO CP_ACP should be CP_UTF8 but if i put CP_UTF8 it doesn't work??
|
||||
int result_len = WideCharToMultiByte(
|
||||
CP_ACP, 0,
|
||||
src, (int)len,
|
||||
NULL, 0,
|
||||
NULL, NULL
|
||||
);
|
||||
char *buf = malloc(result_len + 1);
|
||||
if(buf) {
|
||||
WideCharToMultiByte(
|
||||
CP_ACP, 0,
|
||||
src, (int)len,
|
||||
buf, result_len,
|
||||
NULL, NULL
|
||||
);
|
||||
buf[result_len] = '\0';
|
||||
}
|
||||
return (str_t) {
|
||||
.buf = buf,
|
||||
.len = result_len
|
||||
};
|
||||
#else
|
||||
size_t actual_len = len * sizeof(wchar_t);
|
||||
|
||||
size_t dest_len = len * 6;
|
||||
char *dest = malloc(dest_len);
|
||||
|
||||
iconv_t cd = iconv_open("UTF-8", "WCHAR_T");
|
||||
assert(cd);
|
||||
|
||||
size_t dest_left = dest_len;
|
||||
char *dest_temp = dest;
|
||||
char *src_temp = (char*)src;
|
||||
size_t lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left);
|
||||
assert(lost != ((size_t)-1));
|
||||
|
||||
dest_len -= dest_left;
|
||||
dest = realloc(dest, dest_len + 1);
|
||||
dest[dest_len] = '\0';
|
||||
|
||||
iconv_close(cd);
|
||||
|
||||
return (str_t){
|
||||
.buf = dest,
|
||||
.len = dest_len
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
wchar_t *strToWCHAR(str_t ctx) {
|
||||
#ifdef _WIN32
|
||||
UINT codepage = CP_ACP;
|
||||
int len = MultiByteToWideChar(
|
||||
codepage, 0,
|
||||
ctx.buf, (int)ctx.len,
|
||||
NULL, 0
|
||||
);
|
||||
wchar_t *str = malloc(sizeof(wchar_t) * (len + 1));
|
||||
if(!str) return NULL;
|
||||
len = MultiByteToWideChar(
|
||||
codepage, 0,
|
||||
ctx.buf, (int)ctx.len,
|
||||
str, len
|
||||
);
|
||||
str[len] = '\0';
|
||||
return str;
|
||||
#else
|
||||
size_t dest_len = ctx.len * sizeof(wchar_t);
|
||||
char *dest = malloc(dest_len);
|
||||
|
||||
iconv_t cd = iconv_open("WCHAR_T", "UTF-8");
|
||||
assert(cd);
|
||||
|
||||
size_t dest_left = dest_len;
|
||||
char *dest_temp = dest;
|
||||
char *src_temp = ctx.buf;
|
||||
size_t lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left);
|
||||
assert(lost != ((size_t)-1));
|
||||
|
||||
dest_len -= dest_left;
|
||||
dest = realloc(dest, dest_len + 1);
|
||||
dest[dest_len] = '\0';
|
||||
|
||||
iconv_close(cd);
|
||||
|
||||
return (wchar_t *)dest;
|
||||
#endif
|
||||
}
|
||||
|
||||
str_t strMove(str_t *ctx) {
|
||||
str_t str = strInitBuf(ctx->buf, ctx->len);
|
||||
ctx->buf = NULL;
|
||||
ctx->len = 0;
|
||||
return str;
|
||||
}
|
||||
|
||||
str_t strDup(str_t ctx) {
|
||||
return strInitBuf(ctx.buf, ctx.len);
|
||||
}
|
||||
|
||||
strview_t strGetView(str_t *ctx) {
|
||||
return (strview_t) {
|
||||
.buf = ctx->buf,
|
||||
.len = ctx->len
|
||||
};
|
||||
}
|
||||
|
||||
char *strBegin(str_t *ctx) {
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
char *strEnd(str_t *ctx) {
|
||||
return ctx->buf ? ctx->buf + ctx->len : NULL;
|
||||
}
|
||||
|
||||
char strBack(str_t *ctx) {
|
||||
return ctx->buf ? ctx->buf[ctx->len - 1] : '\0';
|
||||
}
|
||||
|
||||
bool strIsEmpty(str_t *ctx) {
|
||||
return ctx->len == 0;
|
||||
}
|
||||
|
||||
void strAppend(str_t *ctx, const char *str) {
|
||||
strAppendBuf(ctx, str, strlen(str));
|
||||
}
|
||||
|
||||
void strAppendStr(str_t *ctx, str_t str) {
|
||||
strAppendBuf(ctx, str.buf, str.len);
|
||||
}
|
||||
|
||||
void strAppendView(str_t *ctx, strview_t view) {
|
||||
strAppendBuf(ctx, view.buf, view.len);
|
||||
}
|
||||
|
||||
void strAppendBuf(str_t *ctx, const char *buf, size_t len) {
|
||||
size_t oldlen = ctx->len;
|
||||
ctx->len += len;
|
||||
ctx->buf = realloc(ctx->buf, ctx->len + 1);
|
||||
memcpy(ctx->buf + oldlen, buf, len);
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
}
|
||||
|
||||
void strAppendChars(str_t *ctx, char c, size_t count) {
|
||||
size_t oldlen = ctx->len;
|
||||
ctx->len += count;
|
||||
ctx->buf = realloc(ctx->buf, ctx->len + 1);
|
||||
memset(ctx->buf + oldlen, c, count);
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
}
|
||||
|
||||
void strPush(str_t *ctx, char c) {
|
||||
strAppendChars(ctx, c, 1);
|
||||
}
|
||||
|
||||
char strPop(str_t *ctx) {
|
||||
char c = strBack(ctx);
|
||||
ctx->buf = realloc(ctx->buf, ctx->len);
|
||||
ctx->len -= 1;
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
return c;
|
||||
}
|
||||
|
||||
void strSwap(str_t *ctx, str_t *other) {
|
||||
char *buf = other->buf;
|
||||
size_t len = other->len;
|
||||
other->buf = ctx->buf;
|
||||
other->len = ctx->len;
|
||||
ctx->buf = buf;
|
||||
ctx->len = len;
|
||||
}
|
||||
|
||||
void strReplace(str_t *ctx, char from, char to) {
|
||||
for(size_t i = 0; i < ctx->len; ++i) {
|
||||
if(ctx->buf[i] == from) {
|
||||
ctx->buf[i] = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str_t strSubstr(str_t *ctx, size_t pos, size_t len) {
|
||||
if(strIsEmpty(ctx)) return strInit();
|
||||
if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos;
|
||||
return strInitBuf(ctx->buf + pos, len);
|
||||
}
|
||||
|
||||
strview_t strSubview(str_t *ctx, size_t pos, size_t len) {
|
||||
if(strIsEmpty(ctx)) return strvInit(NULL);
|
||||
if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos;
|
||||
return (strview_t) {
|
||||
.buf = ctx->buf + pos,
|
||||
.len = len
|
||||
};
|
||||
}
|
||||
|
||||
void strLower(str_t *ctx) {
|
||||
for(size_t i = 0; i < ctx->len; ++i) {
|
||||
ctx->buf[i] = (char)tolower(ctx->buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
str_t strToLower(str_t ctx) {
|
||||
str_t str = strDup(ctx);
|
||||
strLower(&str);
|
||||
return str;
|
||||
}
|
||||
|
||||
// == STRVIEW_T ====================================================
|
||||
|
||||
strview_t strvInit(const char *cstr) {
|
||||
return strvInitLen(cstr, cstr ? strlen(cstr) : 0);
|
||||
}
|
||||
|
||||
strview_t strvInitStr(str_t str) {
|
||||
return strvInitLen(str.buf, str.len);
|
||||
}
|
||||
|
||||
strview_t strvInitLen(const char *buf, size_t size) {
|
||||
return (strview_t) {
|
||||
.buf = buf,
|
||||
.len = size
|
||||
};
|
||||
}
|
||||
|
||||
char strvFront(strview_t ctx) {
|
||||
return ctx.buf[0];
|
||||
}
|
||||
|
||||
char strvBack(strview_t ctx) {
|
||||
return ctx.buf[ctx.len - 1];
|
||||
}
|
||||
|
||||
const char *strvBegin(strview_t *ctx) {
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
const char *strvEnd(strview_t *ctx) {
|
||||
return ctx->buf + ctx->len;
|
||||
}
|
||||
|
||||
bool strvIsEmpty(strview_t ctx) {
|
||||
return ctx.len == 0;
|
||||
}
|
||||
|
||||
void strvRemovePrefix(strview_t *ctx, size_t n) {
|
||||
ctx->buf += n;
|
||||
ctx->len -= n;
|
||||
}
|
||||
|
||||
void strvRemoveSuffix(strview_t *ctx, size_t n) {
|
||||
ctx->len -= n;
|
||||
}
|
||||
|
||||
str_t strvCopy(strview_t ctx) {
|
||||
return strInitView(ctx);
|
||||
}
|
||||
|
||||
str_t strvCopyN(strview_t ctx, size_t count, size_t from) {
|
||||
size_t sz = ctx.len + 1 - from;
|
||||
count = min(count, sz);
|
||||
return strInitBuf(ctx.buf + from, count);
|
||||
}
|
||||
|
||||
size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from) {
|
||||
size_t sz = ctx.len + 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.len) from = ctx.len - len;
|
||||
size_t sz = ctx.len - from;
|
||||
return strvInitLen(ctx.buf + from, min(len, sz));
|
||||
}
|
||||
|
||||
int strvCompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
return memcmp(ctx.buf, other.buf, ctx.len);
|
||||
}
|
||||
|
||||
int strvICompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
for(size_t i = 0; i < ctx.len; ++i) {
|
||||
int a = tolower(ctx.buf[i]);
|
||||
int 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.len < view.len) return false;
|
||||
return memcmp(ctx.buf, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvEndsWith(strview_t ctx, char c) {
|
||||
return strvBack(ctx) == c;
|
||||
}
|
||||
|
||||
bool strvEndsWithView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvContains(strview_t ctx, char c) {
|
||||
for(size_t i = 0; i < ctx.len; ++i) {
|
||||
if(ctx.buf[i] == c) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool strvContainsView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
size_t end = ctx.len - view.len;
|
||||
for(size_t i = 0; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t strvFind(strview_t ctx, char c, size_t from) {
|
||||
for(size_t i = from; i < ctx.len; ++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.len < view.len) return SIZE_MAX;
|
||||
size_t end = ctx.len - view.len;
|
||||
for(size_t i = from; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvRFind(strview_t ctx, char c, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 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.len);
|
||||
|
||||
if(view.len > ctx.len) {
|
||||
from -= view.len;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(memcmp(buf, view.buf, view.len) == 0) return (buf - ctx.buf);
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from) {
|
||||
if(ctx.len < view.len) return SIZE_MAX;
|
||||
for(size_t i = from; i < ctx.len; ++i) {
|
||||
for(size_t j = 0; j < view.len; ++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.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
for(size_t j = 0; j < view.len; ++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.len - 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.len; ++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.len) {
|
||||
from = ctx.len - 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.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(!strvContains(view, *buf)) {
|
||||
return buf - ctx.buf;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
#ifdef STR_TESTING
|
||||
#include <stdio.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
void strTest(void) {
|
||||
str_t s;
|
||||
debug("== testing init =================");
|
||||
{
|
||||
s = strInit();
|
||||
printf("%s %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
s = strInitStr("hello world");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
uint8_t buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
|
||||
s = strInitBuf((char *)buf, sizeof(buf));
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing view =================");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
strview_t view = strGetView(&s);
|
||||
printf("\"%.*s\" %zu\n", (int)view.len, view.buf, view.len);
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing begin/end ============");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
char *beg = strBegin(&s);
|
||||
char *end = strEnd(&s);
|
||||
printf("[ ");
|
||||
for(; beg < end; ++beg) {
|
||||
printf("%c ", *beg);
|
||||
}
|
||||
printf("]\n");
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing back/isempty =========");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
printf("[ ");
|
||||
while(!strIsEmpty(&s)) {
|
||||
printf("%c ", strBack(&s));
|
||||
strPop(&s);
|
||||
}
|
||||
printf("]\n");
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing append ===============");
|
||||
{
|
||||
s = strInitStr("hello ");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strAppend(&s, "world");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strAppendView(&s, strvInit(", how is it "));
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
uint8_t buf[] = { 'g', 'o', 'i', 'n', 'g' };
|
||||
strAppendBuf(&s, (char*)buf, sizeof(buf));
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strAppendChars(&s, '?', 2);
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing push/pop =============");
|
||||
{
|
||||
s = strInit();
|
||||
str_t s2 = strInitStr("hello world");
|
||||
|
||||
printf("%-14s %-14s\n", "s", "s2");
|
||||
printf("----------------------------\n");
|
||||
while(!strIsEmpty(&s2)) {
|
||||
printf("%-14s %-14s\n", s.buf, s2.buf);
|
||||
strPush(&s, strPop(&s2));
|
||||
}
|
||||
printf("%-14s %-14s\n", s.buf, s2.buf);
|
||||
|
||||
strFree(&s);
|
||||
strFree(&s2);
|
||||
}
|
||||
debug("== testing swap =================");
|
||||
{
|
||||
s = strInitStr("hello");
|
||||
str_t s2 = strInitStr("world");
|
||||
printf("%-8s %-8s\n", "s", "s2");
|
||||
printf("----------------\n");
|
||||
printf("%-8s %-8s\n", s.buf, s2.buf);
|
||||
strSwap(&s, &s2);
|
||||
printf("%-8s %-8s\n", s.buf, s2.buf);
|
||||
|
||||
strFree(&s);
|
||||
strFree(&s2);
|
||||
}
|
||||
debug("== testing substr ===============");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
printf("s: %s\n", s.buf);
|
||||
|
||||
printf("-- string\n");
|
||||
str_t s2 = strSubstr(&s, 0, 5);
|
||||
printf("0..5: \"%s\"\n", s2.buf);
|
||||
strFree(&s2);
|
||||
|
||||
s2 = strSubstr(&s, 5, SIZE_MAX);
|
||||
printf("6..SIZE_MAX: \"%s\"\n", s2.buf);
|
||||
strFree(&s2);
|
||||
|
||||
printf("-- view\n");
|
||||
strview_t v = strSubview(&s, 0, 5);
|
||||
printf("0..5: \"%.*s\"\n", (int)v.len, v.buf);
|
||||
v = strSubview(&s, 5, SIZE_MAX);
|
||||
printf("6..SIZE_MAX: \"%.*s\"\n", (int)v.len, v.buf);
|
||||
|
||||
strFree(&s);
|
||||
}
|
||||
|
||||
strFree(&s);
|
||||
}
|
||||
#endif
|
||||
188
str.h
188
str.h
|
|
@ -1,63 +1,127 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "slice.h"
|
||||
#include "strview.h"
|
||||
|
||||
#define STR_TESTING
|
||||
|
||||
// typedef struct {
|
||||
// char *buf;
|
||||
// size_t len;
|
||||
// } str_t;
|
||||
|
||||
typedef slice_t(char *) str_t;
|
||||
|
||||
str_t strInit(void);
|
||||
str_t strInitStr(const char *cstr);
|
||||
str_t strInitView(strview_t view);
|
||||
str_t strInitBuf(const char *buf, size_t len);
|
||||
|
||||
void strFree(str_t *ctx);
|
||||
|
||||
str_t strMove(str_t *ctx);
|
||||
str_t strDup(str_t ctx);
|
||||
|
||||
strview_t strGetView(str_t *ctx);
|
||||
|
||||
char *strBegin(str_t *ctx);
|
||||
char *strEnd(str_t *ctx);
|
||||
|
||||
char strBack(str_t *ctx);
|
||||
|
||||
bool strIsEmpty(str_t *ctx);
|
||||
|
||||
void strAppend(str_t *ctx, const char *str);
|
||||
void strAppendStr(str_t *ctx, str_t str);
|
||||
void strAppendView(str_t *ctx, strview_t view);
|
||||
void strAppendBuf(str_t *ctx, const char *buf, size_t len);
|
||||
void strAppendChars(str_t *ctx, char c, size_t count);
|
||||
|
||||
void strPush(str_t *ctx, char c);
|
||||
char strPop(str_t *ctx);
|
||||
|
||||
void strSwap(str_t *ctx, str_t *other);
|
||||
|
||||
// if len == SIZE_MAX, copies until end
|
||||
str_t strSubstr(str_t *ctx, size_t pos, size_t len);
|
||||
// if len == SIZE_MAX, returns until end
|
||||
strview_t strSubview(str_t *ctx, size_t pos, size_t len);
|
||||
|
||||
void strLower(str_t *ctx);
|
||||
str_t strToLower(str_t ctx);
|
||||
|
||||
#ifdef STR_TESTING
|
||||
void strTest(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <wchar.h>
|
||||
// #include "strview.h"
|
||||
|
||||
#define STRV_NOT_FOUND SIZE_MAX
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
size_t len;
|
||||
} str_t;
|
||||
|
||||
typedef struct {
|
||||
const char *buf;
|
||||
size_t len;
|
||||
} strview_t;
|
||||
|
||||
// == STR_T ========================================================
|
||||
|
||||
str_t strInit(void);
|
||||
str_t strInitStr(const char *cstr);
|
||||
str_t strInitView(strview_t view);
|
||||
str_t strInitBuf(const char *buf, size_t len);
|
||||
|
||||
str_t strFromWCHAR(const wchar_t *src, size_t len);
|
||||
wchar_t *strToWCHAR(str_t ctx);
|
||||
|
||||
void strFree(str_t *ctx);
|
||||
|
||||
str_t strMove(str_t *ctx);
|
||||
str_t strDup(str_t ctx);
|
||||
|
||||
strview_t strGetView(str_t *ctx);
|
||||
|
||||
char *strBegin(str_t *ctx);
|
||||
char *strEnd(str_t *ctx);
|
||||
|
||||
char strBack(str_t *ctx);
|
||||
|
||||
bool strIsEmpty(str_t *ctx);
|
||||
|
||||
void strAppend(str_t *ctx, const char *str);
|
||||
void strAppendStr(str_t *ctx, str_t str);
|
||||
void strAppendView(str_t *ctx, strview_t view);
|
||||
void strAppendBuf(str_t *ctx, const char *buf, size_t len);
|
||||
void strAppendChars(str_t *ctx, char c, size_t count);
|
||||
|
||||
void strPush(str_t *ctx, char c);
|
||||
char strPop(str_t *ctx);
|
||||
|
||||
void strSwap(str_t *ctx, str_t *other);
|
||||
|
||||
void strReplace(str_t *ctx, char from, char to);
|
||||
|
||||
// if len == SIZE_MAX, copies until end
|
||||
str_t strSubstr(str_t *ctx, size_t pos, size_t len);
|
||||
// if len == SIZE_MAX, returns until end
|
||||
strview_t strSubview(str_t *ctx, size_t pos, size_t len);
|
||||
|
||||
void strLower(str_t *ctx);
|
||||
str_t strToLower(str_t ctx);
|
||||
|
||||
#ifdef STR_TESTING
|
||||
void strTest(void);
|
||||
#endif
|
||||
|
||||
// == STRVIEW_T ====================================================
|
||||
|
||||
strview_t strvInit(const char *cstr);
|
||||
strview_t strvInitStr(str_t str);
|
||||
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);
|
||||
|
||||
str_t strvCopy(strview_t ctx);
|
||||
str_t strvCopyN(strview_t ctx, 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
|
||||
1054
strstream.c
1054
strstream.c
File diff suppressed because it is too large
Load diff
199
strstream.h
199
strstream.h
|
|
@ -1,97 +1,104 @@
|
|||
#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(void);
|
||||
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"
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "str.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(void);
|
||||
str_ostream_t ostrInitLen(size_t initial_alloc);
|
||||
str_ostream_t ostrInitStr(const char *buf, size_t len);
|
||||
|
||||
void ostrFree(str_ostream_t *ctx);
|
||||
void ostrClear(str_ostream_t *ctx);
|
||||
str_t ostrMove(str_ostream_t *ctx);
|
||||
|
||||
char ostrBack(str_ostream_t *ctx);
|
||||
str_t ostrAsStr(str_ostream_t *ctx);
|
||||
strview_t ostrAsView(str_ostream_t *ctx);
|
||||
|
||||
void ostrReplace(str_ostream_t *ctx, char from, char to);
|
||||
|
||||
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 ostrAppendchar(str_ostream_t *ctx, char 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
|
||||
250
strview.c
250
strview.c
|
|
@ -1,250 +0,0 @@
|
|||
#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, cstr ? strlen(cstr) : 0);
|
||||
}
|
||||
|
||||
strview_t strvInitLen(const char *buf, size_t size) {
|
||||
strview_t view;
|
||||
view.buf = buf;
|
||||
view.len = size;
|
||||
return view;
|
||||
}
|
||||
|
||||
char strvFront(strview_t ctx) {
|
||||
return ctx.buf[0];
|
||||
}
|
||||
|
||||
char strvBack(strview_t ctx) {
|
||||
return ctx.buf[ctx.len - 1];
|
||||
}
|
||||
|
||||
const char *strvBegin(strview_t *ctx) {
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
const char *strvEnd(strview_t *ctx) {
|
||||
return ctx->buf + ctx->len;
|
||||
}
|
||||
|
||||
bool strvIsEmpty(strview_t ctx) {
|
||||
return ctx.len == 0;
|
||||
}
|
||||
|
||||
void strvRemovePrefix(strview_t *ctx, size_t n) {
|
||||
ctx->buf += n;
|
||||
ctx->len -= n;
|
||||
}
|
||||
|
||||
void strvRemoveSuffix(strview_t *ctx, size_t n) {
|
||||
ctx->len -= n;
|
||||
}
|
||||
|
||||
size_t strvCopy(strview_t ctx, char **buf) {
|
||||
*buf = malloc(ctx.len + 1);
|
||||
memcpy(*buf, ctx.buf, ctx.len);
|
||||
(*buf)[ctx.len] = '\0';
|
||||
return ctx.len;
|
||||
}
|
||||
|
||||
size_t strvCopyN(strview_t ctx, char **buf, size_t count, size_t from) {
|
||||
size_t sz = ctx.len + 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.len + 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.len) from = ctx.len - len;
|
||||
size_t sz = ctx.len - from;
|
||||
return strvInitLen(ctx.buf + from, min(len, sz));
|
||||
}
|
||||
|
||||
int strvCompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
return memcmp(ctx.buf, other.buf, ctx.len);
|
||||
}
|
||||
|
||||
int strvICompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
for(size_t i = 0; i < ctx.len; ++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.len < view.len) return false;
|
||||
return memcmp(ctx.buf, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvEndsWith(strview_t ctx, char c) {
|
||||
return strvBack(ctx) == c;
|
||||
}
|
||||
|
||||
bool strvEndsWithView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvContains(strview_t ctx, char c) {
|
||||
for(size_t i = 0; i < ctx.len; ++i) {
|
||||
if(ctx.buf[i] == c) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool strvContainsView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
size_t end = ctx.len - view.len;
|
||||
for(size_t i = 0; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t strvFind(strview_t ctx, char c, size_t from) {
|
||||
for(size_t i = from; i < ctx.len; ++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.len < view.len) return SIZE_MAX;
|
||||
size_t end = ctx.len - view.len;
|
||||
for(size_t i = from; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvRFind(strview_t ctx, char c, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 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.len);
|
||||
|
||||
if(view.len > ctx.len) {
|
||||
from -= view.len;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(memcmp(buf, view.buf, view.len) == 0) return (buf - ctx.buf);
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from) {
|
||||
if(ctx.len < view.len) return SIZE_MAX;
|
||||
for(size_t i = from; i < ctx.len; ++i) {
|
||||
for(size_t j = 0; j < view.len; ++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.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
for(size_t j = 0; j < view.len; ++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.len - 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.len; ++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.len) {
|
||||
from = ctx.len - 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.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(!strvContains(view, *buf)) {
|
||||
return buf - ctx.buf;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
71
strview.h
71
strview.h
|
|
@ -1,71 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "slice.h"
|
||||
|
||||
#define STRV_NOT_FOUND SIZE_MAX
|
||||
|
||||
// typedef struct {
|
||||
// const char *buf;
|
||||
// size_t size;
|
||||
// } strview_t;
|
||||
|
||||
typedef slice_t(const char *) 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
|
||||
180
tracelog.c
180
tracelog.c
|
|
@ -1,91 +1,91 @@
|
|||
#include "tracelog.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
|
||||
#endif
|
||||
|
||||
#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 "\033[30m"
|
||||
#define RED "\033[31m"
|
||||
#define GREEN "\033[32m"
|
||||
#define YELLOW "\033[33m"
|
||||
#define BLUE "\033[22;34m"
|
||||
#define MAGENTA "\033[35m"
|
||||
#define CYAN "\033[36m"
|
||||
#define WHITE "\033[37m"
|
||||
#define RESET "\033[0m"
|
||||
#define BOLD "\033[1m"
|
||||
#endif
|
||||
|
||||
#define MAX_TRACELOG_MSG_LENGTH 1024
|
||||
|
||||
bool use_newline = true;
|
||||
|
||||
void traceLog(LogLevel level, const char *fmt, ...) {
|
||||
char buffer[MAX_TRACELOG_MSG_LENGTH];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
const char *beg;
|
||||
switch (level) {
|
||||
case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break;
|
||||
case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break;
|
||||
case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break;
|
||||
case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break;
|
||||
case LogError: beg = BOLD RED "[ERROR]: " RESET; break;
|
||||
case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
size_t offset = strlen(beg);
|
||||
strncpy(buffer, beg, sizeof(buffer));
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
#ifdef TLOG_VS
|
||||
OutputDebugStringA(buffer);
|
||||
if(use_newline) OutputDebugStringA("\n");
|
||||
#else
|
||||
printf("%s", buffer);
|
||||
if(use_newline) puts("");
|
||||
#endif
|
||||
|
||||
#ifndef TLOG_DONT_EXIT_ON_FATAL
|
||||
if (level == LogFatal) exit(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void traceUseNewline(bool newline) {
|
||||
use_newline = newline;
|
||||
#include "tracelog.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
|
||||
#endif
|
||||
|
||||
#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 "\033[30m"
|
||||
#define RED "\033[31m"
|
||||
#define GREEN "\033[32m"
|
||||
#define YELLOW "\033[33m"
|
||||
#define BLUE "\033[22;34m"
|
||||
#define MAGENTA "\033[35m"
|
||||
#define CYAN "\033[36m"
|
||||
#define WHITE "\033[37m"
|
||||
#define RESET "\033[0m"
|
||||
#define BOLD "\033[1m"
|
||||
#endif
|
||||
|
||||
#define MAX_TRACELOG_MSG_LENGTH 1024
|
||||
|
||||
bool use_newline = true;
|
||||
|
||||
void traceLog(LogLevel level, const char *fmt, ...) {
|
||||
char buffer[MAX_TRACELOG_MSG_LENGTH];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
const char *beg;
|
||||
switch (level) {
|
||||
case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break;
|
||||
case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break;
|
||||
case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break;
|
||||
case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break;
|
||||
case LogError: beg = BOLD RED "[ERROR]: " RESET; break;
|
||||
case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break;
|
||||
default: beg = ""; break;
|
||||
}
|
||||
|
||||
size_t offset = strlen(beg);
|
||||
strncpy(buffer, beg, sizeof(buffer));
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
#ifdef TLOG_VS
|
||||
OutputDebugStringA(buffer);
|
||||
if(use_newline) OutputDebugStringA("\n");
|
||||
#else
|
||||
printf("%s", buffer);
|
||||
if(use_newline) puts("");
|
||||
#endif
|
||||
|
||||
#ifndef TLOG_DONT_EXIT_ON_FATAL
|
||||
if (level == LogFatal) exit(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void traceUseNewline(bool newline) {
|
||||
use_newline = newline;
|
||||
}
|
||||
61
tracelog.h
61
tracelog.h
|
|
@ -1,31 +1,32 @@
|
|||
#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
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal
|
||||
} LogLevel;
|
||||
|
||||
void traceLog(LogLevel level, const char *fmt, ...);
|
||||
void traceUseNewline(bool use_newline);
|
||||
|
||||
#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"
|
||||
#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
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal
|
||||
} LogLevel;
|
||||
|
||||
void traceLog(LogLevel level, const char *fmt, ...);
|
||||
void traceUseNewline(bool use_newline);
|
||||
|
||||
#define tall(...) traceLog(LogAll, __VA_ARGS__)
|
||||
#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
|
||||
603
vec.h
Normal file
603
vec.h
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Basic usage:
|
||||
* #define T int
|
||||
* // optional, if not defined all the names will be longer, e.g.
|
||||
* // vec_int_t v = vec_intInit();
|
||||
* #define VEC_SHORT_NAME veci
|
||||
* #include <vec.h> // undefines T
|
||||
* [...]
|
||||
* veci_t v = veciInit();
|
||||
* veciPush(&v, 10);
|
||||
* veciEraseAt(&v, 0);
|
||||
* veciFree(&v);
|
||||
*/
|
||||
|
||||
typedef int( *vec_cmp_fn_t)(const void *, const void *);
|
||||
|
||||
#ifndef T
|
||||
#error "Must define T before including vec.h"
|
||||
#endif
|
||||
|
||||
#define VEC_CAT(a, b) a ## b
|
||||
#define VEC_PASTE(a, b) VEC_CAT(a, b)
|
||||
#define TYPE(prefix, type) VEC_PASTE(VEC_PASTE(prefix, _), VEC_PASTE(type, _t))
|
||||
#define ITER(prefix, type) VEC_PASTE(VEC_PASTE(prefix, _), VEC_PASTE(type, _it_t))
|
||||
|
||||
#ifdef VEC_SHORT_NAME
|
||||
#define VEC_T VEC_PASTE(VEC_SHORT_NAME, _t)
|
||||
#define VEC_IT_T VEC_PASTE(VEC_SHORT_NAME, _it_t)
|
||||
#define VEC VEC_SHORT_NAME
|
||||
#else
|
||||
#define VEC_T TYPE(vec, T)
|
||||
#define VEC_IT_T ITER(vec, T)
|
||||
#define VEC VEC_PASTE(vec, VEC_PASTE(_, T))
|
||||
#endif
|
||||
|
||||
#define FUN(postfix) VEC_PASTE(VEC, postfix)
|
||||
|
||||
#ifndef VEC_NO_DECLARATION
|
||||
|
||||
typedef struct {
|
||||
T *buf;
|
||||
size_t size;
|
||||
size_t allocated;
|
||||
} VEC_T;
|
||||
|
||||
#define vec_foreach(type, name, it, vec) \
|
||||
for(type *it = VEC_PASTE(name, Beg)(&vec); it < VEC_PASTE(name, End)(&vec); ++it)
|
||||
|
||||
VEC_T FUN(Init)(void);
|
||||
VEC_T FUN(InitArr)(T *arr, size_t len);
|
||||
void FUN(Free)(VEC_T *ctx);
|
||||
|
||||
VEC_T FUN(Move)(VEC_T *ctx);
|
||||
VEC_T FUN(Copy)(VEC_T *ctx);
|
||||
|
||||
T *FUN(Beg)(VEC_T *ctx);
|
||||
T *FUN(End)(VEC_T *ctx);
|
||||
T *FUN(Back)(VEC_T *ctx);
|
||||
bool FUN(Empty)(VEC_T *ctx);
|
||||
|
||||
void FUN(Realloc)(VEC_T *ctx, size_t needed);
|
||||
void FUN(Reserve)(VEC_T *ctx, size_t newsize);
|
||||
void FUN(ShrinkToFit)(VEC_T *ctx);
|
||||
|
||||
void FUN(Clear)(VEC_T *ctx);
|
||||
void FUN(ClearZero)(VEC_T *ctx);
|
||||
|
||||
void FUN(InsertAt)(VEC_T *ctx, size_t index, T val);
|
||||
void FUN(InsertAfter)(VEC_T *ctx, T *it, T val);
|
||||
void FUN(InsertBefore)(VEC_T *ctx, T *it, T val);
|
||||
|
||||
void FUN(InsertAtSw)(VEC_T *ctx, size_t index, T val);
|
||||
void FUN(InsertAfterSw)(VEC_T *ctx, T *it, T val);
|
||||
void FUN(InsertBeforeSw)(VEC_T *ctx, T *it, T val);
|
||||
|
||||
// swaps with last
|
||||
void FUN(Erase)(VEC_T *ctx, T *it);
|
||||
void FUN(EraseAt)(VEC_T *ctx, size_t index);
|
||||
// moves whole array back one
|
||||
void FUN(EraseMv)(VEC_T *ctx, T *it);
|
||||
void FUN(EraseAtMv)(VEC_T *ctx, size_t index);
|
||||
|
||||
void FUN(Push)(VEC_T *ctx, T val);
|
||||
void FUN(PushRef)(VEC_T *ctx, T *val);
|
||||
T FUN(Pop)(VEC_T *ctx);
|
||||
|
||||
void FUN(Resize)(VEC_T *ctx, size_t newcount, T val);
|
||||
void FUN(ResizeRef)(VEC_T *ctx, size_t newcount, T *val);
|
||||
|
||||
void FUN(EraseWhen)(VEC_T *ctx, T val);
|
||||
|
||||
typedef bool (*TYPE(vec_erase_fn, T))(const T *val, void *udata);
|
||||
void FUN(EraseIf)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata);
|
||||
void FUN(EraseIfMv)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata);
|
||||
|
||||
// typedef int (*TYPE(vec_sort_fn, T))(const T *a, const T *b);
|
||||
void FUN(Sort)(VEC_T *ctx, vec_cmp_fn_t cmp);
|
||||
|
||||
#endif // VEC_NO_DECLARATION
|
||||
|
||||
#ifndef VEC_NO_IMPLEMENTATION
|
||||
|
||||
inline
|
||||
VEC_T FUN(Init)(void) {
|
||||
return (VEC_T){
|
||||
.buf = NULL,
|
||||
.size = 0,
|
||||
.allocated = 0
|
||||
};
|
||||
}
|
||||
|
||||
inline
|
||||
VEC_T FUN(InitArr)(T *arr, size_t len) {
|
||||
VEC_T v = FUN(Init)();
|
||||
FUN(Realloc)(&v, len);
|
||||
memcpy(v.buf, arr, len * sizeof(T));
|
||||
v.size = len;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Free)(VEC_T *ctx) {
|
||||
free(ctx->buf);
|
||||
ctx->buf = NULL;
|
||||
ctx->size = 0;
|
||||
ctx->allocated = 0;
|
||||
}
|
||||
|
||||
inline
|
||||
VEC_T FUN(Move)(VEC_T *ctx) {
|
||||
VEC_T mv = *ctx;
|
||||
ctx->buf = NULL;
|
||||
FUN(Free)(ctx);
|
||||
return mv;
|
||||
}
|
||||
|
||||
inline
|
||||
VEC_T FUN(Copy)(VEC_T *ctx) {
|
||||
VEC_T cp = FUN(Init)();
|
||||
if(ctx->buf) {
|
||||
FUN(Reserve)(&cp, ctx->size);
|
||||
memcpy(cp.buf, ctx->buf, ctx->size * sizeof(T));
|
||||
cp.size = ctx->size;
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
inline
|
||||
T *FUN(Beg)(VEC_T *ctx) {
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
inline
|
||||
T *FUN(End)(VEC_T *ctx) {
|
||||
return ctx->buf + ctx->size;
|
||||
}
|
||||
|
||||
inline
|
||||
T *FUN(Back)(VEC_T *ctx) {
|
||||
return ctx->buf ? &ctx->buf[ctx->size - 1] : NULL;
|
||||
}
|
||||
|
||||
inline
|
||||
bool FUN(Empty)(VEC_T *ctx) {
|
||||
return ctx->buf ? ctx->size == 0 : true;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Realloc)(VEC_T *ctx, size_t needed) {
|
||||
if((ctx->size + needed) >= ctx->allocated) {
|
||||
ctx->allocated = (ctx->allocated * 2) + needed;
|
||||
ctx->buf = (T *)realloc(ctx->buf, ctx->allocated * sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Reserve)(VEC_T *ctx, size_t newsize) {
|
||||
if(ctx->allocated < newsize) {
|
||||
ctx->allocated = newsize;
|
||||
ctx->buf = (T *)realloc(ctx->buf, ctx->allocated * sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(ShrinkToFit)(VEC_T *ctx) {
|
||||
ctx->allocated = ctx->size;
|
||||
ctx->buf = (T *)realloc(ctx->buf, ctx->allocated * sizeof(T));
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Clear)(VEC_T *ctx) {
|
||||
ctx->size = 0;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(ClearZero)(VEC_T *ctx) {
|
||||
ctx->size = 0;
|
||||
memset(ctx->buf, 0, ctx->allocated * sizeof(T));
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertAt)(VEC_T *ctx, size_t index, T val) {
|
||||
FUN(Realloc)(ctx, 1);
|
||||
for(size_t i = ctx->size; i > index; --i) {
|
||||
ctx->buf[i] = ctx->buf[i - 1];
|
||||
}
|
||||
ctx->buf[index] = val;
|
||||
ctx->size++;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertAfter)(VEC_T *ctx, T *it, T val) {
|
||||
size_t index = it - ctx->buf;
|
||||
// insertAt acts as insertBefore, so we just add 1
|
||||
FUN(InsertAt)(ctx, index + 1, val);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertBefore)(VEC_T *ctx, T *it, T val) {
|
||||
size_t index = it - ctx->buf;
|
||||
FUN(InsertAt)(ctx, index, val);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertAtSw)(VEC_T *ctx, size_t index, T val) {
|
||||
FUN(Realloc)(ctx, 1);
|
||||
ctx->buf[ctx->size] = ctx->buf[index];
|
||||
ctx->buf[index] = val;
|
||||
ctx->size++;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertAfterSw)(VEC_T *ctx, T *it, T val) {
|
||||
size_t index = it - ctx->buf;
|
||||
// insertAt acts as insertBefore, so we just add 1
|
||||
FUN(InsertAtSw)(ctx, index + 1, val);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertBeforeSw)(VEC_T *ctx, T *it, T val) {
|
||||
size_t index = it - ctx->buf;
|
||||
FUN(InsertAtSw)(ctx, index, val);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Erase)(VEC_T *ctx, T *it) {
|
||||
size_t index = it - ctx->buf;
|
||||
FUN(EraseAt)(ctx, index);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(EraseAt)(VEC_T *ctx, size_t index) {
|
||||
ctx->size--;
|
||||
ctx->buf[index] = ctx->buf[ctx->size];
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(EraseMv)(VEC_T *ctx, T *it) {
|
||||
size_t index = it - ctx->buf;
|
||||
FUN(EraseAtMv)(ctx, index);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(EraseAtMv)(VEC_T *ctx, size_t index) {
|
||||
ctx->size--;
|
||||
for(size_t i = index; i < ctx->size; ++i) {
|
||||
ctx->buf[i] = ctx->buf[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Push)(VEC_T *ctx, T val) {
|
||||
FUN(Realloc)(ctx, 1);
|
||||
ctx->buf[ctx->size] = val;
|
||||
ctx->size++;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(PushRef)(VEC_T *ctx, T *val) {
|
||||
FUN(Realloc)(ctx, 1);
|
||||
ctx->buf[ctx->size] = *val;
|
||||
ctx->size++;
|
||||
}
|
||||
|
||||
inline
|
||||
T FUN(Pop)(VEC_T *ctx) {
|
||||
ctx->size--;
|
||||
return ctx->buf[ctx->size];
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Resize)(VEC_T *ctx, size_t newcount, T val) {
|
||||
if(newcount <= ctx->size) {
|
||||
ctx->size = newcount;
|
||||
return;
|
||||
}
|
||||
FUN(Realloc)(ctx, newcount - ctx->size);
|
||||
for(size_t i = ctx->size; i < newcount; ++i) {
|
||||
ctx->buf[i] = val;
|
||||
}
|
||||
ctx->size = newcount;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(ResizeRef)(VEC_T *ctx, size_t newcount, T *val) {
|
||||
if(newcount <= ctx->size) {
|
||||
ctx->size = newcount;
|
||||
}
|
||||
FUN(Realloc)(ctx, newcount - ctx->size);
|
||||
if(val) {
|
||||
for(size_t i = ctx->size; i < newcount; ++i) {
|
||||
ctx->buf[i] = *val;
|
||||
}
|
||||
}
|
||||
ctx->size = newcount;
|
||||
}
|
||||
|
||||
#ifndef VEC_DISABLE_ERASE_WHEN
|
||||
|
||||
inline
|
||||
void FUN(EraseWhen)(VEC_T *ctx, T val) {
|
||||
for(size_t i = 0; i < ctx->size; ++i) {
|
||||
if(ctx->buf[i] == val) {
|
||||
FUN(EraseAt)(ctx, i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
inline
|
||||
void FUN(EraseIf)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata) {
|
||||
for(size_t i = 0; i < ctx->size; ++i) {
|
||||
if(cmp(&ctx->buf[i], udata)) {
|
||||
FUN(EraseAt)(ctx, i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(EraseIfMv)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata) {
|
||||
for(size_t i = 0; i < ctx->size; ++i) {
|
||||
if(cmp(&ctx->buf[i], udata)) {
|
||||
FUN(EraseAtMv)(ctx, i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Sort)(VEC_T *ctx, vec_cmp_fn_t cmp) {
|
||||
qsort(ctx->buf, ctx->size, sizeof(T), cmp);
|
||||
}
|
||||
|
||||
#endif // VEC_NO_IMPLEMENTATION
|
||||
|
||||
#undef FUN
|
||||
#undef VEC
|
||||
#undef VEC_T
|
||||
#undef TYPE
|
||||
#undef VEC_SHORT_NAME
|
||||
#undef T
|
||||
#undef VEC_DISABLE_ERASE_WHEN
|
||||
#undef VEC_NO_DECLARATION
|
||||
#undef VEC_NO_IMPLEMENTATION
|
||||
|
||||
#if 0
|
||||
// vec.h testing:
|
||||
|
||||
#define T int
|
||||
#define VEC_SHORT_NAME veci
|
||||
#include <vec.h>
|
||||
#define foreach(it, vec) vec_foreach(int, veci, it, vec)
|
||||
|
||||
#define T char
|
||||
#define VEC_SHORT_NAME vecc
|
||||
#include <vec.h>
|
||||
#include <tracelog.h>
|
||||
|
||||
|
||||
#define PRINTVALS(v, s) \
|
||||
printf("{ "); \
|
||||
for(size_t i = 0; i < v.size; ++i) \
|
||||
printf(s " ", v.buf[i]); \
|
||||
printf("}\n");
|
||||
|
||||
#define PRINTVEC(v, s) \
|
||||
printf(#v ": {\n"); \
|
||||
printf("\tsize: %zu\n", v.size); \
|
||||
printf("\tallocated: %zu\n", v.allocated); \
|
||||
printf("\tvalues:"); \
|
||||
PRINTVALS(v, s); \
|
||||
printf("}\n"); \
|
||||
|
||||
#define PRINTVECI(v) PRINTVEC(v, "%d")
|
||||
#define PRINTVALSI(v) PRINTVALS(v, "%d")
|
||||
|
||||
bool veciEraseEven(const int *val, void *udata) {
|
||||
return *val % 2 == 0;
|
||||
}
|
||||
|
||||
bool veciEraseHigher(const int *val, void *udata) {
|
||||
return *val > 8;
|
||||
}
|
||||
|
||||
int main() {
|
||||
debug("== TESTING INIT ===========================");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
PRINTVECI(v);
|
||||
veciFree(&v);
|
||||
|
||||
v = veciInitArr((int[]){1, 2, 3, 4}, 4);
|
||||
veciPush(&v, 25);
|
||||
veciPush(&v, 13);
|
||||
PRINTVECI(v);
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING MOVE/COPY ======================");
|
||||
{
|
||||
veci_t a = veciInitArr((int[]){1, 2, 3, 4}, 4);
|
||||
info("before move");
|
||||
PRINTVECI(a);
|
||||
info("after move");
|
||||
veci_t b = veciMove(&a);
|
||||
PRINTVECI(a);
|
||||
PRINTVECI(b);
|
||||
veciFree(&a);
|
||||
veciFree(&b);
|
||||
|
||||
a = veciInitArr((int[]){1, 2, 3, 4}, 4);
|
||||
b = veciCopy(&a);
|
||||
info("copied");
|
||||
PRINTVECI(a);
|
||||
PRINTVECI(b);
|
||||
info("modified b");
|
||||
b.buf[2] = 9;
|
||||
PRINTVECI(a);
|
||||
PRINTVECI(b);
|
||||
veciFree(&a);
|
||||
veciFree(&b);
|
||||
}
|
||||
debug("== TESTING BACK ===========================");
|
||||
{
|
||||
vecc_t v = veccInitArr((char[]){'a', 'b', 'c', 'd', 'e', 'f'}, 6);
|
||||
|
||||
PRINTVEC(v, "%c");
|
||||
info("The last character is '%c'.", *veccBack(&v));
|
||||
|
||||
veccFree(&v);
|
||||
}
|
||||
debug("== TESTING EMPTY ==========================");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
info("Initially, vecEmpty(): %s", veciEmpty(&v) ? "true":"false");
|
||||
veciPush(&v, 42);
|
||||
info("After adding elements, vecEmpty(): %s", veciEmpty(&v) ? "true":"false");
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING RESERVE/SHRINK_TO_FIT/CLEAR ====");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
info("Default capacity: %zu", v.allocated);
|
||||
veciResize(&v, 100, 0);
|
||||
info("100 elements: %zu", v.allocated);
|
||||
veciResize(&v, 50, 0);
|
||||
info("after resize(50): %zu", v.allocated);
|
||||
veciShrinkToFit(&v);
|
||||
info("after shrinkToFit(): %zu", v.allocated);
|
||||
veciClear(&v);
|
||||
info("after clear(): %zu", v.allocated);
|
||||
veciShrinkToFit(&v);
|
||||
info("after shrinkToFit(): %zu", v.allocated);
|
||||
for(int i = 1000; i < 1300; ++i) {
|
||||
veciPush(&v, i);
|
||||
}
|
||||
info("after adding 300 elements: %zu", v.allocated);
|
||||
veciShrinkToFit(&v);
|
||||
info("after shrinkToFit(): %zu", v.allocated);
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING ITERATORS ======================");
|
||||
{
|
||||
veci_t v = veciInitArr((int[]){1, 2, 3, 4, 5}, 5);
|
||||
PRINTVECI(v);
|
||||
info("foreach:");
|
||||
for(int *it = veciBeg(&v); it != veciEnd(&v); ++it) {
|
||||
printf("\t*it: %d\n", *it);
|
||||
}
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING INSERT =========================");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
info("init with 3 100");
|
||||
veciResize(&v, 3, 100);
|
||||
PRINTVALSI(v);
|
||||
info("insert 200 at 0");
|
||||
veciInsertAt(&v, 0, 200);
|
||||
PRINTVALSI(v);
|
||||
info("insert 300 before beginning");
|
||||
veciInsertBefore(&v, veciBeg(&v), 300);
|
||||
PRINTVALSI(v);
|
||||
info("insert 400 after beg + 1");
|
||||
veciInsertAfter(&v, veciBeg(&v) + 1, 400);
|
||||
PRINTVALSI(v);
|
||||
info("insert swap 500 at 3");
|
||||
veciInsertAtSw(&v, 3, 500);
|
||||
PRINTVALSI(v);
|
||||
info("insert swap 600 before beg");
|
||||
veciInsertBeforeSw(&v, veciBeg(&v), 600);
|
||||
PRINTVALSI(v);
|
||||
info("insert swap 700 after end - 4");
|
||||
veciInsertAfterSw(&v, veciEnd(&v) - 4, 700);
|
||||
PRINTVALSI(v);
|
||||
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING ERASE ==========================");
|
||||
{
|
||||
veci_t v = veciInitArr((int[]){0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 10);
|
||||
info("initializing with number from 0 to 9");
|
||||
PRINTVALSI(v);
|
||||
info("erasing beginning");
|
||||
veciErase(&v, veciBeg(&v));
|
||||
PRINTVALSI(v);
|
||||
info("erasing index 5");
|
||||
veciEraseAt(&v, 5);
|
||||
PRINTVALSI(v);
|
||||
info("erasing mv end - 3");
|
||||
veciEraseMv(&v, veciEnd(&v) - 3);
|
||||
PRINTVALSI(v);
|
||||
info("erasing mv index 1");
|
||||
veciEraseAtMv(&v, 1);
|
||||
PRINTVALSI(v);
|
||||
info("erasing mv all even numbers");
|
||||
veciEraseIfMv(&v, veciEraseEven, NULL);
|
||||
PRINTVALSI(v);
|
||||
info("erasing numbers higher than 8");
|
||||
veciEraseIf(&v, veciEraseHigher, NULL);
|
||||
PRINTVALSI(v);
|
||||
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING CLEAR_ZERO =====================");
|
||||
{
|
||||
veci_t v = veciInitArr((int[]){0, 1, 2, 3, 4, 5}, 6);
|
||||
info("initialized from 0 to 6");
|
||||
PRINTVECI(v);
|
||||
info("clearZero");
|
||||
size_t oldsize = v.size;
|
||||
veciClearZero(&v);
|
||||
for(int i = 0; i < oldsize; ++i) {
|
||||
printf("\t%d > %d\n", i, v.buf[i]);
|
||||
}
|
||||
}
|
||||
debug("== TESTING PUSH/PUSH_REF ==================");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
|
||||
info("pushing 10");
|
||||
veciPush(&v, 10);
|
||||
int value = 50;
|
||||
info("pushing reference to value: %d", value);
|
||||
veciPushRef(&v, &value);
|
||||
|
||||
info("vector holds: ");
|
||||
printf("> ");
|
||||
foreach(it, v) {
|
||||
printf("%d ", *it);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING POP ============================");
|
||||
{
|
||||
vecc_t v = veccInitArr("hello world!", 12);
|
||||
info("initialized with %.*s", (int)v.size, v.buf);
|
||||
while(!veccEmpty(&v)) {
|
||||
printf("pooped '%c'\n", veccPop(&v));
|
||||
printf("> [%.*s]\n", (int)v.size, v.buf);
|
||||
}
|
||||
veccFree(&v);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue