From 701be08ec17a76673b190b7c57b8fb7fa73f5ce8 Mon Sep 17 00:00:00 2001 From: "jacob.eva" Date: Tue, 6 Aug 2024 17:25:50 +0100 Subject: [PATCH] Add the start of BLE support on ESP32 --- Bluetooth.h | 132 +++++++++--------------- Utilities.h | 174 ++----------------------------- src/bluetooth/BLE.cpp | 232 ++++++++++++++++++++++++++++++++++++++++++ src/bluetooth/BLE.hpp | 46 +++++++++ src/misc/FIFOBuffer.c | 95 +++++++++++++++++ src/misc/FIFOBuffer.h | 59 +++++++++++ 6 files changed, 488 insertions(+), 250 deletions(-) create mode 100644 src/bluetooth/BLE.cpp create mode 100644 src/bluetooth/BLE.hpp create mode 100644 src/misc/FIFOBuffer.c create mode 100644 src/misc/FIFOBuffer.h diff --git a/Bluetooth.h b/Bluetooth.h index fb0abe4..6e767a9 100644 --- a/Bluetooth.h +++ b/Bluetooth.h @@ -20,10 +20,8 @@ #include "esp_bt_device.h" BluetoothSerial SerialBT; #elif HAS_BLE == true - #include "esp_bt_main.h" - #include "esp_bt_device.h" - // TODO: Remove - #define SerialBT Serial + //#include "src/bluetooth/BLE.h" + BLESerial SerialBT; #endif #elif MCU_VARIANT == MCU_NRF52 @@ -156,89 +154,55 @@ char bt_devname[11]; } #elif HAS_BLE == true - void bt_stop() { - if (bt_state != BT_STATE_OFF) { - bt_allow_pairing = false; - bt_state = BT_STATE_OFF; - } +// Many of the functions in this section are stubs. The actual functions can be found in src/bluetooth/BLE.cpp + +void bt_stop() { + if (bt_state != BT_STATE_OFF) { + bt_allow_pairing = false; + bt_state = BT_STATE_OFF; + SerialBT.stop(); + } +} + +void bt_disable_pairing() { + bt_allow_pairing = false; + bt_ssp_pin = 0; + bt_state = BT_STATE_ON; +} + +bool bt_setup_hw() { + return SerialBT.bt_setup_hw(); +} + +void bt_start() { + if (bt_state == BT_STATE_OFF) { + bt_state = BT_STATE_ON; + SerialBT.bt_start(); + } +} + +bool bt_init() { + bt_state = BT_STATE_OFF; + if (bt_setup_hw()) { + if (bt_enabled && !console_active) bt_start(); + return true; + } else { + return false; } +} - void bt_disable_pairing() { - bt_allow_pairing = false; - bt_ssp_pin = 0; - bt_state = BT_STATE_ON; - } +void bt_enable_pairing() { + if (bt_state == BT_STATE_OFF) bt_start(); + bt_allow_pairing = true; + bt_pairing_started = millis(); + bt_state = BT_STATE_PAIRING; +} - void bt_connect_callback(uint16_t conn_handle) { - bt_state = BT_STATE_CONNECTED; - cable_state = CABLE_STATE_DISCONNECTED; - } - - void bt_disconnect_callback(uint16_t conn_handle, uint8_t reason) { - bt_state = BT_STATE_ON; - } - - bool bt_setup_hw() { - if (!bt_ready) { - if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { - bt_enabled = true; - } else { - bt_enabled = false; - } - if (btStart()) { - if (esp_bluedroid_init() == ESP_OK) { - if (esp_bluedroid_enable() == ESP_OK) { - const uint8_t* bda_ptr = esp_bt_dev_get_address(); - char *data = (char*)malloc(BT_DEV_ADDR_LEN+1); - for (int i = 0; i < BT_DEV_ADDR_LEN; i++) { - data[i] = bda_ptr[i]; - } - data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE)); - unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN); - memcpy(bt_dh, hash, BT_DEV_HASH_LEN); - sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]); - free(data); - - // TODO: Implement GAP & GATT for RNode comms over BLE - - bt_ready = true; - return true; - - } else { return false; } - } else { return false; } - } else { return false; } - } else { return false; } - } - - void bt_start() { - if (bt_state == BT_STATE_OFF) { - bt_state = BT_STATE_ON; - // TODO: Implement - } - } - - bool bt_init() { - bt_state = BT_STATE_OFF; - if (bt_setup_hw()) { - if (bt_enabled && !console_active) bt_start(); - return true; - } else { - return false; - } - } - - void bt_enable_pairing() { - if (bt_state == BT_STATE_OFF) bt_start(); - bt_allow_pairing = true; - bt_pairing_started = millis(); - bt_state = BT_STATE_PAIRING; - } - - void update_bt() { - if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) { - bt_disable_pairing(); - } - } +void update_bt() { + if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) { + bt_disable_pairing(); + } +} #endif #elif MCU_VARIANT == MCU_NRF52 diff --git a/Utilities.h b/Utilities.h index 3f0a8b8..087fd5c 100644 --- a/Utilities.h +++ b/Utilities.h @@ -45,8 +45,14 @@ uint8_t eeprom_read(uint32_t mapped_addr); #include "Display.h" #endif -#if HAS_BLUETOOTH == true || HAS_BLE == true +#if HAS_BLUETOOTH || HAS_BLE void kiss_indicate_btpin(); +#endif + +#if HAS_BLE + #include "src/bluetooth/BLE.hpp" + #include "Bluetooth.h" +#elif HAS_BLUETOOTH #include "Bluetooth.h" #endif @@ -1516,168 +1522,4 @@ void unlock_rom() { eeprom_erase(); } -typedef struct FIFOBuffer -{ - unsigned char *begin; - unsigned char *end; - unsigned char * volatile head; - unsigned char * volatile tail; -} FIFOBuffer; - -inline bool fifo_isempty(const FIFOBuffer *f) { - return f->head == f->tail; -} - -inline bool fifo_isfull(const FIFOBuffer *f) { - return ((f->head == f->begin) && (f->tail == f->end)) || (f->tail == f->head - 1); -} - -inline void fifo_push(FIFOBuffer *f, unsigned char c) { - *(f->tail) = c; - - if (f->tail == f->end) { - f->tail = f->begin; - } else { - f->tail++; - } -} - -inline unsigned char fifo_pop(FIFOBuffer *f) { - if(f->head == f->end) { - f->head = f->begin; - return *(f->end); - } else { - return *(f->head++); - } -} - -inline void fifo_flush(FIFOBuffer *f) { - f->head = f->tail; -} - -#if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - static inline bool fifo_isempty_locked(const FIFOBuffer *f) { - bool result; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - result = fifo_isempty(f); - } - return result; - } - - static inline bool fifo_isfull_locked(const FIFOBuffer *f) { - bool result; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - result = fifo_isfull(f); - } - return result; - } - - static inline void fifo_push_locked(FIFOBuffer *f, unsigned char c) { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - fifo_push(f, c); - } - } -#endif - -/* -static inline unsigned char fifo_pop_locked(FIFOBuffer *f) { - unsigned char c; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - c = fifo_pop(f); - } - return c; -} -*/ - -inline void fifo_init(FIFOBuffer *f, unsigned char *buffer, size_t size) { - f->head = f->tail = f->begin = buffer; - f->end = buffer + size; -} - -inline size_t fifo_len(FIFOBuffer *f) { - return f->end - f->begin; -} - -typedef struct FIFOBuffer16 -{ - uint16_t *begin; - uint16_t *end; - uint16_t * volatile head; - uint16_t * volatile tail; -} FIFOBuffer16; - -inline bool fifo16_isempty(const FIFOBuffer16 *f) { - return f->head == f->tail; -} - -inline bool fifo16_isfull(const FIFOBuffer16 *f) { - return ((f->head == f->begin) && (f->tail == f->end)) || (f->tail == f->head - 1); -} - -inline void fifo16_push(FIFOBuffer16 *f, uint16_t c) { - *(f->tail) = c; - - if (f->tail == f->end) { - f->tail = f->begin; - } else { - f->tail++; - } -} - -inline uint16_t fifo16_pop(FIFOBuffer16 *f) { - if(f->head == f->end) { - f->head = f->begin; - return *(f->end); - } else { - return *(f->head++); - } -} - -inline void fifo16_flush(FIFOBuffer16 *f) { - f->head = f->tail; -} - -#if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 - static inline bool fifo16_isempty_locked(const FIFOBuffer16 *f) { - bool result; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - result = fifo16_isempty(f); - } - - return result; - } -#endif - -/* -static inline bool fifo16_isfull_locked(const FIFOBuffer16 *f) { - bool result; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - result = fifo16_isfull(f); - } - return result; -} - - -static inline void fifo16_push_locked(FIFOBuffer16 *f, uint16_t c) { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - fifo16_push(f, c); - } -} - -static inline size_t fifo16_pop_locked(FIFOBuffer16 *f) { - size_t c; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - c = fifo16_pop(f); - } - return c; -} -*/ - -inline void fifo16_init(FIFOBuffer16 *f, uint16_t *buffer, uint16_t size) { - f->head = f->tail = f->begin = buffer; - f->end = buffer + size; -} - -inline uint16_t fifo16_len(FIFOBuffer16 *f) { - return (f->end - f->begin); -} +#include "src/misc/FIFOBuffer.h" diff --git a/src/bluetooth/BLE.cpp b/src/bluetooth/BLE.cpp new file mode 100644 index 0000000..d74c13a --- /dev/null +++ b/src/bluetooth/BLE.cpp @@ -0,0 +1,232 @@ +#include "BLE.hpp" + +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp32-hal-bt.h" +#include +#include +#include +#include + +#include +#include "../../MD5.h" +#include +#include "../../Boards.h" +#include + +// These UUIDs emulate the nordic BLE UART service +#define SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e" +#define RX_UUID "6e400002-b5a3-f393-e0a9-e50e24dcca9e" +#define TX_UUID "6e400003-b5a3-f393-e0a9-e50e24dcca9e" + +// Bluetooth variables +// Todo, clean this up, it's a total mess +extern bool bt_enabled; +extern bool bt_ready; +extern bool bt_allow_pairing; +extern uint8_t bt_state; +extern uint8_t cable_state; +extern portMUX_TYPE update_lock; +extern void kiss_indicate_btpin(); +extern BLESerial SerialBT; +#define BT_DEV_ADDR_LEN 6 +#define BT_DEV_HASH_LEN 16 + #define BT_STATE_NA 0xff + #define BT_STATE_OFF 0x00 + #define BT_STATE_ON 0x01 + #define BT_STATE_PAIRING 0x02 + #define BT_STATE_CONNECTED 0x03 + #define CABLE_STATE_DISCONNECTED 0x00 + #define CABLE_STATE_CONNECTED 0x01 + #define FEND 0xC0 +extern char bt_da[BT_DEV_ADDR_LEN]; +extern char bt_dh[BT_DEV_HASH_LEN]; +extern char bt_devname[11]; +extern uint32_t bt_ssp_pin; +#define eeprom_addr(a) (a+EEPROM_OFFSET) + +bool BLECallbacks::onConfirmPIN(uint32_t passkey){ + return false; +} + +bool BLECallbacks::onSecurityRequest(){ + return true; +} + +void BLECallbacks::onPassKeyNotify(uint32_t passkey) { + bt_ssp_pin = passkey; + kiss_indicate_btpin(); +} + +uint32_t BLECallbacks::onPassKeyRequest() { return 0; } + +void BLECallbacks::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + bt_state = BT_STATE_CONNECTED; + cable_state = CABLE_STATE_DISCONNECTED; +} + +void BLECallbacks::onConnect(BLEServer *server) { +} + +void BLECallbacks::onDisconnect(BLEServer *server) { + bt_state = BT_STATE_ON; + SerialBT.Advertising->start(); +} + +void BLECallbacks::onWrite(BLECharacteristic* chr) { + if (chr->getUUID().toString() == RX_UUID) { + std::string data = chr->getValue(); + + for (int i = 0; i < data.length(); i++) { + fifo_push(&SerialBT.rxFIFO, data[i]); + SerialBT.rxFIFOLength++; + } + } +} + +bool BLESerial::bt_setup_hw() { + if (!bt_ready) { + if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { + bt_enabled = true; + } else { + bt_enabled = false; + } + if (btStart()) { + if (esp_bluedroid_init() == ESP_OK) { + if (esp_bluedroid_enable() == ESP_OK) { + const uint8_t* bda_ptr = esp_bt_dev_get_address(); + char *data = (char*)malloc(BT_DEV_ADDR_LEN+1); + for (int i = 0; i < BT_DEV_ADDR_LEN; i++) { + data[i] = bda_ptr[i]; + } + data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE)); + unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN); + memcpy(bt_dh, hash, BT_DEV_HASH_LEN); + sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]); + free(data); + + BLEDevice::init(bt_devname); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + + _security = new BLESecurity(); + _security->setKeySize(); + _security->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND); // Secure Connections with MITM Protection and bonding enabled. + _security->setCapability(ESP_IO_CAP_OUT); // Display only + _security->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + // This ensures the remote device has actually entered the pin from + // the device by forcing the set authentication mode (MITM_BOND). + // This is because it is possible to bypass passkey entry in BLE by + // simply changing the reported capabilities of your device + // otherwise. Therefore, devices which have not authenticated + // properly should not be allowed to pair in the first place. + uint8_t own_auth_cfg_only = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_ENABLE; + esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &own_auth_cfg_only, sizeof(uint8_t)); + + _server = BLEDevice::createServer(); + _service = _server->createService(SERVICE_UUID); + _txchr = _service->createCharacteristic(TX_UUID, BLECharacteristic::PROPERTY_NOTIFY); + _rxchr = _service->createCharacteristic(RX_UUID, BLECharacteristic::PROPERTY_WRITE); + _txchr->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED); + _rxchr->setAccessPermissions(ESP_GATT_PERM_WRITE_ENCRYPTED); + + _txchr->addDescriptor(new BLE2902()); + _rxchr->addDescriptor(new BLE2902()); + + _txchr->setReadProperty(true); + _rxchr->setWriteProperty(true); + + BLECallbacks* callbacks = new BLECallbacks(); + + _rxchr->setCallbacks(callbacks); + _server->setCallbacks(callbacks); + BLEDevice::setSecurityCallbacks(callbacks); + Advertising = _server->getAdvertising(); + + memset(_rxBuffer, 0, sizeof(_rxBuffer)); + fifo_init(&rxFIFO, _rxBuffer, BLE_RX_BUFFER_SIZE); + memset(_txBuffer, 0, sizeof(_txBuffer)); + fifo_init(&_txFIFO, _txBuffer, BLE_TX_BUFFER_SIZE); + + rxFIFOLength = 0; + + bt_ready = true; + return true; + + } else { return false; } + } else { return false; } + } else { return false; } + } else { return false; } +} + +void BLESerial::bt_start() { + _service->start(); + Advertising->start(); +} + +void BLESerial::stop() { + _service->stop(); + Advertising->stop(); +} + +size_t BLESerial::write(uint8_t byte) { + bool endcmd = false; + if ((!fifo_isempty(&_txFIFO)) && byte == FEND) { + endcmd = true; + } + + if (fifo_isfull(&_txFIFO)) { + flush(); + } + + fifo_push(&_txFIFO, byte); + + if (endcmd) { + flush(); + } + return 1; +} + +/*size_t BLESerial::write(const uint8_t *buffer, size_t size) { + uint16_t written = 0; + for (int i = 0; i < size; i++) { + fifo_push(&_txFIFO, buffer[i]); + written++; + } + flush(); + return written; +}*/ + +// todo, this can be removed once fifo_len function works as intended +int BLESerial::available() { + return rxFIFOLength; +} + +int BLESerial::read() { + rxFIFOLength--; + uint8_t byte = fifo_pop(&rxFIFO); + delay(5); + return byte; +} +int BLESerial::peek() { + // doesn't work for the moment. todo, remove? + return 0; +} +void BLESerial::flush() { + uint8_t tx_buffer[BLE_TX_BUFFER_SIZE]; + uint16_t index = 0; + while (!fifo_isempty(&_txFIFO)) { + if (index == BLE_TX_BUFFER_SIZE) { + _txchr->setValue(tx_buffer, index); + _txchr->notify(true); + index = 0; + } + + tx_buffer[index] = fifo_pop(&_txFIFO); + index++; + } + if (index > 0) { + _txchr->setValue(tx_buffer, index); + _txchr->notify(true); + } +} diff --git a/src/bluetooth/BLE.hpp b/src/bluetooth/BLE.hpp new file mode 100644 index 0000000..95f09ea --- /dev/null +++ b/src/bluetooth/BLE.hpp @@ -0,0 +1,46 @@ +#include +#include +#include "../misc/FIFOBuffer.h" + +#define BLE_RX_BUFFER_SIZE 256 +#define BLE_TX_BUFFER_SIZE ESP_GATT_MAX_ATTR_LEN + + +class BLECallbacks : public BLESecurityCallbacks, public BLEServerCallbacks, public BLECharacteristicCallbacks { + public: + bool onConfirmPIN(uint32_t passkey); + bool onSecurityRequest(); + void onPassKeyNotify(uint32_t passkey); + uint32_t onPassKeyRequest(); + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl); + void onConnect(BLEServer *server); + void onDisconnect(BLEServer *server); + void onWrite(BLECharacteristic *chr); +}; + +class BLESerial : public Stream { + public: + void bt_start(); + bool bt_setup_hw(); + void stop(); + // From Stream & Print + virtual size_t write(uint8_t byte); + //virtual size_t write(const uint8_t *buffer, size_t size); + virtual int available(); + virtual int read(); + virtual int peek(); + virtual void flush(); + + BLEAdvertising *Advertising; + FIFOBuffer rxFIFO; + volatile uint16_t rxFIFOLength; + private: + BLEServer *_server; + BLEService *_service; + BLECharacteristic *_txchr; + BLECharacteristic *_rxchr; + BLESecurity *_security; + uint8_t _rxBuffer[BLE_RX_BUFFER_SIZE]; + FIFOBuffer _txFIFO; + uint8_t _txBuffer[BLE_TX_BUFFER_SIZE]; +}; diff --git a/src/misc/FIFOBuffer.c b/src/misc/FIFOBuffer.c new file mode 100644 index 0000000..86fa0ca --- /dev/null +++ b/src/misc/FIFOBuffer.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include "FIFOBuffer.h" + +#ifdef __cplusplus + extern "C" { +#endif + + bool fifo_isempty(const FIFOBuffer *f) { + return f->head == f->tail; +} + + bool fifo_isfull(const FIFOBuffer *f) { + return ((f->head == f->begin) && (f->tail == f->end)) || (f->tail == f->head - 1); +} + + void fifo_push(FIFOBuffer *f, unsigned char c) { + *(f->tail) = c; + + if (f->tail == f->end) { + f->tail = f->begin; + } else { + f->tail++; + } +} + + unsigned char fifo_pop(FIFOBuffer *f) { + if(f->head == f->end) { + f->head = f->begin; + return *(f->end); + } else { + return *(f->head++); + } +} + + void fifo_flush(FIFOBuffer *f) { + f->head = f->tail; +} + + + void fifo_init(FIFOBuffer *f, unsigned char *buffer, size_t size) { + f->head = f->tail = f->begin = buffer; + f->end = buffer + size; +} + +// todo, fix this so it actually displays the amount of data in the fifo +// buffer, not just the size allocated for the buffer + size_t fifo_len(FIFOBuffer *f) { + return f->end - f->begin; +} + + bool fifo16_isempty(const FIFOBuffer16 *f) { + return f->head == f->tail; +} + + bool fifo16_isfull(const FIFOBuffer16 *f) { + return ((f->head == f->begin) && (f->tail == f->end)) || (f->tail == f->head - 1); +} + + void fifo16_push(FIFOBuffer16 *f, uint16_t c) { + *(f->tail) = c; + + if (f->tail == f->end) { + f->tail = f->begin; + } else { + f->tail++; + } +} + + uint16_t fifo16_pop(FIFOBuffer16 *f) { + if(f->head == f->end) { + f->head = f->begin; + return *(f->end); + } else { + return *(f->head++); + } +} + + void fifo16_flush(FIFOBuffer16 *f) { + f->head = f->tail; +} + + void fifo16_init(FIFOBuffer16 *f, uint16_t *buffer, uint16_t size) { + f->head = f->tail = f->begin = buffer; + f->end = buffer + size; +} + + uint16_t fifo16_len(FIFOBuffer16 *f) { + return (f->end - f->begin); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/misc/FIFOBuffer.h b/src/misc/FIFOBuffer.h new file mode 100644 index 0000000..bdc6d00 --- /dev/null +++ b/src/misc/FIFOBuffer.h @@ -0,0 +1,59 @@ +#ifndef FIFOBUFFER_H + +#define FIFOBUFFER_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* An 8 bit FIFO buffer implementation */ +typedef struct FIFOBuffer +{ + unsigned char *begin; + unsigned char *end; + unsigned char * volatile head; + unsigned char * volatile tail; +} FIFOBuffer; + +bool fifo_isempty(const FIFOBuffer *f); + +bool fifo_isfull(const FIFOBuffer *f); + +void fifo_push(FIFOBuffer *f, unsigned char c); + +unsigned char fifo_pop(FIFOBuffer *f); + +void fifo_flush(FIFOBuffer *f); + +void fifo_init(FIFOBuffer *f, unsigned char *buffer, size_t size); + +size_t fifo_len(FIFOBuffer *f); + +/* A 16-bit implementation of the same FIFO buffer. */ +typedef struct FIFOBuffer16 +{ + uint16_t *begin; + uint16_t *end; + uint16_t * volatile head; + uint16_t * volatile tail; +} FIFOBuffer16; + +bool fifo16_isempty(const FIFOBuffer16 *f); + +bool fifo16_isfull(const FIFOBuffer16 *f); + +void fifo16_push(FIFOBuffer16 *f, uint16_t c); + +uint16_t fifo16_pop(FIFOBuffer16 *f); + +void fifo16_flush(FIFOBuffer16 *f); + +void fifo16_init(FIFOBuffer16 *f, uint16_t *buffer, uint16_t size); + +uint16_t fifo16_len(FIFOBuffer16 *f); + +#ifdef __cplusplus +} +#endif + +#endif