commit 8b8be9421ae0c44c933d9f4fa287cce77e0b1811 Author: Kyle Isom Date: Fri Oct 6 03:13:46 2023 +0000 Initial import. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a75a318 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.a +*.o +*.bin +build +cmake-build-* +tlv_test diff --git a/Arena.cc b/Arena.cc new file mode 100644 index 0000000..ce75e95 --- /dev/null +++ b/Arena.cc @@ -0,0 +1,195 @@ +#include +#include +#include + +#if defined(__linux__) +#include +#include +#include +#include +#include +#endif + +#include "Arena.h" + + +#define ARENA_UNINIT 0 +#define ARENA_STATIC 1 +#define ARENA_ALLOC 2 +#define ARENA_MMAP 3 + +#define PROT_RW PROT_READ|PROT_WRITE + + +int +new_arena(Arena &arena, uint8_t *mem, size_t size) +{ + arena.store = mem; + arena.size = size; + arena.type = ARENA_STATIC; + return 0; +} + + +int +alloc_new_arena(Arena &arena, size_t size) +{ + if (arena.size > 0) { + if (arena_destroy(arena) != 0) { + return -1; + } + } + + arena.type = ARENA_ALLOC; + arena.size = size; + arena.store = (uint8_t *)calloc(sizeof(uint8_t), size); + if (arena.store == NULL) { + return -1; + } + + return 0; +} + + +#if defined(__linux__) +int +mmap_arena(Arena &arena, int fd, size_t size) +{ + if (arena.size > 0) { + if (arena_destroy(arena) != 0) { + return -1; + } + } + + arena.type = ARENA_MMAP; + arena.size = size; + arena.store = (uint8_t *)mmap(NULL, size, PROT_RW, MAP_SHARED, fd, 0); + if ((void *)arena.store == MAP_FAILED) { + return -1; + } + arena.fd = fd; + return 0; +} + + +int +open_arena(Arena &arena, const char *path) +{ + struct stat st; + + if (arena.size > 0) { + if (arena_destroy(arena) != 0) { + return -1; + } + } + + if (stat(path, &st) != 0) { + return -1; + } + + arena.fd = open(path, O_RDWR); + if (arena.fd == -1) { + return -1; + } + + return mmap_arena(arena, arena.fd, (size_t)st.st_size); +} + + +int +create_arena(Arena &arena, const char *path, size_t size, mode_t mode) +{ + int fd = 0; + + if (arena.size > 0) { + if (arena_destroy(arena) != 0) { + return -1; + } + } + + fd = open(path, O_WRONLY|O_CREAT, mode); + if (fd == -1) { + return -1; + } + + if (ftruncate(fd, size) == -1) { + return -1; + } + + close(fd); + + return open_arena(arena, path); +} +#endif + + +void +arena_clear(Arena &arena) +{ + if (arena.size == 0) { + return; + } + + memset(arena.store, 0, arena.size); +} + + +int +arena_destroy(Arena &arena) +{ + if (arena.type == ARENA_UNINIT) { + return 0; + } + + switch (arena.type) { + case ARENA_STATIC: + break; + case ARENA_ALLOC: + free(arena.store); + break; + case ARENA_MMAP: + if (munmap(arena.store, arena.size) == -1) { + return -1; + } + + if (close(arena.fd) == -1) { + return -1; + } + + arena.fd = 0; + break; + default: + abort(); + return -1; + } + + arena.type = ARENA_UNINIT; + arena.size = 0; + arena.store = NULL; + return 0; +} + + +int +write_arena(Arena &arena, const char *path) +{ + FILE *arenaFile = NULL; + int retc = -1; + + arenaFile = fopen(path, "w"); + if (arenaFile == NULL) { + return -1; + } + + if (fwrite(arena.store, sizeof(*arena.store), arena.size, + arenaFile) == arena.size) { + retc = 0; + } + + if (fclose(arenaFile) != 0) { + return -1; + } + + return retc; +} + diff --git a/Arena.h b/Arena.h new file mode 100644 index 0000000..c77fb36 --- /dev/null +++ b/Arena.h @@ -0,0 +1,34 @@ +#ifndef KIMODEM_ARENA_H +#define KIMODEM_ARENA_H + + +#include +#include +#include + + +typedef struct { + uint8_t *store; + size_t size; + int fd; + uint8_t type; +} Arena; + + +int new_arena(Arena &, uint8_t *, size_t); +int alloc_new_arena(Arena &, size_t); +#if defined(__linux__) +int mmap_arena(Arena &, int); /* arena will own fd */ +int create_arena(Arena &arena, const char *path, size_t size, mode_t mode); +int open_arena(Arena &, const char *, size_t); +#endif + +void arena_clear(Arena &); +int arena_destroy(Arena &); /* dispose of any memory used by arena */ + +/* DANGER: if arena is file backed (mmap or open), DO NOT WRITE TO THE + * BACKING FILE! */ +int write_arena(const char *); + + +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a1023eb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.25) +project(klib CXX) + +set(CMAKE_CXX_STANDARD 14) + +add_library(klib STATIC + Arena.cc + TLV.cc) + +add_executable(tlv_test tlv_test.cc) +target_link_libraries(tlv_test klib) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1596009 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +TARGET := klib.a +TESTS := tlv_test +HEADERS := $(wildcard *.h) +SOURCES := $(wildcard *.cc) +OBJS := Arena.o Phonebook.o TLV.o + +CXX := clang++ +CXXFLAGS := -g -std=c++14 -Werror -Wall + +.PHONY: all +all: $(TARGET) $(TESTS) tags run-tests + +tags: $(HEADERS) $(SOURCES) + ctags $(HEADERS) $(SOURCES) + +$(TARGET): $(OBJS) + $(AR) rcs $@ $(OBJS) + +tlv_test: tlv_test.o $(TARGET) + $(CXX) -o $@ $(CXXFLAGS) $@.o $(TARGET) + +.PHONY: print-% +print-%: ; @echo '$(subst ','\'',$*=$($*))' + +klib.a: $(OBJS) + +%.o: %.cc + $(CXX) -o $@ -c $(CXXFLAGS) $< + +.PHONY: clean +clean: + # build outputs + rm -f $(TARGET) $(TESTS) *.o + + # test miscellaneous + rm -f core core.* tags arena_test.bin + +.PHONY: run-tests +run-tests: $(TESTS) + for testbin in $(TESTS); \ + do \ + echo "./$${testbin}" ; \ + ./$${testbin}; \ + done diff --git a/Phonebook.cc b/Phonebook.cc new file mode 100644 index 0000000..56b16da --- /dev/null +++ b/Phonebook.cc @@ -0,0 +1,82 @@ +#include +#include +#include "Phonebook.h" + +bool +Phonebook::lookup(const char *key, uint8_t klen, TLV::Record &res) +{ + res.Tag = this->ktag; + uint8_t *cursor = TLV::find_tag(this->arena, NULL, res); + + while (cursor != NULL) { + if ((klen == res.Len) && + (memcmp(res.Val, key, klen) == 0)) { + TLV::read_from_memory(res, cursor); + if (res.Tag != this->vtag) { + abort(); + } + return true; + } + cursor = TLV::find_tag(this->arena, cursor, res); + } + + return false; + +} + + +int +Phonebook::set(const char *key, uint8_t klen, char *val, uint8_t vlen) +{ + TLV::Record rec; + uint8_t *cursor = NULL; + + set_record(rec, this->ktag, klen, key); + cursor = this->seek(key, klen); + if (cursor != NULL) { + TLV::delete_record(this->arena, cursor); + TLV::delete_record(this->arena, cursor); + } + + cursor = TLV::write_to_memory(this->arena, NULL, rec); + if (cursor == NULL) { + return -1; + } + + set_record(rec, this->vtag, vlen, val); + if (TLV::write_to_memory(this->arena, NULL, rec) == NULL) { + return -1; + } + + return 0; +} + + + +uint8_t * +Phonebook::seek(const char *key, uint8_t klen) +{ + TLV::Record rec; + + rec.Tag = this->ktag; + uint8_t *cursor = TLV::find_tag(this->arena, NULL, rec); + + while (cursor != NULL) { + if ((klen == rec.Len) && + (memcmp(rec.Val, key, klen) == 0)) { + return cursor; + } + cursor = TLV::skip_record(rec, cursor); + } + + return NULL; +} + + +bool +Phonebook::has(const char *key, uint8_t klen) +{ + return this->seek(key, klen) != NULL; +} + + diff --git a/Phonebook.h b/Phonebook.h new file mode 100644 index 0000000..55282ed --- /dev/null +++ b/Phonebook.h @@ -0,0 +1,38 @@ +#ifndef KLIB_PHONEBOOK_H +#define KLIB_PHONEBOOK_H + + +#include "Arena.h" +#include "TLV.h" + + +#define PHONEBOOK_KEY_TAG 1 +#define PHONEBOOK_VAL_TAG 2 + + + +class Phonebook { +public: + Phonebook(Arena &arena) : + arena(arena), + ktag(PHONEBOOK_KEY_TAG), + vtag(PHONEBOOK_VAL_TAG) {} ; + Phonebook(Arena &arena, uint8_t kt, uint8_t vt) : + arena(arena), + ktag(kt), + vtag(vt) {}; + + bool lookup(const char *key, uint8_t klen, TLV::Record &res); + int set(const char *key, uint8_t klen, char *val, uint8_t vlen); + bool has(const char *key, uint8_t klen); + +private: + uint8_t *seek(const char *key, uint8_t klen); + + Arena &arena; + uint8_t ktag; + uint8_t vtag; +}; + + +#endif diff --git a/TLV.cc b/TLV.cc new file mode 100644 index 0000000..f0b852d --- /dev/null +++ b/TLV.cc @@ -0,0 +1,141 @@ +#include +#include "TLV.h" + + +#define REC_SIZE(x) ((std::size_t)x.Len + 2) + + +namespace TLV { + + +static bool +space_available(Arena &arena, uint8_t *cursor, uint8_t len) +{ + uintptr_t remaining = 0; + + if (cursor == NULL) { + return false; + } + + remaining = (uintptr_t)cursor - (uintptr_t)arena.store; + remaining = arena.size - remaining; + return ((size_t)remaining >= ((size_t)len+2)); +} + + +uint8_t * +write_to_memory(Arena &arena, uint8_t *cursor, Record &rec) +{ + // If cursor is NULL, the user needs us to select an empty + // slot for the record. If we can't find one, that's an + // error. + // + // If, however, the user gives us a cursor, we'll trust it + // (though space_available will sanity check that cursor). + if (cursor == NULL) { + cursor = find_empty(arena, cursor); + if (cursor == NULL) { + return NULL; + } + } + + if (!space_available(arena, cursor, rec.Len)) { + return NULL; + } + + memcpy(cursor, &rec, REC_SIZE(rec)); + cursor = skip_record(rec, cursor); + + return cursor; +} + + +void +set_record(Record &rec, uint8_t tag, uint8_t len, const char *val) +{ + rec.Tag = tag; + rec.Len = len; + memcpy(rec.Val, val, len); +} + + +void +read_from_memory(Record &rec, uint8_t *cursor) +{ + rec.Tag = cursor[0]; + rec.Len = cursor[1]; + memcpy(rec.Val, cursor+2, rec.Len); +} + + +/* + * returns a pointer to memory where the record was found, + * e.g. find_tag(...)[0] is the tag of the found record. + */ +uint8_t * +find_tag(Arena &arena, uint8_t *cursor, Record &rec) +{ + uint8_t tag, len; + + if (cursor == NULL) { + cursor = arena.store; + } + + while ((tag = cursor[0]) != rec.Tag) { + len = cursor[1]; + if (!space_available(arena, cursor, len)) { + return NULL; + } + cursor += len; + cursor += 2; + } + + if (tag != rec.Tag) { + return NULL; + } + + if (tag != TAG_EMPTY) { + read_from_memory(rec, cursor); + cursor = skip_record(rec, cursor); + } + return cursor; +} + + +uint8_t * +find_empty(Arena &arena, uint8_t *cursor) { + Record rec; + + rec.Tag = TAG_EMPTY; + return find_tag(arena, cursor, rec); +} + + + +uint8_t * +skip_record(Record &rec, uint8_t *cursor) +{ + return (uint8_t *)((uintptr_t)cursor + rec.Len + 2); +} + + +void +delete_record(Arena &arena, uint8_t *cursor) +{ + // + if (cursor == NULL) { + return; + } + + uintptr_t len = cursor[1] + 2; + uintptr_t stop = (uintptr_t)arena.size - (uintptr_t)cursor; + + stop -= len; + + for (uintptr_t i = (uintptr_t)cursor; i < stop; i++) { + cursor[i] = cursor[i+len]; + }; +} + + +} // namespace TLV diff --git a/TLV.h b/TLV.h new file mode 100644 index 0000000..e3c5cd1 --- /dev/null +++ b/TLV.h @@ -0,0 +1,44 @@ +#ifndef KIMODEM_TLV_H +#define KIMODEM_TLV_H + +#include + +#include "Arena.h" + + +#ifndef TLV_MAX_LEN +#define TLV_MAX_LEN 253 +#endif + + +#define TAG_EMPTY 0 + + +namespace TLV { + + +struct Record { + uint8_t Tag; + uint8_t Len; + char Val[TLV_MAX_LEN]; +}; + + +uint8_t *write_to_memory(Arena &, uint8_t *, Record &); +void read_from_memory(Record &, uint8_t *); +void set_record(Record &, uint8_t, uint8_t, const char *); +void delete_record(Arena &, uint8_t *); + +/* + * returns a pointer to memory where the record was found, + * e.g. find_tag(...)[0] is the tag of the found record. + */ +uint8_t *find_tag(Arena &, uint8_t *, Record &); +uint8_t *find_empty(Arena &, uint8_t *); +uint8_t *skip_record(Record &, uint8_t *); + + +} // namespace TLV + + +#endif diff --git a/test_fixtures.h b/test_fixtures.h new file mode 100644 index 0000000..70150d7 --- /dev/null +++ b/test_fixtures.h @@ -0,0 +1,20 @@ +#ifndef KLIB_TEST_FIXTURES_H +#define KLIB_TEST_FIXTURES_H + + +#define ARENA_SIZE 128 +#define ARENA_FILE "arena_test.bin" + +/* strlen=13 */ +#define TEST_STR1 "Hello, world" +#define TEST_STRLEN1 13 +#define TEST_STR2 "Bye, world!!" +#define TEST_STRLEN2 13 +#define TEST_STR3 "Hello, arena" +#define TEST_STRLEN3 13 + +/* strlen 35 */ +#define TEST_STR4 "How is a raven like a writing desk?" +#define TEST_STRLEN4 35 + +#endif diff --git a/tlv_test.cc b/tlv_test.cc new file mode 100644 index 0000000..54f113a --- /dev/null +++ b/tlv_test.cc @@ -0,0 +1,120 @@ +#include +#include +#include + +#include "Arena.h" +#include "TLV.h" + +#include "test_fixtures.h" + + +static uint8_t arena_buffer[ARENA_SIZE]; + + +static bool +cmp_record(TLV::Record &a, TLV::Record &b) +{ + if (a.Tag != b .Tag) { + return false; + } + + if (a.Len != b.Len) { + return false; + } + + if (memcmp(a.Val, b.Val, a.Len) != 0) { + return false; + } + + return true; +} + + +bool +tlv_test_suite(Arena &backend) +{ + TLV::Record rec1, rec2, rec3, rec4; + uint8_t *cursor = NULL; + + TLV::set_record(rec1, 1, TEST_STRLEN1, TEST_STR1); + TLV::set_record(rec2, 2, TEST_STRLEN2, TEST_STR2); + TLV::set_record(rec3, 1, TEST_STRLEN4, TEST_STR4); + rec4.Tag = 1; + + assert(TLV::write_to_memory(backend, cursor, rec1) != NULL); + assert((cursor = TLV::write_to_memory(backend, cursor, rec2)) != NULL); + assert(TLV::write_to_memory(backend, cursor, rec3) != NULL); + cursor = NULL; + + // the cursor should point at the next record, + // and rec4 should contain the same data as rec1. + cursor = TLV::find_tag(backend, cursor, rec4); + assert(cursor != NULL); + assert(cursor != backend.store); + assert(cmp_record(rec1, rec4)); + + cursor = TLV::find_tag(backend, cursor, rec4); + assert(cursor != NULL); + assert(cmp_record(rec3, rec4)); + + TLV::set_record(rec4, 3, TEST_STRLEN3, TEST_STR3); + assert(TLV::write_to_memory(backend, NULL, rec4)); + + TLV::delete_record(backend, cursor); + assert(cursor[0] == TAG_EMPTY); + + return true; +} + +bool +run_suite(Arena &backend, const char *label) +{ + std::cout << "running test suite " << label << ": "; + if (!tlv_test_suite(backend)) { + std::cout << "FAILED" << std::endl; + return false; + } + std::cout << "OK" << std::endl; + + std::cout << "\tdestroying arena: "; + if (arena_destroy(backend) != 0) { + std::cout << "FAILED" << std::endl; + return false; + } + + std::cout << "OK" << std::endl; + return true; +} + + +int +main(int argc, const char *argv[]) +{ + Arena arena_static; + Arena arena_mem; + Arena arena_file; + + std::cout << "TESTPROG: " << argv[0] << std::endl; + + if (-1 == new_arena(arena_static, arena_buffer, ARENA_SIZE)) { + abort(); + } else if (!run_suite(arena_static, "arena_static")) { + abort(); + } + + if (-1 == create_arena(arena_file, ARENA_FILE, ARENA_SIZE, 0644)) { + abort(); + } else if (!run_suite(arena_file, "arena_file")) { + abort(); + } + + if (-1 == alloc_new_arena(arena_mem, ARENA_SIZE)) { + abort(); + } else if (!run_suite(arena_mem, "arena_mem")) { + abort(); + } + + + std::cout << "OK" << std::endl; + return 0; +}