From: Dan Sheppard Date: Tue, 22 Apr 2025 14:58:22 +0000 (+0100) Subject: Test infrastructure. X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~dans/git?a=commitdiff_plain;h=dc9db0eb965892deaee5731d5112fd5a510b4f50;p=coquet.git Test infrastructure. --- diff --git a/Makefile b/Makefile index dc3a9e1..9f57f48 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,55 @@ SRCDIR := src BINDIR := bin -OBJDIR := obj +OBJDIR := obj/main +TESTOBJDIR := obj/test + +SRCFILES := unix.c util.c coquet.c superblock.c sha2.c testvfs.c -TARGET := $(BINDIR)/coquet -SRCFILES := main.c unix.c util.c coquet.c superblock.c sha2.c SRC := $(addprefix $(SRCDIR)/,$(SRCFILES)) +DEPSRC := $(wildcard $(SRCDIR)/*.c) + +DEPS = $(DEPSRC:$(SRCDIR)/%.c=$(OBJDIR)/%.d) OBJ := $(SRC:$(SRCDIR)/%.c=$(OBJDIR)/%.o) -DEPS := $(SRCFILES:%.c=$(OBJDIR)/%.d) -# TARGET = coquet +TESTDEPS = $(DEPSRC:$(SRCDIR)/%.c=$(TESTOBJDIR)/%.d) +TESTOBJ := $(SRC:$(SRCDIR)/%.c=$(TESTOBJDIR)/%.o) + LIBS = CC = gcc CFLAGS = -g -Wall --std=c99 -.PHONY: default all clean +.PHONY: default all clean test -default: $(TARGET) +default: $(BINDIR)/coquet-cli $(BINDIR)/coquet-test all: default +obj/test: + -mkdir obj/test + $(OBJDIR)/%.o: $(SRCDIR)/%.c $(CC) $(CFLAGS) -c $< -o $@ +$(TESTOBJDIR)/%.o: $(SRCDIR)/%.c obj/test + $(CC) $(CFLAGS) -DCOQUET_TEST -c $< -o $@ + $(OBJDIR)/%.d: $(SRCDIR)/%.c $(CC) -MM -MP -MT $(@:.d=.o) $< > $@ -$(TARGET): $(OBJ) - $(CC) $(OBJ) -Wall $(LIBS) -o $@ +$(TESTOBJDIR)/%.d: $(SRCDIR)/%.c obj/test + $(CC) -DCOQUET_TEST -MM -MP -MT $(@:.d=.o) $< > $@ + +$(BINDIR)/coquet-cli: $(OBJ) $(OBJDIR)/main.o + $(CC) $(OBJ) $(OBJDIR)/main.o -Wall $(LIBS) -o $@ + +$(BINDIR)/coquet-test: $(TESTOBJ) $(TESTOBJDIR)/test.o + $(CC) $(TESTOBJ) $(TESTOBJDIR)/test.o -Wall $(LIBS) -o $@ # .PRECIOUS: $(TARGET) $(OBJECTS) clean: -rm -f $(OBJDIR)/* - -rm -f $(TARGET) + -rm -f $(TESTOBJDIR)/* + -rm -f $(BINDIR)/* --include $(SRC:$(SRCDIR)/%.c=$(OBJDIR)/%.d) +-include $(DEPS) +-include $(TESTDEPS) diff --git a/src/coquet.c b/src/coquet.c index 220b976..0da0fcb 100644 --- a/src/coquet.c +++ b/src/coquet.c @@ -4,14 +4,12 @@ #include "constants.h" #include "coquet.h" #include "util.h" - -extern vfs_t vfs_unix; +#include "vfs.h" /* VFS null is used as a placeholder prior to initialisation, so that * subsequent tidies can be clean. */ vfs_t vfs_null = { - .make = NULL, .start = NULL, .get_error_text = NULL, .lock = NULL, @@ -22,19 +20,52 @@ vfs_t vfs_null = { .finish = NULL }; +/* Teardown and free vfs layer. */ +static int finish_vfs(coquet_t *cq) { + int r; + + if(cq->vfs_funcs.finish != NULL) { + r = (cq->vfs_funcs.finish)(cq->vfs_data); + if(r != COQUET_RET_OK) { + return r; + } + cq->vfs_funcs.finish = NULL; + } + + return COQUET_RET_OK; +} + +#ifdef COQUET_TEST +static int testvfs_wrap(coquet_t *cq) { + cq->vfs_data = testvfs_make(&cq->vfs_funcs,cq->vfs_data); + if(cq->vfs_data == NULL) + return COQUET_RET_HEAPERR; + + cq->vfs_funcs = vfs_test; + + return COQUET_RET_OK; +} +#endif + /* Allocate and initialise vfs layer. */ static int init_vfs(coquet_t *cq, char *basename) { int r; cq->vfs_funcs = vfs_unix; - - /* Allocate memory */ - cq->vfs_data = (cq->vfs_funcs.make)(); + cq->vfs_data = unix_make(); if(cq->vfs_data == NULL) { cq->vfs_funcs = vfs_null; return COQUET_RET_HEAPERR; } +#ifdef COQUET_TEST + r = testvfs_wrap(cq); + if(r != COQUET_RET_OK) { + finish_vfs(cq); + return r; + } +#endif + /* Initialise */ r = (cq->vfs_funcs.start)(cq->vfs_data,basename); if(r != COQUET_RET_OK) { @@ -59,21 +90,6 @@ int coquet_init(coquet_t *cq, char * basename) { return COQUET_RET_OK; } -/* Teardown and free vfs layer. */ -static int finish_vfs(coquet_t *cq) { - int r; - - if(cq->vfs_funcs.make != NULL) { - r = (cq->vfs_funcs.finish)(cq->vfs_data); - if(r != COQUET_RET_OK) { - return r; - } - cq->vfs_funcs.make = NULL; - } - - return COQUET_RET_OK; -} - /* Tear down whole library. Idempotent to allow crash recovery. */ int coquet_finish(coquet_t *cq) { int r; diff --git a/src/coquet.h b/src/coquet.h index 4cd6547..9118fac 100644 --- a/src/coquet.h +++ b/src/coquet.h @@ -29,4 +29,9 @@ int coquet_finish(coquet_t *cq); */ char * coquet_error_string(coquet_t *cq, int error); +#ifdef COQUET_TEST +void test_bail(coquet_t * cq, int error_code); +void test_unlink(char *path); +#endif + #endif diff --git a/src/hmac.c b/src/hmac.c deleted file mode 100644 index e69de29..0000000 diff --git a/src/superblock.c b/src/superblock.c index ad95312..3eaa86e 100644 --- a/src/superblock.c +++ b/src/superblock.c @@ -13,10 +13,11 @@ * +-------------------+ * | magic | 0 8 * | version | 8 8 - * | sb_hash | 16 32 - * | sb_serial | 48 8 + * | global_iv | 16 32 + * | sb_hash | 48 32 + * | sb_serial | 80 8 * +-------------------+ - * | unused | 56 968 + * | unused | 88 936 * +-------------------+ * | current | 1024 512 * +-------------------+ @@ -27,11 +28,10 @@ * * offset length * +-------------------+ - * | global_iv | 0 32 - * | block_size | 32 1 - * | nursery_size | 33 1 + * | block_size | 0 1 + * | nursery_size | 1 1 * +-------------------+ - * | unused | 34 477 + * | unused | 2 509 * +-------------------+ * | reserved | 511 1 * +-------------------+ @@ -42,8 +42,9 @@ */ struct cq_super default_super = { - .current = {{0,},12,10}, - .desired = {{0,},12,10}, + .current = {12,10}, + .desired = {12,10}, + .global_iv = {0,}, .sb_serial = 0, .sb_hash = {0,}, .version = COQUET_VERSION, @@ -54,9 +55,8 @@ struct cq_super default_super = { * at this point, so don't trust the data yet. */ static void extract_config(uint8_t *data, struct cq_super_config *sc) { - memcpy(sc->global_iv,data,HASH_LEN); - sc->block_size = be_decode(data+32,1); - sc->nursery_size = be_decode(data+33,1); + sc->block_size = be_decode(data,1); + sc->nursery_size = be_decode(data+1,1); } static int extract_half(uint8_t *data, struct cq_super *super) { @@ -70,18 +70,20 @@ static int extract_half(uint8_t *data, struct cq_super *super) { return 0; } super->version = be_decode(data+8,8); - /* All versions are fine for now. */ + /* (all versions are fine for now). */ + + memcpy(super->global_iv,data+16,GLOBAL_IV_LEN); memset(super->sb_hash,0,HASH_LEN); - super->sb_serial = be_decode(data+48,8); + super->sb_serial = be_decode(data+80,8); extract_config(data+1024,&(super->current)); extract_config(data+1536,&(super->desired)); /* verify */ - memcpy(hash,data+16,HASH_LEN); - memset(data+16,0,HASH_LEN); + memcpy(hash,data+48,HASH_LEN); + memset(data+48,0,HASH_LEN); sha2_init_hmac(&sha2, SHA2_VARIETY_512_256, - super->current.global_iv, GLOBAL_IV_LEN); + super->global_iv, GLOBAL_IV_LEN); sha2_more(&sha2,data,HALF_BYTES); sha2_finish(&sha2,cmp,HASH_LEN); if(memcmp(hash,cmp,HASH_LEN)) { @@ -95,11 +97,10 @@ static int super_init(coquet_t *cq, struct cq_super *super) { int r; r = (cq->vfs_funcs.random)(cq->vfs_data, - super->current.global_iv, GLOBAL_IV_LEN); + super->global_iv, GLOBAL_IV_LEN); if(r != COQUET_RET_OK) { return r; } - memcpy(super->desired.global_iv,super->current.global_iv,GLOBAL_IV_LEN); return COQUET_RET_OK; } @@ -156,28 +157,28 @@ static int lock_super(coquet_t *cq, int mode, bool_t wait) { static void intract_config(uint8_t *data, struct cq_super_config *sc) { - memcpy(data,sc->global_iv,HASH_LEN); - be_encode(data+32,sc->block_size,1); - be_encode(data+33,sc->nursery_size,1); + be_encode(data,sc->block_size,1); + be_encode(data+1,sc->nursery_size,1); } static void intract(uint8_t *data, struct cq_super *super, uint64_t serial) { struct sha2_ctx_t sha2; /* intract */ - memset(data,0,HASH_LEN); + memset(data,0,HALF_BYTES); be_encode(data,COQUET_MAGIC,8); be_encode(data+8,COQUET_VERSION,8); - be_encode(data+48,serial,8); + memcpy(data+16,super->global_iv,GLOBAL_IV_LEN); + be_encode(data+80,serial,8); intract_config(data+1024,&(super->current)); intract_config(data+1536,&(super->desired)); /* set hash */ sha2_init_hmac(&sha2, SHA2_VARIETY_512_256, - super->current.global_iv, GLOBAL_IV_LEN); + super->global_iv, GLOBAL_IV_LEN); sha2_more(&sha2,data,HALF_BYTES); - sha2_finish(&sha2,data+16,HASH_LEN); + sha2_finish(&sha2,data+48,HASH_LEN); } static int super_write(coquet_t *cq, struct cq_super *super, @@ -224,3 +225,37 @@ int cq_super_save(coquet_t *cq, struct cq_super *super, bool_t wait) { return ret; } + +#ifdef COQUET_TEST + +#include + +void test_superblock() { + coquet_t cq; + int r; + struct cq_super super; + + printf("testing superblock\n"); + r = coquet_init(&cq,"tmp/test"); + test_bail(&cq,r); + + test_unlink("tmp/test.coquet"); + r = (cq.vfs_funcs.open)(cq.vfs_data,COQUET_FILE_MAIN,COQUET_CMODE_EITHER); + test_bail(&cq,r); + + testvfs_fakerandom(cq.vfs_data,0xA5); + r = cq_super_load(&cq,&super,1); + test_bail(&cq,r); + + cq_super_save(&cq,&super,1); + test_bail(&cq,r); + testvfs_fakerandom(cq.vfs_data,-1); + + r = (cq.vfs_funcs.close)(cq.vfs_data,COQUET_FILE_MAIN); + test_bail(&cq,r); + + r = coquet_finish(&cq); + test_bail(&cq,r); +} + +#endif diff --git a/src/superblock.h b/src/superblock.h index b9426c4..314e75c 100644 --- a/src/superblock.h +++ b/src/superblock.h @@ -9,7 +9,6 @@ #define SUPERBLOCK_LAST_OFFSET (SUPER_BYTES-1) struct cq_super_config { - uint8_t global_iv[GLOBAL_IV_LEN]; uint8_t block_size; /* log2 bytes */ uint8_t nursery_size; /* log2 blocks */ }; @@ -18,6 +17,7 @@ struct cq_super { struct cq_super_config current, desired; uint64_t version; uint64_t sb_serial; + uint8_t global_iv[GLOBAL_IV_LEN]; uint8_t sb_hash[32]; /* HMAC SHA512-256 using global_iv */ int from_b; /* true if loaded from b, false if from a */ }; @@ -27,4 +27,8 @@ struct cq_super { int cq_super_save(coquet_t *cq, struct cq_super *super, bool_t wait); int cq_super_load(coquet_t *cq, struct cq_super *super, bool_t create); +#ifdef COQUET_TEST +void test_superblock(); +#endif + #endif \ No newline at end of file diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..bbc8db7 --- /dev/null +++ b/src/test.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include "superblock.h" + +void test_unlink(char *path) { + int r; + + r = unlink(path); + if(r && errno != ENOENT) { + fprintf(stderr,"Couldn't unlink '%s': %s\n",path,strerror(errno)); + exit(1); + } +} + +void test_bail(coquet_t * cq, int error_code) { + char *msg; + + if(error_code == COQUET_RET_OK) { + return; + } + msg = coquet_error_string(cq,error_code); + if(!msg) { + fprintf(stderr,"Heap exhausted"); + exit(1); + } + fprintf(stderr,"%s\n",msg); + free(msg); + coquet_finish(cq); + exit(1); +} + +int main() { + printf("testing\n"); + test_superblock(); + return 0; +} diff --git a/src/testvfs.c b/src/testvfs.c new file mode 100644 index 0000000..fb7b7c6 --- /dev/null +++ b/src/testvfs.c @@ -0,0 +1,128 @@ +#define _GNU_SOURCE +#include +#include +#include +#include "vfs.h" +#include "coquet.h" + +struct test_data { + int fake_random; + vfs_t vfs_funcs; + void *vfs_data; +}; + +void * testvfs_make(vfs_t *vfs_funcs, void *vfs_data) { + struct test_data *td; + + td = malloc(sizeof(struct test_data)); + if(td == NULL) + return NULL; + td->fake_random = -1; + td->vfs_funcs = *vfs_funcs; + td->vfs_data = vfs_data; + return td; +} + +static int test_start(void * vfs_data, char *filename) { + struct test_data * td = (struct test_data *)vfs_data; + + printf("Using test VFS\n"); + return (td->vfs_funcs.start)(td->vfs_data,filename); +} + +static int test_finish(void *vfs_data) { + struct test_data * td = (struct test_data *)vfs_data; + int r; + + r = (td->vfs_funcs.finish)(td->vfs_data); + if(r != COQUET_RET_OK) + return r; + free(td); + return COQUET_RET_OK; +} + +static char * test_get_error_text(void * vfs_data) { + struct test_data * td = (struct test_data *)vfs_data; + + return (td->vfs_funcs.get_error_text)(td->vfs_data); +} + +static int test_open(void * vfs_data, int which_file, + int mode) { + struct test_data * td = (struct test_data *)vfs_data; + + return (td->vfs_funcs.open)(td->vfs_data,which_file,mode); +} + +static int test_close(void * vfs_data, int which_file) { + struct test_data * td = (struct test_data *)vfs_data; + + return (td->vfs_funcs.close)(td->vfs_data,which_file); +} + +static int test_write(void * vfs_data, int which_file, uint8_t * data, + off_t offset, uint64_t length) { + struct test_data * td = (struct test_data *)vfs_data; + + return (td->vfs_funcs.write)(td->vfs_data, + which_file, data, offset, length); +} + +static int test_read(void * vfs_data, int which_file, uint8_t * data, + uint64_t offset, uint64_t length) { + struct test_data * td = (struct test_data *)vfs_data; + + return (td->vfs_funcs.read)(td->vfs_data, + which_file, data, offset, length); +} + +static int test_lock(void * vfs_data, int which_lock, int lock_mode, + bool_t wait) { + struct test_data * td = (struct test_data *)vfs_data; + + return (td->vfs_funcs.lock)(td->vfs_data, + which_lock, lock_mode, wait); +} + +static int test_delete(void *vfs_data, int which_file) { + struct test_data * td = (struct test_data *)vfs_data; + + return (td->vfs_funcs.delete)(td->vfs_data, which_file); +} + +static int test_sync(void * vfs_data, int which_file, bool_t data_only) { + struct test_data * td = (struct test_data *)vfs_data; + + return (td->vfs_funcs.sync)(td->vfs_data, which_file, data_only); +} + +static int test_random(void * vfs_data, uint8_t *out, int len) { + struct test_data * td = (struct test_data *)vfs_data; + + if(td->fake_random < 0) { + return (td->vfs_funcs.random)(td->vfs_data, out, len); + } else { + memset(out,td->fake_random,len); + return COQUET_RET_OK; + } +} + +void testvfs_fakerandom(void * vfs_data, int setting) { + struct test_data * td = (struct test_data *)vfs_data; + + td->fake_random = setting; +} + +vfs_t vfs_test = { + .start = test_start, + .get_error_text = test_get_error_text, + .lock = test_lock, + .open = test_open, + .close = test_close, + .write = test_write, + .read = test_read, + .finish = test_finish, + .delete = test_delete, + .sync = test_sync, + .random = test_random +}; diff --git a/src/unix.c b/src/unix.c index ab48a01..87bdd74 100644 --- a/src/unix.c +++ b/src/unix.c @@ -1,5 +1,4 @@ #define _GNU_SOURCE -#include /* During testing */ #include #include #include @@ -36,10 +35,12 @@ static void set_error(struct unix_data * pd, char *error, } } -static void * unix_make() { +void * unix_make() { struct unix_data * pd; pd = malloc(sizeof(struct unix_data)); + if(pd == NULL) + return NULL; pd->seen_error = 0; pd->error_text = NULL; pd->filename = pd->dirname_buf = NULL; @@ -455,7 +456,6 @@ static int unix_random(void * vfs_data, uint8_t *out, int len) { } vfs_t vfs_unix = { - .make = unix_make, .start = unix_start, .get_error_text = unix_get_error_text, .lock = unix_lock, diff --git a/src/vfs.h b/src/vfs.h index 6d677dd..005b484 100644 --- a/src/vfs.h +++ b/src/vfs.h @@ -9,14 +9,6 @@ * COQUET_RET_VFS or COQUET_RET_HEAPERR unless docs here indicate otherwise. */ typedef struct vfs { - - /* Returning vfs_data payload which will then be - * owned by caller until passed to finish on shutdown. Initialise - * is then called. This is split in two to allow errors to be reported - * in the regular way by init. May be null if heap allocation failed. - */ - void * (*make)(); - /* Initialise VFS layer. Use basename as filepath in all calls. * basename = a/b/foo => a/b/foo.coquet, a/b/foo.coquet.tmp, etc. * basename is still owned by caller. @@ -89,4 +81,17 @@ typedef struct vfs { } vfs_t; +extern vfs_t vfs_unix; + +void * unix_make(); + +#ifdef COQUET_TEST + +extern vfs_t vfs_test; + +void * testvfs_make(vfs_t *vfs_funcs, void *vfs_data); +void testvfs_fakerandom(void * vfs_data, int setting); + +#endif /* COQUET_TEST */ + #endif