From 8b8be9421ae0c44c933d9f4fa287cce77e0b1811 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Fri, 6 Oct 2023 03:13:46 +0000 Subject: [PATCH] Initial import. --- .gitignore | 6 ++ Arena.cc | 195 ++++++++++++++++++++++++++++++++++++++++++++++++ Arena.h | 34 +++++++++ CMakeLists.txt | 11 +++ Makefile | 44 +++++++++++ Phonebook.cc | 82 ++++++++++++++++++++ Phonebook.h | 38 ++++++++++ TLV.cc | 141 ++++++++++++++++++++++++++++++++++ TLV.h | 44 +++++++++++ test_fixtures.h | 20 +++++ tlv_test.cc | 120 +++++++++++++++++++++++++++++ 11 files changed, 735 insertions(+) create mode 100644 .gitignore create mode 100644 Arena.cc create mode 100644 Arena.h create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 Phonebook.cc create mode 100644 Phonebook.h create mode 100644 TLV.cc create mode 100644 TLV.h create mode 100644 test_fixtures.h create mode 100644 tlv_test.cc 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; +}