From f8709b964471e161201b02706f22a2c8fbb4a965 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 17 Oct 2021 16:05:21 +0000 Subject: [PATCH] Fixed the docs and add support for advertising data --- components/modules/ble.c | 590 ++------------------------------------- docs/modules/ble.md | 17 +- 2 files changed, 42 insertions(+), 565 deletions(-) diff --git a/components/modules/ble.c b/components/modules/ble.c index c39c7f9e..ce1b3395 100644 --- a/components/modules/ble.c +++ b/components/modules/ble.c @@ -52,6 +52,8 @@ static int lble_start_advertising(); static int lble_gap_event(struct ble_gap_event *event, void *arg); static const char *gadget_name; +static uint8_t *gadget_mfg; +static size_t gadget_mfg_len; static const struct ble_gatt_svc_def *gatt_svr_svcs; @@ -85,16 +87,6 @@ typedef struct { /** * Utility function to log an array of bytes. */ -static void -print_bytes(const uint8_t *bytes, int len) -{ - int i; - - for (i = 0; i < len; i++) { - MODLOG_DFLT(INFO, "%s0x%02x", i != 0 ? ":" : "", bytes[i]); - } -} - static void print_addr(const void *addr) { @@ -105,550 +97,6 @@ print_addr(const void *addr) u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); } - - -#if 0 - - -#include -#include -#include -#include "bsp/bsp.h" -#include "host/ble_hs.h" -#include "host/ble_uuid.h" -#include "bleprph.h" - -/** - * The vendor specific security test service consists of two characteristics: - * o random-number-generator: generates a random 32-bit number each time - * it is read. This characteristic can only be read over an encrypted - * connection. - * o static-value: a single-byte characteristic that can always be read, - * but can only be written over an encrypted connection. - */ - -/* 59462f12-9543-9999-12c8-58b459a2712d */ -static const ble_uuid128_t gatt_svr_svc_sec_test_uuid = - BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12, - 0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59); - -/* 5c3a659e-897e-45e1-b016-007107c96df6 */ -static const ble_uuid128_t gatt_svr_chr_sec_test_rand_uuid = - BLE_UUID128_INIT(0xf6, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, - 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); - -/* 5c3a659e-897e-45e1-b016-007107c96df7 */ -static const ble_uuid128_t gatt_svr_chr_sec_test_static_uuid = - BLE_UUID128_INIT(0xf7, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, - 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); - -static uint8_t gatt_svr_sec_test_static_val; - -static int -gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, - struct ble_gatt_access_ctxt *ctxt, - void *arg); - -static const struct ble_gatt_svc_def gatt_svr_svcs[] = { - { - /*** Service: Security test. */ - .type = BLE_GATT_SVC_TYPE_PRIMARY, - .uuid = &gatt_svr_svc_sec_test_uuid.u, - .characteristics = (struct ble_gatt_chr_def[]) { { - /*** Characteristic: Random number generator. */ - .uuid = &gatt_svr_chr_sec_test_rand_uuid.u, - .access_cb = gatt_svr_chr_access_sec_test, - .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, - }, { - /*** Characteristic: Static value. */ - .uuid = &gatt_svr_chr_sec_test_static_uuid.u, - .access_cb = gatt_svr_chr_access_sec_test, - .flags = BLE_GATT_CHR_F_READ | - BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC, - }, { - 0, /* No more characteristics in this service. */ - } }, - }, - - { - 0, /* No more services. */ - }, -}; - -static int -gatt_svr_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, - void *dst, uint16_t *len) -{ - uint16_t om_len; - int rc; - - om_len = OS_MBUF_PKTLEN(om); - if (om_len < min_len || om_len > max_len) { - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - - rc = ble_hs_mbuf_to_flat(om, dst, max_len, len); - if (rc != 0) { - return BLE_ATT_ERR_UNLIKELY; - } - - return 0; -} - -static int -gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, - struct ble_gatt_access_ctxt *ctxt, - void *arg) -{ - const ble_uuid_t *uuid; - int rand_num; - int rc; - - uuid = ctxt->chr->uuid; - - /* Determine which characteristic is being accessed by examining its - * 128-bit UUID. - */ - - if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_rand_uuid.u) == 0) { - assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); - - /* Respond with a 32-bit random number. */ - rand_num = rand(); - rc = os_mbuf_append(ctxt->om, &rand_num, sizeof rand_num); - return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; - } - - if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_static_uuid.u) == 0) { - switch (ctxt->op) { - case BLE_GATT_ACCESS_OP_READ_CHR: - rc = os_mbuf_append(ctxt->om, &gatt_svr_sec_test_static_val, - sizeof gatt_svr_sec_test_static_val); - return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; - - case BLE_GATT_ACCESS_OP_WRITE_CHR: - rc = gatt_svr_chr_write(ctxt->om, - sizeof gatt_svr_sec_test_static_val, - sizeof gatt_svr_sec_test_static_val, - &gatt_svr_sec_test_static_val, NULL); - return rc; - - default: - assert(0); - return BLE_ATT_ERR_UNLIKELY; - } - } - - /* Unknown characteristic; the nimble stack should not have called this - * function. - */ - assert(0); - return BLE_ATT_ERR_UNLIKELY; -} - -void -gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) -{ - char buf[BLE_UUID_STR_LEN]; - - switch (ctxt->op) { - case BLE_GATT_REGISTER_OP_SVC: - MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n", - ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), - ctxt->svc.handle); - break; - - case BLE_GATT_REGISTER_OP_CHR: - MODLOG_DFLT(DEBUG, "registering characteristic %s with " - "def_handle=%d val_handle=%d\n", - ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), - ctxt->chr.def_handle, - ctxt->chr.val_handle); - break; - - case BLE_GATT_REGISTER_OP_DSC: - MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n", - ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), - ctxt->dsc.handle); - break; - - default: - assert(0); - break; - } -} - -int -gatt_svr_init(void) -{ - int rc; - - rc = ble_gatts_count_cfg(gatt_svr_svcs); - if (rc != 0) { - return rc; - } - - rc = ble_gatts_add_svcs(gatt_svr_svcs); - if (rc != 0) { - return rc; - } - - return 0; -} - - -/** - * Logs information about a connection to the console. - */ -static void -bleprph_print_conn_desc(struct ble_gap_conn_desc *desc) -{ - MODLOG_DFLT(INFO, "handle=%d our_ota_addr_type=%d our_ota_addr=", - desc->conn_handle, desc->our_ota_addr.type); - print_addr(desc->our_ota_addr.val); - MODLOG_DFLT(INFO, " our_id_addr_type=%d our_id_addr=", - desc->our_id_addr.type); - print_addr(desc->our_id_addr.val); - MODLOG_DFLT(INFO, " peer_ota_addr_type=%d peer_ota_addr=", - desc->peer_ota_addr.type); - print_addr(desc->peer_ota_addr.val); - MODLOG_DFLT(INFO, " peer_id_addr_type=%d peer_id_addr=", - desc->peer_id_addr.type); - print_addr(desc->peer_id_addr.val); - MODLOG_DFLT(INFO, " conn_itvl=%d conn_latency=%d supervision_timeout=%d " - "encrypted=%d authenticated=%d bonded=%d\n", - desc->conn_itvl, desc->conn_latency, - desc->supervision_timeout, - desc->sec_state.encrypted, - desc->sec_state.authenticated, - desc->sec_state.bonded); -} - -/** - * Enables advertising with the following parameters: - * o General discoverable mode. - * o Undirected connectable mode. - */ -static void -bleprph_advertise(void) -{ - struct ble_gap_adv_params adv_params; - struct ble_hs_adv_fields fields; - const char *name; - int rc; - - /** - * Set the advertisement data included in our advertisements: - * o Flags (indicates advertisement type and other general info). - * o Advertising tx power. - * o Device name. - * o 16-bit service UUIDs (alert notifications). - */ - - memset(&fields, 0, sizeof fields); - - /* Advertise two flags: - * o Discoverability in forthcoming advertisement (general) - * o BLE-only (BR/EDR unsupported). - */ - fields.flags = BLE_HS_ADV_F_DISC_GEN | - BLE_HS_ADV_F_BREDR_UNSUP; - - /* Indicate that the TX power level field should be included; have the - * stack fill this value automatically. This is done by assigning the - * special value BLE_HS_ADV_TX_PWR_LVL_AUTO. - */ - fields.tx_pwr_lvl_is_present = 1; - fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; - - name = ble_svc_gap_device_name(); - fields.name = (uint8_t *)name; - fields.name_len = strlen(name); - fields.name_is_complete = 1; - - fields.uuids16 = (ble_uuid16_t[]) { - BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID) - }; - fields.num_uuids16 = 1; - fields.uuids16_is_complete = 1; - - rc = ble_gap_adv_set_fields(&fields); - if (rc != 0) { - MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc); - return; - } - - /* Begin advertising. */ - memset(&adv_params, 0, sizeof adv_params); - adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; - adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; - rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, - &adv_params, bleprph_gap_event, NULL); - if (rc != 0) { - MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc); - return; - } -} - -/** - * The nimble host executes this callback when a GAP event occurs. The - * application associates a GAP event callback with each connection that forms. - * bleprph uses the same callback for all connections. - * - * @param event The type of event being signalled. - * @param ctxt Various information pertaining to the event. - * @param arg Application-specified argument; unused by - * bleprph. - * - * @return 0 if the application successfully handled the - * event; nonzero on failure. The semantics - * of the return code is specific to the - * particular GAP event being signalled. - */ -static int -bleprph_gap_event(struct ble_gap_event *event, void *arg) -{ - struct ble_gap_conn_desc desc; - int rc; - - switch (event->type) { - case BLE_GAP_EVENT_CONNECT: - /* A new connection was established or a connection attempt failed. */ - MODLOG_DFLT(INFO, "connection %s; status=%d ", - event->connect.status == 0 ? "established" : "failed", - event->connect.status); - if (event->connect.status == 0) { - rc = ble_gap_conn_find(event->connect.conn_handle, &desc); - assert(rc == 0); - bleprph_print_conn_desc(&desc); - } - MODLOG_DFLT(INFO, "\n"); - - if (event->connect.status != 0) { - /* Connection failed; resume advertising. */ - bleprph_advertise(); - } - return 0; - - case BLE_GAP_EVENT_DISCONNECT: - MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); - bleprph_print_conn_desc(&event->disconnect.conn); - MODLOG_DFLT(INFO, "\n"); - - /* Connection terminated; resume advertising. */ - bleprph_advertise(); - return 0; - - case BLE_GAP_EVENT_CONN_UPDATE: - /* The central has updated the connection parameters. */ - MODLOG_DFLT(INFO, "connection updated; status=%d ", - event->conn_update.status); - rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc); - assert(rc == 0); - bleprph_print_conn_desc(&desc); - MODLOG_DFLT(INFO, "\n"); - return 0; - - case BLE_GAP_EVENT_ADV_COMPLETE: - MODLOG_DFLT(INFO, "advertise complete; reason=%d", - event->adv_complete.reason); - bleprph_advertise(); - return 0; - - case BLE_GAP_EVENT_ENC_CHANGE: - /* Encryption has been enabled or disabled for this connection. */ - MODLOG_DFLT(INFO, "encryption change event; status=%d ", - event->enc_change.status); - rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); - assert(rc == 0); - bleprph_print_conn_desc(&desc); - MODLOG_DFLT(INFO, "\n"); - return 0; - - case BLE_GAP_EVENT_SUBSCRIBE: - MODLOG_DFLT(INFO, "subscribe event; conn_handle=%d attr_handle=%d " - "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", - event->subscribe.conn_handle, - event->subscribe.attr_handle, - event->subscribe.reason, - event->subscribe.prev_notify, - event->subscribe.cur_notify, - event->subscribe.prev_indicate, - event->subscribe.cur_indicate); - return 0; - - case BLE_GAP_EVENT_MTU: - MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n", - event->mtu.conn_handle, - event->mtu.channel_id, - event->mtu.value); - return 0; - - case BLE_GAP_EVENT_REPEAT_PAIRING: - /* We already have a bond with the peer, but it is attempting to - * establish a new secure link. This app sacrifices security for - * convenience: just throw away the old bond and accept the new link. - */ - - /* Delete the old bond. */ - rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); - assert(rc == 0); - ble_store_util_delete_peer(&desc.peer_id_addr); - - /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should - * continue with the pairing operation. - */ - return BLE_GAP_REPEAT_PAIRING_RETRY; - - case BLE_GAP_EVENT_PASSKEY_ACTION: - ESP_LOGI(tag, "PASSKEY_ACTION_EVENT started \n"); - struct ble_sm_io pkey = {0}; - int key = 0; - - if (event->passkey.params.action == BLE_SM_IOACT_DISP) { - pkey.action = event->passkey.params.action; - pkey.passkey = 123456; // This is the passkey to be entered on peer - ESP_LOGI(tag, "Enter passkey %d on the peer side", pkey.passkey); - rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); - ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); - } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { - ESP_LOGI(tag, "Passkey on device's display: %d", event->passkey.params.numcmp); - ESP_LOGI(tag, "Accept or reject the passkey through console in this format -> key Y or key N"); - pkey.action = event->passkey.params.action; - if (scli_receive_key(&key)) { - pkey.numcmp_accept = key; - } else { - pkey.numcmp_accept = 0; - ESP_LOGE(tag, "Timeout! Rejecting the key"); - } - rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); - ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); - } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { - static uint8_t tem_oob[16] = {0}; - pkey.action = event->passkey.params.action; - for (int i = 0; i < 16; i++) { - pkey.oob[i] = tem_oob[i]; - } - rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); - ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); - } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { - ESP_LOGI(tag, "Enter the passkey through console in this format-> key 123456"); - pkey.action = event->passkey.params.action; - if (scli_receive_key(&key)) { - pkey.passkey = key; - } else { - pkey.passkey = 0; - ESP_LOGE(tag, "Timeout! Passing 0 as the key"); - } - rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); - ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc); - } - return 0; - } - - return 0; -} - -static void -bleprph_on_reset(int reason) -{ - MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); -} - -static void -bleprph_on_sync(void) -{ - int rc; - - rc = ble_hs_util_ensure_addr(0); - assert(rc == 0); - - /* Figure out address to use while advertising (no privacy for now) */ - rc = ble_hs_id_infer_auto(0, &own_addr_type); - if (rc != 0) { - MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc); - return; - } - - /* Printing ADDR */ - uint8_t addr_val[6] = {0}; - rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL); - - MODLOG_DFLT(INFO, "Device Address: "); - print_addr(addr_val); - MODLOG_DFLT(INFO, "\n"); - /* Begin advertising. */ - bleprph_advertise(); -} - -void bleprph_host_task(void *param) -{ - ESP_LOGI(tag, "BLE Host Task Started"); - /* This function will return only when nimble_port_stop() is executed */ - nimble_port_run(); - - nimble_port_freertos_deinit(); -} - -void -app_main(void) -{ - int rc; - - /* Initialize NVS — it is used to store PHY calibration data */ - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); - } - ESP_ERROR_CHECK(ret); - - ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init()); - - nimble_port_init(); - /* Initialize the NimBLE host configuration. */ - ble_hs_cfg.reset_cb = bleprph_on_reset; - ble_hs_cfg.sync_cb = bleprph_on_sync; - ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; - ble_hs_cfg.store_status_cb = ble_store_util_status_rr; - - ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE; -#ifdef CONFIG_EXAMPLE_BONDING - ble_hs_cfg.sm_bonding = 1; -#endif -#ifdef CONFIG_EXAMPLE_MITM - ble_hs_cfg.sm_mitm = 1; -#endif -#ifdef CONFIG_EXAMPLE_USE_SC - ble_hs_cfg.sm_sc = 1; -#else - ble_hs_cfg.sm_sc = 0; -#endif -#ifdef CONFIG_EXAMPLE_BONDING - ble_hs_cfg.sm_our_key_dist = 1; - ble_hs_cfg.sm_their_key_dist = 1; -#endif - - - rc = gatt_svr_init(); - assert(rc == 0); - - /* Set the default device name. */ - rc = ble_svc_gap_device_name_set("nimble-bleprph"); - assert(rc == 0); - - /* XXX Need to have template for store */ - ble_store_config_init(); - - nimble_port_freertos_init(bleprph_host_task); - - /* Initialize command line interface to accept input from user */ - rc = scli_init(); - if (rc != ESP_OK) { - ESP_LOGE(tag, "scli_init() failed"); - } -} -#endif static int gethexval(char c) { if (c < '0') { @@ -918,20 +366,23 @@ lble_task_cb(task_param_t param, task_prio_t prio) { lua_rawgeti(L, -1, 1); lua_remove(L, -2); // and throw away the table } + // value, struct, chr table lua_getfield(L, -3, "write"); if (!lua_isnoneornil(L, -1)) { - lua_pushvalue(L, -2); // the values table - if (lua_pcall(L, 1, 0, 0)) { + lua_pushvalue(L, -4); // the characterstics table + lua_pushvalue(L, -3); // the value + if (lua_pcall(L, 2, 0, 0)) { PERROR(); message.errcode = BLE_ATT_ERR_UNLIKELY; lua_pop(L, 2); goto cleanup; } + } else { + lua_pop(L, 1); // Throw away the null write pointer + // just save the result in the value + lua_setfield(L, -3, "value"); + message.errcode = 0; } - lua_pop(L, 1); // Throw away the null write pointer - // just save the result in the value - lua_setfield(L, -3, "value"); - message.errcode = 0; } cleanup: @@ -1308,6 +759,9 @@ lble_start_advertising() { fields.tx_pwr_lvl_is_present = 1; fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + fields.mfg_data = gadget_mfg; + fields.mfg_data_len = gadget_mfg_len; + size_t name_length = strlen(name); if (name_length < 16) { fields.name = (uint8_t *)name; @@ -1413,6 +867,22 @@ static int lble_init(lua_State *L) { gadget_name = name; int rc; + + free((void *) gadget_mfg); + gadget_mfg = NULL; + gadget_mfg_len = 0; + + lua_getfield(L, 1, "mfg"); + if (!lua_isnoneornil(L, -1)) { + size_t len; + const char *mfg = lua_tolstring(L, -1, &len); + gadget_mfg = malloc(len); + if (!gadget_mfg) { + return luaL_error(L, "out of memory"); + } + gadget_mfg_len = len; + memcpy(gadget_mfg, mfg, len); + } /* Initialize the NimBLE host configuration. */ // ble_hs_cfg.reset_cb = bleprph_on_reset; diff --git a/docs/modules/ble.md b/docs/modules/ble.md index 7087871f..a2796662 100644 --- a/docs/modules/ble.md +++ b/docs/modules/ble.md @@ -22,7 +22,7 @@ configuration table. See below for a detailed description of this table. #### Example ```lua -local config = {name="MyGadget=", services={{uuid="0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}}}} +local config = {name="MyGadget=", services={{uuid="0123456789abcdef0123456789abcdef", characteristics={{uuid="1234", value=0, type='c'}}}}} ble.init(config) ``` @@ -47,6 +47,10 @@ ble.shutdown(function(err) print(err or "Ok!") end) ## Conventions +## UUID + +The service and characteristic identifiers are UUIDs. These are represented in twin-hex. They must be either 4 characters, 8 characters or 32 characters long. + ## Configuration Table The configuration table contains the following keys: @@ -55,6 +59,7 @@ The configuration table contains the following keys: - `services` This is a list of tables that define the individual services. The primary service is the first service. Many examples will only have a single service. +- `mfg` This is a string to be advertised in the mfg data field. ### Service table @@ -62,8 +67,6 @@ The service table contains the following keys: - `uuid` The UUID of the service. This is a 16 byte string (128 bits) that identifies the particular service. It can also be a two byte string for a well-known service. - `characteristics` This is a list of tables, where each entry describes a characateristic (attribute) -- `preread` This is an optional function that is invoked just before a read of any characteristic in this service. -- `postwrite` This is an optional function that is invoked just after a write of any characteristic in this service. ### Characteristic table @@ -75,7 +78,11 @@ The characteristic table contains the following keys: - `read` This is a function that will be invoked to read the value (and so does not need the `value` entry). It should return a string of bytes (unless `type` is set) - `write` This is a function that will be invoked to write the value (and so does not need the `value` entry). It is given a string of bytes (unless `type` is set) -The characteristics are treated as read/write unless only one of the `read` or `write` keys is present. +The characteristics are treated as read/write unless only one of the `read` or `write` keys is present and the `value` key is not specificed. + +The calling conventions for these functions are as follows: + +- `read` This is invoked with the charactersitic table as its only argument. +- `write` This is invoked with two arguments, the characteristic table and the data to be written (after conversion by `type`) -The calling conventions for these functions are TBD.