$(LOCAL_PATH)/include
LOCAL_SRC_FILES := \
+ ./src/config.c \
./src/fixed_queue.c \
./src/list.c \
./src/semaphore.c
LOCAL_CFLAGS := -std=c99 -Wall -Werror
LOCAL_MODULE := libosi
LOCAL_MODULE_TAGS := optional
-LOCAL_SHARED_LIBRARIES := libc
+LOCAL_SHARED_LIBRARIES := libc liblog
LOCAL_MODULE_CLASS := STATIC_LIBRARIES
include $(BUILD_STATIC_LIBRARY)
$(LOCAL_PATH)/include
LOCAL_SRC_FILES := \
+ ./test/config_test.cpp \
./test/list_test.cpp
LOCAL_CFLAGS := -Wall -Werror
LOCAL_MODULE := ositests
LOCAL_MODULE_TAGS := tests
+LOCAL_SHARED_LIBRARIES := liblog
LOCAL_STATIC_LIBRARIES := libosi
include $(BUILD_NATIVE_TEST)
--- /dev/null
+#pragma once
+
+// This module implements a configuration parser. Clients can query the
+// contents of a configuration file through the interface provided here.
+// The current implementation is read-only; mutations are only kept in
+// memory. This parser supports the INI file format.
+
+// Implementation notes:
+// - Key/value pairs that are not within a section are assumed to be under
+// the |CONFIG_DEFAULT_SECTION| section.
+// - Multiple sections with the same name will be merged as if they were in
+// a single section.
+// - Empty sections with no key/value pairs will be treated as if they do
+// not exist. In other words, |config_has_section| will return false for
+// empty sections.
+// - Duplicate keys in a section will overwrite previous values.
+
+#include <stdbool.h>
+
+// The default section name to use if a key/value pair is not defined within
+// a section.
+#define CONFIG_DEFAULT_SECTION "Global"
+
+struct config_t;
+typedef struct config_t config_t;
+
+// Loads the specified file and returns a handle to the config file. If there
+// was a problem loading the file or allocating memory, this function returns
+// NULL. Clients must call |config_free| on the returned handle when it is no
+// longer required. |filename| must not be NULL and must point to a readable
+// file on the filesystem.
+config_t *config_new(const char *filename);
+
+// Frees resources associated with the config file. No further operations may
+// be performed on the |config| object after calling this function. |config|
+// may be NULL.
+void config_free(config_t *config);
+
+// Returns true if the config file contains a section named |section|. If
+// the section has no key/value pairs in it, this function will return false.
+// |config| and |section| must not be NULL.
+bool config_has_section(const config_t *config, const char *section);
+
+// Returns true if the config file has a key named |key| under |section|.
+// Returns false otherwise. |config|, |section|, and |key| must not be NULL.
+bool config_has_key(const config_t *config, const char *section, const char *key);
+
+// Returns the integral value for a given |key| in |section|. If |section|
+// or |key| do not exist, or the value cannot be fully converted to an integer,
+// this function returns |def_value|. |config|, |section|, and |key| must not
+// be NULL.
+int config_get_int(const config_t *config, const char *section, const char *key, int def_value);
+
+// Returns the boolean value for a given |key| in |section|. If |section|
+// or |key| do not exist, or the value cannot be converted to a boolean, this
+// function returns |def_value|. |config|, |section|, and |key| must not be NULL.
+bool config_get_bool(const config_t *config, const char *section, const char *key, bool def_value);
+
+// Returns the string value for a given |key| in |section|. If |section| or
+// |key| do not exist, this function returns |def_value|. The returned string
+// is owned by the config module and must not be freed. |config|, |section|,
+// and |key| must not be NULL. |def_value| may be NULL.
+const char *config_get_string(const config_t *config, const char *section, const char *key, const char *def_value);
+
+// Sets an integral value for the |key| in |section|. If |key| or |section| do
+// not already exist, this function creates them. |config|, |section|, and |key|
+// must not be NULL.
+void config_set_int(config_t *config, const char *section, const char *key, int value);
+
+// Sets a boolean value for the |key| in |section|. If |key| or |section| do
+// not already exist, this function creates them. |config|, |section|, and |key|
+// must not be NULL.
+void config_set_bool(config_t *config, const char *section, const char *key, bool value);
+
+// Sets a string value for the |key| in |section|. If |key| or |section| do
+// not already exist, this function creates them. |config|, |section|, |key|, and
+// |value| must not be NULL.
+void config_set_string(config_t *config, const char *section, const char *key, const char *value);
--- /dev/null
+#define LOG_TAG "bt_osi_config"
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <utils/Log.h>
+
+#include "config.h"
+#include "list.h"
+
+typedef struct {
+ char *key;
+ char *value;
+} entry_t;
+
+typedef struct {
+ char *name;
+ list_t *entries;
+} section_t;
+
+struct config_t {
+ list_t *sections;
+};
+
+static void config_parse(FILE *fp, config_t *config);
+
+static section_t *section_new(const char *name);
+static void section_free(void *ptr);
+static section_t *section_find(const config_t *config, const char *section);
+
+static entry_t *entry_new(const char *key, const char *value);
+static void entry_free(void *ptr);
+static entry_t *entry_find(const config_t *config, const char *section, const char *key);
+
+config_t *config_new(const char *filename) {
+ assert(filename != NULL);
+
+ FILE *fp = fopen(filename, "rt");
+ if (!fp) {
+ ALOGE("%s unable to open file '%s': %s", __func__, filename, strerror(errno));
+ return NULL;
+ }
+
+ config_t *config = calloc(1, sizeof(config_t));
+ if (!config) {
+ ALOGE("%s unable to allocate memory for config_t.", __func__);
+ fclose(fp);
+ return NULL;
+ }
+
+ config->sections = list_new(section_free);
+ config_parse(fp, config);
+
+ fclose(fp);
+
+ return config;
+}
+
+void config_free(config_t *config) {
+ if (!config)
+ return;
+
+ list_free(config->sections);
+ free(config);
+}
+
+bool config_has_section(const config_t *config, const char *section) {
+ assert(config != NULL);
+ assert(section != NULL);
+
+ return (section_find(config, section) != NULL);
+}
+
+bool config_has_key(const config_t *config, const char *section, const char *key) {
+ assert(config != NULL);
+ assert(section != NULL);
+ assert(key != NULL);
+
+ return (entry_find(config, section, key) != NULL);
+}
+
+int config_get_int(const config_t *config, const char *section, const char *key, int def_value) {
+ assert(config != NULL);
+ assert(section != NULL);
+ assert(key != NULL);
+
+ entry_t *entry = entry_find(config, section, key);
+ if (!entry)
+ return def_value;
+
+ char *endptr;
+ int ret = strtol(entry->value, &endptr, 0);
+ return (*endptr == '\0') ? ret : def_value;
+}
+
+bool config_get_bool(const config_t *config, const char *section, const char *key, bool def_value) {
+ assert(config != NULL);
+ assert(section != NULL);
+ assert(key != NULL);
+
+ entry_t *entry = entry_find(config, section, key);
+ if (!entry)
+ return def_value;
+
+ if (!strcmp(entry->value, "true"))
+ return true;
+ if (!strcmp(entry->value, "false"))
+ return false;
+
+ return def_value;
+}
+
+const char *config_get_string(const config_t *config, const char *section, const char *key, const char *def_value) {
+ assert(config != NULL);
+ assert(section != NULL);
+ assert(key != NULL);
+
+ entry_t *entry = entry_find(config, section, key);
+ if (!entry)
+ return def_value;
+
+ return entry->value;
+}
+
+void config_set_int(config_t *config, const char *section, const char *key, int value) {
+ assert(config != NULL);
+ assert(section != NULL);
+ assert(key != NULL);
+
+ char value_str[32] = { 0 };
+ sprintf(value_str, "%d", value);
+ config_set_string(config, section, key, value_str);
+}
+
+void config_set_bool(config_t *config, const char *section, const char *key, bool value) {
+ assert(config != NULL);
+ assert(section != NULL);
+ assert(key != NULL);
+
+ config_set_string(config, section, key, value ? "true" : "false");
+}
+
+void config_set_string(config_t *config, const char *section, const char *key, const char *value) {
+ section_t *sec = section_find(config, section);
+ if (!sec) {
+ sec = section_new(section);
+ list_append(config->sections, sec);
+ }
+
+ for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
+ entry_t *entry = list_node(node);
+ if (!strcmp(entry->key, key)) {
+ free(entry->value);
+ entry->value = strdup(value);
+ return;
+ }
+ }
+
+ entry_t *entry = entry_new(key, value);
+ list_append(sec->entries, entry);
+}
+
+static char *trim(char *str) {
+ while (isspace(*str))
+ ++str;
+
+ if (!*str)
+ return str;
+
+ char *end_str = str + strlen(str) - 1;
+ while (end_str > str && isspace(*end_str))
+ --end_str;
+
+ end_str[1] = '\0';
+ return str;
+}
+
+static void config_parse(FILE *fp, config_t *config) {
+ assert(fp != NULL);
+ assert(config != NULL);
+
+ int line_num = 0;
+ char line[1024];
+ char section[1024];
+ strcpy(section, CONFIG_DEFAULT_SECTION);
+
+ while (fgets(line, sizeof(line), fp)) {
+ char *line_ptr = trim(line);
+ ++line_num;
+
+ // Skip blank and comment lines.
+ if (*line_ptr == '\0' || *line_ptr == '#')
+ continue;
+
+ if (*line_ptr == '[') {
+ size_t len = strlen(line_ptr);
+ if (line_ptr[len - 1] != ']') {
+ ALOGD("%s unterminated section name on line %d.", __func__, line_num);
+ continue;
+ }
+ strncpy(section, line_ptr + 1, len - 2);
+ section[len - 2] = '\0';
+ } else {
+ char *split = strchr(line_ptr, '=');
+ if (!split) {
+ ALOGD("%s no key/value separator found on line %d.", __func__, line_num);
+ continue;
+ }
+
+ *split = '\0';
+ config_set_string(config, section, trim(line_ptr), trim(split + 1));
+ }
+ }
+}
+
+static section_t *section_new(const char *name) {
+ section_t *section = calloc(1, sizeof(section_t));
+ if (!section)
+ return NULL;
+
+ section->name = strdup(name);
+ section->entries = list_new(entry_free);
+ return section;
+}
+
+static void section_free(void *ptr) {
+ if (!ptr)
+ return;
+
+ section_t *section = ptr;
+ free(section->name);
+ list_free(section->entries);
+}
+
+static section_t *section_find(const config_t *config, const char *section) {
+ for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
+ section_t *sec = list_node(node);
+ if (!strcmp(sec->name, section))
+ return sec;
+ }
+
+ return NULL;
+}
+
+static entry_t *entry_new(const char *key, const char *value) {
+ entry_t *entry = calloc(1, sizeof(entry_t));
+ if (!entry)
+ return NULL;
+
+ entry->key = strdup(key);
+ entry->value = strdup(value);
+ return entry;
+}
+
+static void entry_free(void *ptr) {
+ if (!ptr)
+ return;
+
+ entry_t *entry = ptr;
+ free(entry->key);
+ free(entry->value);
+}
+
+static entry_t *entry_find(const config_t *config, const char *section, const char *key) {
+ section_t *sec = section_find(config, section);
+ if (!sec)
+ return NULL;
+
+ for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
+ entry_t *entry = list_node(node);
+ if (!strcmp(entry->key, key))
+ return entry;
+ }
+
+ return NULL;
+}
--- /dev/null
+#include <gtest/gtest.h>
+
+extern "C" {
+#include "config.h"
+}
+
+static const char CONFIG_FILE[] = "/data/local/tmp/config_test.conf";
+static const char CONFIG_FILE_CONTENT[] =
+" \n\
+first_key=value \n\
+ \n\
+# Device ID (DID) configuration \n\
+[DID] \n\
+ \n\
+# Record Number: 1, 2 or 3 - maximum of 3 records \n\
+recordNumber = 1 \n\
+ \n\
+# Primary Record - true or false (default) \n\
+# There can be only one primary record \n\
+primaryRecord = true \n\
+ \n\
+# Vendor ID '0xFFFF' indicates no Device ID Service Record is present in the device \n\
+# 0x000F = Broadcom Corporation (default) \n\
+#vendorId = 0x000F \n\
+ \n\
+# Vendor ID Source \n\
+# 0x0001 = Bluetooth SIG assigned Device ID Vendor ID value (default) \n\
+# 0x0002 = USB Implementer's Forum assigned Device ID Vendor ID value \n\
+#vendorIdSource = 0x0001 \n\
+ \n\
+# Product ID & Product Version \n\
+# Per spec DID v1.3 0xJJMN for version is interpreted as JJ.M.N \n\
+# JJ: major version number, M: minor version number, N: sub-minor version number \n\
+# For example: 1200, v14.3.6 \n\
+productId = 0x1200 \n\
+version = 0x1111 \n\
+ \n\
+# Optional attributes \n\
+#clientExecutableURL = \n\
+#serviceDescription = \n\
+#documentationURL = \n\
+ \n\
+# Additional optional DID records. Bluedroid supports up to 3 records. \n\
+[DID] \n\
+[DID] \n\
+version = 0x1436 \n\
+";
+
+class ConfigTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ FILE *fp = fopen(CONFIG_FILE, "wt");
+ fwrite(CONFIG_FILE_CONTENT, 1, sizeof(CONFIG_FILE_CONTENT), fp);
+ fclose(fp);
+ }
+};
+
+TEST_F(ConfigTest, config_new_no_file) {
+ config_t *config = config_new("/meow");
+ EXPECT_TRUE(config == NULL);
+}
+
+TEST_F(ConfigTest, config_new) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_TRUE(config != NULL);
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_free_null) {
+ config_free(NULL);
+}
+
+TEST_F(ConfigTest, config_has_section) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_TRUE(config_has_section(config, "DID"));
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_has_key_in_default_section) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_TRUE(config_has_key(config, CONFIG_DEFAULT_SECTION, "first_key"));
+ EXPECT_STREQ(config_get_string(config, CONFIG_DEFAULT_SECTION, "first_key", "meow"), "value");
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_has_keys) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_TRUE(config_has_key(config, "DID", "recordNumber"));
+ EXPECT_TRUE(config_has_key(config, "DID", "primaryRecord"));
+ EXPECT_TRUE(config_has_key(config, "DID", "productId"));
+ EXPECT_TRUE(config_has_key(config, "DID", "version"));
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_no_bad_keys) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_FALSE(config_has_key(config, "DID_BAD", "primaryRecord"));
+ EXPECT_FALSE(config_has_key(config, "DID", "primaryRecord_BAD"));
+ EXPECT_FALSE(config_has_key(config, CONFIG_DEFAULT_SECTION, "primaryRecord"));
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_get_int_version) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_EQ(config_get_int(config, "DID", "version", 0), 0x1436);
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_get_int_default) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_EQ(config_get_int(config, "DID", "primaryRecord", 123), 123);
+ config_free(config);
+}