diff --git a/platform/posix/btstack_tlv_posix.c b/platform/posix/btstack_tlv_posix.c new file mode 100644 index 000000000..646058c8d --- /dev/null +++ b/platform/posix/btstack_tlv_posix.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2017 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY MATTHIAS RINGWALD AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#define __BTSTACK_FILE__ "btstack_tlv_posix.c" + +#include "btstack_tlv.h" +#include "btstack_tlv_posix.h" +#include "btstack_debug.h" +#include "btstack_util.h" +#include "btstack_debug.h" +#include "string.h" + +#include +#include + + +// Header: +// - Magic: 'BTstack' +// - Status: +// - bits 765432: reserved +// - bits 10: epoch + +// Entries +// - Tag: 32 bit +// - Len: 32 bit +// - Value: Len in bytes + +#define BTSTACK_TLV_HEADER_LEN 8 +static const char * btstack_tlv_header_magic = "BTstack"; + +#define DUMMY_SIZE 4 +typedef struct tlv_entry { + void * next; + uint32_t tag; + uint32_t len; + uint8_t value[DUMMY_SIZE]; // dummy size +} tlv_entry_t; + +static int btstack_tlv_posix_append_tag(btstack_tlv_posix_t * self, uint32_t tag, const uint8_t * data, uint32_t data_size){ + + if (!self->file) return 1; + + log_info("append tag %04x, len %u", tag, data_size); + + uint8_t header[8]; + big_endian_store_32(header, 0, tag); + big_endian_store_32(header, 4, data_size); + size_t written_header = fwrite(header, 1, sizeof(header), self->file); + if (written_header != sizeof(header)) return 1; + size_t written_value = fwrite(data, 1, data_size, self->file); + if (written_value != data_size) return 1; + return 1; +} + +static tlv_entry_t * btstack_tlv_posix_find_entry(btstack_tlv_posix_t * self, uint32_t tag){ + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &self->entry_list); + while (btstack_linked_list_iterator_has_next(&it)){ + tlv_entry_t * entry = (tlv_entry_t*) btstack_linked_list_iterator_next(&it); + if (entry->tag != tag) continue; + return entry; + } + return NULL; +} + +/** + * Delete Tag + * @param tag + */ +static void btstack_tlv_posix_delete_tag(void * context, uint32_t tag){ + btstack_tlv_posix_t * self = (btstack_tlv_posix_t *) context; + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &self->entry_list); + while (btstack_linked_list_iterator_has_next(&it)){ + tlv_entry_t * entry = (tlv_entry_t*) btstack_linked_list_iterator_next(&it); + if (entry->tag != tag) continue; + btstack_linked_list_iterator_remove(&it); + btstack_tlv_posix_append_tag(self, tag, 0, NULL); + return; + } +} + +/** + * Get Value for Tag + * @param tag + * @param buffer + * @param buffer_size + * @returns size of value + */ +static int btstack_tlv_posix_get_tag(void * context, uint32_t tag, uint8_t * buffer, uint32_t buffer_size){ + btstack_tlv_posix_t * self = (btstack_tlv_posix_t *) context; + tlv_entry_t * entry = btstack_tlv_posix_find_entry(self, tag); + // not found + if (!entry) return 0; + // return len if buffer = NULL + if (!buffer) return entry->len; + // otherwise copy data into buffer + uint16_t bytes_to_copy = btstack_min(buffer_size, entry->len); + memcpy(buffer, &entry->value[0], bytes_to_copy); + return bytes_to_copy; +} + +/** + * Store Tag + * @param tag + * @param data + * @param data_size + */ +static int btstack_tlv_posix_store_tag(void * context, uint32_t tag, const uint8_t * data, uint32_t data_size){ + btstack_tlv_posix_t * self = (btstack_tlv_posix_t *) context; + + // remove old entry + tlv_entry_t * old_entry = btstack_tlv_posix_find_entry(self, tag); + if (old_entry){ + btstack_linked_list_remove(&self->entry_list, (btstack_linked_item_t *) old_entry); + free(old_entry); + } + + // create new entry + tlv_entry_t * new_entry = (tlv_entry_t *) malloc(sizeof(tlv_entry_t) - DUMMY_SIZE + data_size); + if (!new_entry) return 0; + new_entry->tag = tag; + new_entry->len = data_size; + memcpy(&new_entry->value[0], data, data_size); + + // append new entry + btstack_linked_list_add(&self->entry_list, (btstack_linked_item_t *) new_entry); + + // write new tag + btstack_tlv_posix_append_tag(self, tag, data, data_size); + + return 0; +} + +// returns 0 on success +static int btstack_tlv_posix_read_db(btstack_tlv_posix_t * self){ + // open file + log_info("open db %s", self->db_path); + self->file = fopen(self->db_path,"r+"); + uint8_t header[BTSTACK_TLV_HEADER_LEN]; + if (self->file){ + // checker header + size_t objects_read = fread(header, 1, BTSTACK_TLV_HEADER_LEN, self->file ); + int file_valid = 0; + if (objects_read == BTSTACK_TLV_HEADER_LEN){ + if (memcmp(header, btstack_tlv_header_magic, strlen(btstack_tlv_header_magic)) == 0){ + log_info("BTstack Magic Header found"); + // read entries + while (1){ + uint8_t entry[8]; + size_t entries_read = fread(entry, 1, sizeof(entry), self->file); + if (entries_read == 0){ + // EOF, we're good + file_valid = 1; + break; + } + if (entries_read == sizeof(entry)){ + uint32_t tag = big_endian_read_32(entry, 0); + uint32_t len = big_endian_read_32(entry, 4); + // arbitrary safetly check: values < 1000 bytes each + if (len > 1000) break; + tlv_entry_t * new_entry = (tlv_entry_t *) malloc(sizeof(tlv_entry_t) - DUMMY_SIZE + len); + if (!new_entry) return 0; + new_entry->tag = tag; + new_entry->len = len; + // read + size_t value_read = fread(&new_entry->value[0], 1, len, self->file); + if (value_read == len){ + // append new entry + btstack_linked_list_add(&self->entry_list, (btstack_linked_item_t *) new_entry); + } else { + // fail + free(entry); + break; + } + } + } + } + } + if (!file_valid) { + log_info("file invalid, re-create"); + fclose(self->file); + self->file = NULL; + } + } + if (!self->file){ + // create truncate file + self->file = fopen(self->db_path,"w+"); + memset(header, 0, sizeof(header)); + strcpy((char *)header, btstack_tlv_header_magic); + fwrite(header, 1, sizeof(header), self->file); + // write out all valid entries (if any) + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &self->entry_list); + while (btstack_linked_list_iterator_has_next(&it)){ + tlv_entry_t * entry = (tlv_entry_t*) btstack_linked_list_iterator_next(&it); + btstack_tlv_posix_append_tag(self, entry->tag, &entry->value[0], entry->len); + } + } + return 0; +} + +static const btstack_tlv_t btstack_tlv_posix = { + /* int (*get_tag)(..); */ &btstack_tlv_posix_get_tag, + /* int (*store_tag)(..); */ &btstack_tlv_posix_store_tag, + /* void (*delete_tag)(v..); */ &btstack_tlv_posix_delete_tag, +}; + +/** + * Init Tag Length Value Store + */ +const btstack_tlv_t * btstack_tlv_posix_init_instance(btstack_tlv_posix_t * self, const char * db_path){ + memset(self, 0, sizeof(btstack_tlv_posix_t)); + self->db_path = db_path; + + // read DB + btstack_tlv_posix_read_db(self); + return &btstack_tlv_posix; +} + diff --git a/platform/posix/btstack_tlv_posix.h b/platform/posix/btstack_tlv_posix.h new file mode 100644 index 000000000..1b9c903d8 --- /dev/null +++ b/platform/posix/btstack_tlv_posix.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY MATTHIAS RINGWALD AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * btstack_tlv_posix.h + * + * Implementation for BTstack's Tag Value Length Persistent Storage implementations + * using in-memory storage (RAM & malloc) and append-only log files on disc + */ + +#ifndef __BTSTACK_TLV_POSIX_H +#define __BTSTACK_TLV_POSIX_H + +#include +#include +#include "btstack_tlv.h" +#include "btstack_linked_list.h" + +#if defined __cplusplus +extern "C" { +#endif + +typedef struct { + btstack_linked_list_t entry_list; + const char * db_path; + FILE * file; +} btstack_tlv_posix_t; + +/** + * Init Tag Length Value Store + * @param context btstack_tlv_posix_t + * @param db_path on disc + */ +const btstack_tlv_t * btstack_tlv_posix_init_instance(btstack_tlv_posix_t * context, const char * db_path); + +#if defined __cplusplus +} +#endif +#endif // __BTSTACK_TLV_POSIX_H diff --git a/test/Makefile b/test/Makefile index 7b945b913..2ead5bda1 100644 --- a/test/Makefile +++ b/test/Makefile @@ -5,6 +5,7 @@ SUBDIRS = \ att_db \ avdtp \ avrcp \ + tlv_posix \ ble_client \ btstack_link_key_db \ des_iterator \ diff --git a/test/tlv_posix/.gitignore b/test/tlv_posix/.gitignore new file mode 100644 index 000000000..21de83719 --- /dev/null +++ b/test/tlv_posix/.gitignore @@ -0,0 +1,2 @@ +tlv_test +tlv_test.pklg diff --git a/test/tlv_posix/Makefile b/test/tlv_posix/Makefile new file mode 100644 index 000000000..885b21c83 --- /dev/null +++ b/test/tlv_posix/Makefile @@ -0,0 +1,47 @@ +CC=g++ + +BTSTACK_ROOT = ../.. +CPPUTEST_HOME = ${BTSTACK_ROOT}/test/cpputest + +COMMON_OBJ = \ + btstack_tlv_posix.o \ + btstack_util.o \ + btstack_linked_list.o \ + hci_dump.o \ + +VPATH = \ + ${BTSTACK_ROOT}/src \ + ${BTSTACK_ROOT}/src/classic \ + ${BTSTACK_ROOT}/src/ble \ + ${BTSTACK_ROOT}/platform/posix \ + +CFLAGS = \ + -DBTSTACK_TEST \ + -g \ + -Wall \ + -Wmissing-prototypes \ + -Wnarrowing \ + -I. \ + -I.. \ + -I${BTSTACK_ROOT}/src \ + -I${BTSTACK_ROOT}/platform/posix \ + +LDFLAGS += -lCppUTest -lCppUTestExt + +TESTS = tlv_test + +all: ${TESTS} + +clean: + rm -rf *.o $(TESTS) *.dSYM *.pklg + +tlv_test: ${COMMON_OBJ} tlv_test.o + ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ + +test: all + @echo Run all test + @set -e; \ + for test in $(TESTS); do \ + ./$$test; \ + done + diff --git a/test/tlv_posix/tlv_test.c b/test/tlv_posix/tlv_test.c new file mode 100644 index 000000000..3d9163a29 --- /dev/null +++ b/test/tlv_posix/tlv_test.c @@ -0,0 +1,164 @@ + +#include "CppUTest/TestHarness.h" +#include "CppUTest/CommandLineTestRunner.h" + +#include "btstack_tlv.h" +#include "btstack_tlv_posix.h" +#include "hci_dump.h" +#include "btstack_util.h" +#include "btstack_config.h" +#include "btstack_debug.h" +#include + +#define TEST_DB "/tmp/test.tlv" + +/// TLV +TEST_GROUP(BSTACK_TLV){ + const btstack_tlv_t * btstack_tlv_impl; + btstack_tlv_posix_t btstack_tlv_context; + void setup(void){ + log_info("setup"); + // delete old file + unlink(TEST_DB); + // open db + btstack_tlv_impl = btstack_tlv_posix_init_instance(&btstack_tlv_context, TEST_DB); + } + void reopen_db(void){ + log_info("reopen"); + // close file + fclose(btstack_tlv_context.file); + // reopen + btstack_tlv_impl = btstack_tlv_posix_init_instance(&btstack_tlv_context, TEST_DB); + } + void teardown(void){ + log_info("teardown"); + // close file + fclose(btstack_tlv_context.file); + } +}; + +TEST(BSTACK_TLV, TestMissingTag){ + uint32_t tag = 'abcd'; + int size = btstack_tlv_impl->get_tag(&btstack_tlv_context, tag, NULL, 0); + CHECK_EQUAL(size, 0); +} + +TEST(BSTACK_TLV, TestWriteRead){ + uint32_t tag = 'abcd'; + uint8_t data = 7; + uint8_t buffer = data; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag, &buffer, 1); + int size = btstack_tlv_impl->get_tag(&btstack_tlv_context, tag, NULL, 0); + CHECK_EQUAL(size, 1); + btstack_tlv_impl->get_tag(&btstack_tlv_context, tag, &buffer, 1); + CHECK_EQUAL(buffer, data); +} + +TEST(BSTACK_TLV, TestWriteWriteRead){ + uint32_t tag = 'abcd'; + uint8_t data = 7; + uint8_t buffer = data; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag, &buffer, 1); + data++; + buffer = data; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag, &buffer, 1); + int size = btstack_tlv_impl->get_tag(&btstack_tlv_context, tag, NULL, 0); + CHECK_EQUAL(size, 1); + btstack_tlv_impl->get_tag(&btstack_tlv_context, tag, &buffer, 1); + CHECK_EQUAL(buffer, data); +} + +TEST(BSTACK_TLV, TestWriteABARead){ + uint32_t tag_a = 'aaaa'; + uint32_t tag_b = 'bbbb'; + uint8_t data = 7; + uint8_t buffer = data; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag_a, &buffer, 1); + data++; + buffer = data; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag_b, &buffer, 1); + data++; + buffer = data; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag_a, &buffer, 1); + int size = btstack_tlv_impl->get_tag(&btstack_tlv_context, tag_a, NULL, 0); + CHECK_EQUAL(size, 1); + btstack_tlv_impl->get_tag(&btstack_tlv_context, tag_a, &buffer, 1); + CHECK_EQUAL(buffer, data); +} + +TEST(BSTACK_TLV, TestWriteDeleteRead){ + uint32_t tag = 'abcd'; + uint8_t data = 7; + uint8_t buffer = data; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag, &buffer, 1); + data++; + buffer = data; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag, &buffer, 1); + btstack_tlv_impl->delete_tag(&btstack_tlv_context, tag); + int size = btstack_tlv_impl->get_tag(&btstack_tlv_context, tag, NULL, 0); + CHECK_EQUAL(size, 0); +} + +TEST(BSTACK_TLV, TestMigrate){ + uint32_t tag = 'abcd'; + uint8_t data[8]; + memcpy(data, "01234567", 8); + + // entry 8 + data 8 = 16. + int i; + for (i=0;i<8;i++){ + data[0] = '0' + i; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag, &data[0], 8); + } + + reopen_db(); + + uint8_t buffer[8]; + btstack_tlv_impl->get_tag(&btstack_tlv_context, tag, &buffer[0], 1); + CHECK_EQUAL(buffer[0], data[0]); +} + +TEST(BSTACK_TLV, TestMigrate2){ + uint32_t tag1 = 0x11223344; + uint32_t tag2 = 0x44556677; + uint8_t data1[8]; + memcpy(data1, "01234567", 8); + uint8_t data2[8]; + memcpy(data2, "abcdefgh", 8); + + // entry 8 + data 8 = 16. + int i; + for (i=0;i<8;i++){ + data1[0] = '0' + i; + data2[0] = 'a' + i; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag1, data1, 8); + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag2, data2, 8); + } + + reopen_db(); + + uint8_t buffer[8]; + btstack_tlv_impl->get_tag(&btstack_tlv_context, tag1, &buffer[0], 1); + CHECK_EQUAL(buffer[0], data1[0]); + btstack_tlv_impl->get_tag(&btstack_tlv_context, tag2, &buffer[0], 1); + CHECK_EQUAL(buffer[0], data2[0]); +} + +TEST(BSTACK_TLV, TestWriteResetRead){ + uint32_t tag = 'abcd'; + uint8_t data = 7; + uint8_t buffer = data; + btstack_tlv_impl->store_tag(&btstack_tlv_context, tag, &buffer, 1); + + reopen_db(); + + int size = btstack_tlv_impl->get_tag(&btstack_tlv_context, tag, NULL, 0); + CHECK_EQUAL(size, 1); + btstack_tlv_impl->get_tag(&btstack_tlv_context, tag, &buffer, 1); + CHECK_EQUAL(buffer, data); +} + +int main (int argc, const char * argv[]){ + hci_dump_open("tlv_test.pklg", HCI_DUMP_PACKETLOGGER); + return CommandLineTestRunner::RunAllTests(argc, argv); +}