diff --git a/README.md b/README.md index 3ad3d9b..1da453b 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,29 @@ -*worked on this a few days offtime, now here is the first working source* - # esp-ena -Implementation of the Covid-19 Exposure Notification API by Apple and Google on an ESP32 (with ESP-IDF). +Implementation of contact tracing with the Covid-19 Exposure Notification API by Apple and Google on an ESP32 (with [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html)). More information about the Covid-19 Exposure Notification at [Apple](https://www.apple.com/covid19/contacttracing/) and [Google](https://www.google.com/covid19/exposurenotifications/). This is meant for people without smartphone or without smartphones with Apples/Googles implementation. [Demo Video](https://twitter.com/Lurkars/status/1282223547579019264) -This implementation covers for now the BLE part including the cryptography specifications needed (see Bluetooth Specifications and Cryptography Specifications documents in the links above): +This implementation fully covers for the BLE part including the cryptography specifications needed (see Bluetooth Specifications and Cryptography Specifications documents in the links above): * send beacons * store TEKs on flash (last 14) * receive beacons * received beacons are stored after 5 minutes threshold (storage is limited, ~100k beacons can be stored) -Features missing for now are: -* compare received beacons with infected list -* calculating risks scores - -Extensions planned: -* add RTC (will test DS3231) -* add display (added SSD1306) +Additional features for full ENA device +* calculating risks scores (after adding reported keys and storing exposure information) +* RTC support with DS3231 +* display support with SSD1306 * interface to * set time - * delete beacons + +Features missing for now are: +* retrieve infected list and parse from binary (started with binary parsing) + +Extensions planned: +* interface to + * delete data * show status * report infection? * receive infected beacons list (will test [Corona Warn App](https://github.com/corona-warn-app)) @@ -44,9 +45,7 @@ The following acronyms will be used in code and comments: * *AEM* Associated Encrypted Metadata - send and received metadata Open questions -* now save ENIN for stored beacons (documentation says timestamp), but for infection status ENIN should be enough!? * service UUID is send reversed, must RPI and AEM also beeing send in reverse? Don't know BLE specification enough -* fixed change of advertise payload every 10 minutes, random value between ~15 minutes better? ## How to use diff --git a/components/ds3231/CMakeLists.txt b/components/ds3231/CMakeLists.txt new file mode 100644 index 0000000..b84f0cd --- /dev/null +++ b/components/ds3231/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register( + SRCS + "ds3231.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES + i2c-main +) \ No newline at end of file diff --git a/components/ds3231/ds3231.c b/components/ds3231/ds3231.c new file mode 100644 index 0000000..311234e --- /dev/null +++ b/components/ds3231/ds3231.c @@ -0,0 +1,119 @@ +// Copyright 2020 Lukas Haubaum +// +// Licensed under the GNU Affero General Public License, Version 3; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// https://www.gnu.org/licenses/agpl-3.0.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include + +#include "driver/i2c.h" +#include "esp_log.h" + +#include "i2c-main.h" + +#include "ds3231.h" + +uint8_t ds3231_dec2bcd(uint8_t value) +{ + return ((value / 10 * 16) + (value % 10)); +} + +uint8_t ds3231_bcd2dec(uint8_t value) +{ + return ((value / 16 * 10) + (value % 16)); +} + +void ds3231_get_time(struct tm *time) +{ + if (!i2c_is_initialized()) + { + i2c_main_init(); + } + uint8_t data[7]; + + i2c_cmd_handle_t cmd; + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (DS3231_ADDRESS << 1) | I2C_MASTER_WRITE, true); + i2c_master_write_byte(cmd, DS3231_TIME, true); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (DS3231_ADDRESS << 1) | I2C_MASTER_READ, true); + i2c_master_read(cmd, data, 7, I2C_MASTER_LAST_NACK); + i2c_master_stop(cmd); + ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS)); + i2c_cmd_link_delete(cmd); + + time->tm_sec = ds3231_bcd2dec(data[0]); + time->tm_min = ds3231_bcd2dec(data[1]); + if (data[2] & DS3231_12_HOUR_FLAG) + { + time->tm_hour = ds3231_bcd2dec(data[2] & DS3231_12_HOUR_MASK) - 1; + if (data[2] & DS3231_PM_HOUR_FLAG) + { + time->tm_hour += 12; + } + } + else + { + time->tm_hour = ds3231_bcd2dec(data[2]); + } + time->tm_wday = ds3231_bcd2dec(data[3]) - 1; + time->tm_mday = ds3231_bcd2dec(data[4]); + time->tm_mon = ds3231_bcd2dec(data[5] & DS3231_MONTH_MASK) - 1; + uint8_t century = (data[5] & DS3231_CENTURY_FLAG) >> 7; + if (century) + { + time->tm_year = ds3231_bcd2dec(data[6]) + 100; + } + else + { + time->tm_year = ds3231_bcd2dec(data[6]); + } + time->tm_isdst = 0; +} + +void ds3231_set_time(struct tm *time) +{ + if (!i2c_is_initialized()) + { + i2c_main_init(); + } + uint8_t data[7] = {0}; + data[0] = ds3231_dec2bcd(time->tm_sec); + data[1] = ds3231_dec2bcd(time->tm_min); + data[2] = ds3231_dec2bcd(time->tm_hour); // write 24h format + data[3] = ds3231_dec2bcd(time->tm_wday + 1); + data[4] = ds3231_dec2bcd(time->tm_mday); + uint8_t century = 0; + if (time->tm_year > 100) + { + century = DS3231_CENTURY_FLAG; + data[6] = ds3231_dec2bcd(time->tm_year - 100); + } + else + { + data[6] = ds3231_dec2bcd(time->tm_year); + } + + data[5] = ds3231_dec2bcd(time->tm_mon + 1) + century; + + i2c_cmd_handle_t cmd; + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (DS3231_ADDRESS << 1) | I2C_MASTER_WRITE, true); + + i2c_master_write_byte(cmd, DS3231_TIME, true); + i2c_master_write(cmd, data, 7, true); + + i2c_master_stop(cmd); + ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS)); + i2c_cmd_link_delete(cmd); +} diff --git a/components/ds3231/include/ds3231.h b/components/ds3231/include/ds3231.h new file mode 100644 index 0000000..42607cf --- /dev/null +++ b/components/ds3231/include/ds3231.h @@ -0,0 +1,78 @@ +// Copyright 2020 Lukas Haubaum +// +// Licensed under the GNU Affero General Public License, Version 3; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// https://www.gnu.org/licenses/agpl-3.0.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _ds3231_H_ +#define _ds3231_H_ + +#include + +// I2C address +#define DS3231_ADDRESS 0x68 + +// I2C registers +#define DS3231_TIME 0x00 +#define DS3231_SECONDS 0x00 +#define DS3231_MINUTES 0x01 +#define DS3231_HOURS 0x02 +#define DS3231_DAY 0x03 +#define DS3231_DATE 0x04 +#define DS3231_MONTH 0x05 +#define DS3231_YEAR 0x06 +#define DS3231_ALARM1_SECONDS 0x07 +#define DS3231_ALARM1_MINUTES 0x08 +#define DS3231_ALARM1_HOURS 0x09 +#define DS3231_ALARM1_DATE 0x0A +#define DS3231_ALARM2_MINUTES 0x0B +#define DS3231_ALARM2_HOURS 0x0C +#define DS3231_ALARM2_DATE 0x0D +#define DS3231_CONTROL 0x0E +#define DS3231_STATUS 0x0F +#define DS3231_AGING_OFFSET 0x10 +#define DS3231_MSB_TEMP 0x11 +#define DS3231_LSB_TEMP 0x12 + +// control registers +#define DS3231_CONTROL_A1IE 0x01 +#define DS3231_CONTROL_A2IE 0x02 +#define DS3231_CONTROL_INTCN 0x04 +#define DS3231_CONTROL_RS1 0x08 +#define DS3231_CONTROL_RS2 0x10 +#define DS3231_CONTROL_CONV 0x20 +#define DS3231_CONTROL_BBSQW 0x40 +#define DS3231_CONTROL_EOSC 0x80 + +// status registers +#define DS3231_STATUSL_A1F 0x01 +#define DS3231_STATUSL_A2F 0x02 +#define DS3231_STATUSL_BSY 0x04 +#define DS3231_STATUSL_EN32KHZ 0x08 +#define DS3231_STATUSL_OSF 0x80 + +// flags +#define DS3231_CENTURY_FLAG 0x80 +#define DS3231_12_HOUR_FLAG 0x40 +#define DS3231_PM_HOUR_FLAG 0x20 +#define DS3231_12_HOUR_MASK 0x1F +#define DS3231_MONTH_MASK 0x1F + +/** + * @brief Read time from DS3231 + */ +void ds3231_get_time(struct tm *time); + +/** + * @brief Write time to DS3231 + */ +void ds3231_set_time(struct tm *time); + +#endif \ No newline at end of file diff --git a/components/ena-interface/CMakeLists.txt b/components/ena-interface/CMakeLists.txt index 41e429a..f70bfe9 100644 --- a/components/ena-interface/CMakeLists.txt +++ b/components/ena-interface/CMakeLists.txt @@ -3,6 +3,7 @@ idf_component_register( "ena-interface.c" "ena-interface-datetime.c" "ena-interface-menu.c" + "ena-interface-status.c" INCLUDE_DIRS "include" PRIV_REQUIRES ena diff --git a/components/ena-interface/ena-interface-datetime.c b/components/ena-interface/ena-interface-datetime.c index c731cf0..2ef66b8 100644 --- a/components/ena-interface/ena-interface-datetime.c +++ b/components/ena-interface/ena-interface-datetime.c @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. #include +#include +#include #include "esp_log.h" #include "ena-interface.h" @@ -22,15 +24,51 @@ static int interface_datetime_state = ENA_INTERFACE_DATETIME_STATE_YEAR; +const uint32_t interface_datetime_steps[6] = { + 31557600, 2629800, 86400, 3600, 60, 1}; + void ena_interface_datetime_esc(void) { ena_interface_menu_start(); } +void ena_interface_datetime_ok(void) +{ + interface_datetime_state++; + if (interface_datetime_state > ENA_INTERFACE_DATETIME_STATE_SECONDS) + { + interface_datetime_state = ENA_INTERFACE_DATETIME_STATE_YEAR; + } + ESP_LOGD(ENA_INTERFACE_LOG, "datetime to %d", interface_datetime_state); +} + +void ena_interface_datetime_up(void) +{ + time_t curtime = time(NULL); + curtime += interface_datetime_steps[interface_datetime_state]; + struct timeval tv = {0}; + tv.tv_sec = curtime; + settimeofday(&tv, NULL); + ESP_LOGD(ENA_INTERFACE_LOG, "increment %d about %u %s", interface_datetime_state, interface_datetime_steps[interface_datetime_state], ctime(&curtime)); +} + +void ena_interface_datetime_down(void) +{ + time_t curtime = time(NULL); + curtime -= interface_datetime_steps[interface_datetime_state]; + struct timeval tv = {0}; + tv.tv_sec = curtime; + settimeofday(&tv, NULL); + ESP_LOGD(ENA_INTERFACE_LOG, "decrement %d about %u %s", interface_datetime_state, interface_datetime_steps[interface_datetime_state], ctime(&curtime)); +} + void ena_interface_datetime_start(void) { - ena_interface_set_state(ENA_INTERFACE_STATE_SET_YEAR); + ena_interface_set_state(ENA_INTERFACE_STATE_SET_DATETIME); ena_interface_register_touch_callback(TOUCH_PAD_ESC, &ena_interface_datetime_esc); + ena_interface_register_touch_callback(TOUCH_PAD_OK, &ena_interface_datetime_ok); + ena_interface_register_touch_callback(TOUCH_PAD_UP, &ena_interface_datetime_up); + ena_interface_register_touch_callback(TOUCH_PAD_DOWN, &ena_interface_datetime_down); ESP_LOGD(ENA_INTERFACE_LOG, "start datetime interface"); } diff --git a/components/ena-interface/ena-interface-menu.c b/components/ena-interface/ena-interface-menu.c index e77c336..e374d2d 100644 --- a/components/ena-interface/ena-interface-menu.c +++ b/components/ena-interface/ena-interface-menu.c @@ -17,6 +17,7 @@ #include "driver/touch_pad.h" #include "ena-interface.h" #include "ena-interface-datetime.h" +#include "ena-interface-status.h" #include "ena-interface-menu.h" @@ -24,17 +25,35 @@ static int interface_menu_state = ENA_INTERFACE_MENU_STATE_IDLE; void ena_interface_menu_ok(void) { - if (interface_menu_state == ENA_INTERFACE_MENU_STATE_SELECT_TIME) { + if (interface_menu_state == ENA_INTERFACE_MENU_STATE_SELECT_TIME) + { ena_interface_datetime_start(); } + else if (interface_menu_state == ENA_INTERFACE_MENU_STATE_SELECT_STATUS) + { + ena_interface_status_start(); + } + else if (interface_menu_state == ENA_INTERFACE_MENU_STATE_IDLE) + { + if (ena_interface_get_state() == ENA_INTERFACE_STATE_MENU) + { + ena_interface_set_state(ENA_INTERFACE_STATE_IDLE); + ena_interface_register_touch_callback(TOUCH_PAD_UP, NULL); + ena_interface_register_touch_callback(TOUCH_PAD_DOWN, NULL); + } + else + { + ena_interface_menu_start(); + } + } } void ena_interface_menu_up(void) { interface_menu_state--; - if (interface_menu_state < 0) + if (interface_menu_state < ENA_INTERFACE_MENU_STATE_IDLE) { - interface_menu_state = sizeof(interface_menu_state) - 1; + interface_menu_state = ENA_INTERFACE_MENU_STATE_SELECT_STATUS; } ESP_LOGD(ENA_INTERFACE_LOG, "menu up to %d", interface_menu_state); } @@ -42,9 +61,9 @@ void ena_interface_menu_up(void) void ena_interface_menu_down(void) { interface_menu_state++; - if (interface_menu_state == sizeof(interface_menu_state)) + if (interface_menu_state > ENA_INTERFACE_MENU_STATE_SELECT_STATUS) { - interface_menu_state = 0; + interface_menu_state = ENA_INTERFACE_MENU_STATE_IDLE; } ESP_LOGD(ENA_INTERFACE_LOG, "menu down to %d", interface_menu_state); } diff --git a/components/ena-interface/ena-interface-status.c b/components/ena-interface/ena-interface-status.c new file mode 100644 index 0000000..d3dd8f6 --- /dev/null +++ b/components/ena-interface/ena-interface-status.c @@ -0,0 +1,37 @@ +// Copyright 2020 Lukas Haubaum +// +// Licensed under the GNU Affero General Public License, Version 3; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// https://www.gnu.org/licenses/agpl-3.0.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include + +#include "esp_log.h" +#include "driver/touch_pad.h" +#include "ena-interface.h" +#include "ena-interface-menu.h" + +#include "ena-interface-status.h" + +void ena_interface_status_esc(void) +{ + ena_interface_menu_start(); +} + +void ena_interface_status_start(void) +{ + ena_interface_set_state(ENA_INTERFACE_STATE_STATUS); + ena_interface_register_touch_callback(TOUCH_PAD_ESC, &ena_interface_status_esc); + ena_interface_register_touch_callback(TOUCH_PAD_OK, NULL); + ena_interface_register_touch_callback(TOUCH_PAD_UP, NULL); + ena_interface_register_touch_callback(TOUCH_PAD_DOWN, NULL); + + ESP_LOGD(ENA_INTERFACE_LOG, "start status interface"); +} diff --git a/components/ena-interface/ena-interface.c b/components/ena-interface/ena-interface.c index e0ae368..1b5128b 100644 --- a/components/ena-interface/ena-interface.c +++ b/components/ena-interface/ena-interface.c @@ -33,24 +33,23 @@ void ena_interface_register_touch_callback(int touch_pad, ena_interface_touch_ca void ena_interface_run(void *pvParameter) { - uint16_t touch_value; - uint16_t touch_thresh; - bool touch_status_current[4] = {0}; + static uint16_t touch_value; + static uint16_t touch_thresh; + static bool touch_status_current[TOUCH_PAD_MAX] = {0}; while (1) { for (int i = 0; i < TOUCH_PAD_COUNT; i++) { - int touch_id = touch_mapping[i]; - ESP_ERROR_CHECK_WITHOUT_ABORT(touch_pad_read_filtered(touch_id, &touch_value)); - ESP_ERROR_CHECK_WITHOUT_ABORT(touch_pad_get_thresh(touch_id, &touch_thresh)); + ESP_ERROR_CHECK_WITHOUT_ABORT(touch_pad_read_filtered(touch_mapping[i], &touch_value)); + ESP_ERROR_CHECK_WITHOUT_ABORT(touch_pad_get_thresh(touch_mapping[i], &touch_thresh)); touch_status_current[i] = touch_value < touch_thresh; if (!touch_status[i] & touch_status_current[i]) { - ESP_LOGD(ENA_INTERFACE_LOG, "touch %u at %d (thresh %u)", touch_value, touch_id, touch_thresh); - if (touch_callbacks[touch_id] != NULL) + ESP_LOGD(ENA_INTERFACE_LOG, "touch %u at %d (thresh %u)", touch_value, touch_mapping[i], touch_thresh); + if (touch_callbacks[touch_mapping[i]] != NULL) { - (*touch_callbacks[touch_id])(); + (*touch_callbacks[touch_mapping[i]])(); } } touch_status[i] = touch_status_current[i]; @@ -73,22 +72,19 @@ void ena_interface_start(void) for (int i = 0; i < TOUCH_PAD_COUNT; i++) { - int touch_id = touch_mapping[i]; - ESP_ERROR_CHECK(touch_pad_config(touch_id, 0)); + ESP_ERROR_CHECK(touch_pad_config(touch_mapping[i], 0)); } ESP_ERROR_CHECK(touch_pad_filter_start(TOUCHPAD_FILTER_TOUCH_PERIOD)); - uint16_t touch_value; for (int i = 0; i < TOUCH_PAD_COUNT; i++) { - int touch_id = touch_mapping[i]; - ESP_ERROR_CHECK(touch_pad_read_filtered(touch_id, &touch_value)); - ESP_ERROR_CHECK(touch_pad_set_thresh(touch_id, touch_value * 2 / 3)); - ESP_LOGD(ENA_INTERFACE_LOG, "calibrate %u at %u (thresh %u)", touch_id, touch_value, (touch_value * 2 / 3)); + ESP_ERROR_CHECK(touch_pad_read_filtered(touch_mapping[i], &touch_value)); + ESP_ERROR_CHECK(touch_pad_set_thresh(touch_mapping[i], touch_value * 2 / 3)); + ESP_LOGD(ENA_INTERFACE_LOG, "calibrate %u at %u (thresh %u)", touch_mapping[i], touch_value, (touch_value * 2 / 3)); } - xTaskCreate(&ena_interface_run, "ena_interface_run", configMINIMAL_STACK_SIZE * 4, NULL, 5, NULL); + xTaskCreate(&ena_interface_run, "ena_interface_run", 4096, NULL, 5, NULL); } int ena_interface_get_state(void) diff --git a/components/ena-interface/include/ena-interface-menu.h b/components/ena-interface/include/ena-interface-menu.h index 696d362..2293f18 100644 --- a/components/ena-interface/include/ena-interface-menu.h +++ b/components/ena-interface/include/ena-interface-menu.h @@ -18,7 +18,7 @@ typedef enum { ENA_INTERFACE_MENU_STATE_IDLE = 0, ENA_INTERFACE_MENU_STATE_SELECT_TIME, - ENA_INTERFACE_MENU_STATE_SELECT_INFO, + ENA_INTERFACE_MENU_STATE_SELECT_STATUS, } ena_interface_menu_state; void ena_interface_menu_start(void); diff --git a/components/ena-interface/include/ena-interface-status.h b/components/ena-interface/include/ena-interface-status.h new file mode 100644 index 0000000..6c7202d --- /dev/null +++ b/components/ena-interface/include/ena-interface-status.h @@ -0,0 +1,19 @@ +// Copyright 2020 Lukas Haubaum +// +// Licensed under the GNU Affero General Public License, Version 3; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// https://www.gnu.org/licenses/agpl-3.0.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _ena_INTERFACE_STATUS_H_ +#define _ena_INTERFACE_STATUS_H_ + +void ena_interface_status_start(void); + +#endif \ No newline at end of file diff --git a/components/ena-interface/include/ena-interface.h b/components/ena-interface/include/ena-interface.h index 4adea8b..b360cef 100644 --- a/components/ena-interface/include/ena-interface.h +++ b/components/ena-interface/include/ena-interface.h @@ -28,15 +28,10 @@ */ typedef enum { - ENA_INTERFACE_STATE_IDLE = 0, // ilde state, do nothing - ENA_INTERFACE_STATE_MENU, // main menu - ENA_INTERFACE_STATE_SET_YEAR, // set current year - ENA_INTERFACE_STATE_SET_MONTH, // set current month - ENA_INTERFACE_STATE_SET_DAY, // set current day - ENA_INTERFACE_STATE_SET_HOUR, // set current hour - ENA_INTERFACE_STATE_SET_MINUTE, // set current minute - ENA_INTERFACE_STATE_SET_SECONDS, // set current second - ENA_INTERFACE_STATE_STATUS, // view current status + ENA_INTERFACE_STATE_IDLE = 0, // ilde state, do nothing + ENA_INTERFACE_STATE_MENU, // main menu + ENA_INTERFACE_STATE_SET_DATETIME, // set current date and time + ENA_INTERFACE_STATE_STATUS, // current status } ena_interface_state; /** diff --git a/components/ena/CMakeLists.txt b/components/ena/CMakeLists.txt index efcf715..525c3dd 100644 --- a/components/ena/CMakeLists.txt +++ b/components/ena/CMakeLists.txt @@ -5,10 +5,15 @@ idf_component_register( "ena-bluetooth-advertise.c" "ena-bluetooth-scan.c" "ena-crypto.c" + "ena-exposure.c" "ena-storage.c" INCLUDE_DIRS "include" - PRIV_REQUIRES + PRIV_REQUIRES spi_flash mbedtls bt + nanopb + EMBED_FILES + "test/export.bin" + "test/export.sig" ) \ No newline at end of file diff --git a/components/ena/Kconfig b/components/ena/Kconfig index a655919..2a43f94 100644 --- a/components/ena/Kconfig +++ b/components/ena/Kconfig @@ -14,6 +14,12 @@ menu "Exposure Notification API" help Defines the maximum number of TEKs to be stored. (Default 14 [14 * 144 => 14 days]) + config ENA_STORAGE_EXPOSURE_INFORMATION_MAX + int "Max. exporure information" + default 500 + help + Defines the maximum number of exposure information to be stored. (Default 500) + config ENA_STORAGE_TEMP_BEACONS_MAX int "Max. temporary beacons" default 1000 @@ -81,5 +87,12 @@ menu "Exposure Notification API" Defines the TEK rolling period in 10 minute steps. (Default 144 => 24 hours) endmenu + menu "Miscellaneous" + config ENA_RAM + int "ENA RAM" + default 100000 + help + RAM required for main task. (Default 100 KB) + endmenu endmenu \ No newline at end of file diff --git a/components/ena/ena-beacons.c b/components/ena/ena-beacons.c index 569e579..baa688d 100644 --- a/components/ena/ena-beacons.c +++ b/components/ena/ena-beacons.c @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. #include +#include #include "esp_log.h" @@ -21,17 +22,7 @@ #include "ena-beacons.h" static uint32_t temp_beacons_count = 0; -static ena_temp_beacon_t temp_beacons[ENA_STORAGE_TEMP_BEACONS_MAX]; - -ena_beacon_t ena_beacons_convert(ena_temp_beacon_t temp_beacon) -{ - ena_beacon_t beacon; - memcpy(beacon.rpi, temp_beacon.rpi, ENA_KEY_LENGTH); - memcpy(beacon.aem, temp_beacon.aem, ENA_AEM_METADATA_LENGTH); - beacon.timestamp = temp_beacon.timestamp_last; - beacon.rssi = temp_beacon.rssi; - return beacon; -} +static ena_beacon_t temp_beacons[ENA_STORAGE_TEMP_BEACONS_MAX]; int ena_get_temp_beacon_index(uint8_t *rpi, uint8_t *aem) { @@ -55,8 +46,7 @@ void ena_beacons_temp_refresh(uint32_t unix_timestamp) { ESP_LOGD(ENA_BEACON_LOG, "create beacon after treshold"); ESP_LOG_BUFFER_HEXDUMP(ENA_BEACON_LOG, temp_beacons[i].rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); - ena_beacon_t beacon = ena_beacons_convert(temp_beacons[i]); - ena_storage_add_beacon(&beacon); + ena_storage_add_beacon(&temp_beacons[i]); ena_storage_remove_temp_beacon(i); } else @@ -77,7 +67,8 @@ void ena_beacons_temp_refresh(uint32_t unix_timestamp) #if (CONFIG_ENA_STORAGE_DUMP) // DEBUG dump - ena_storage_dump_tek(); + ena_storage_dump_teks(); + ena_storage_dump_exposure_information(); ena_storage_dump_temp_beacons(); ena_storage_dump_beacons(); #endif @@ -86,7 +77,6 @@ void ena_beacons_temp_refresh(uint32_t unix_timestamp) void ena_beacon(uint32_t unix_timestamp, uint8_t *rpi, uint8_t *aem, int rssi) { uint32_t beacon_index = ena_get_temp_beacon_index(rpi, aem); - if (beacon_index == -1) { temp_beacons[temp_beacons_count].timestamp_first = unix_timestamp; @@ -95,8 +85,7 @@ void ena_beacon(uint32_t unix_timestamp, uint8_t *rpi, uint8_t *aem, int rssi) temp_beacons[temp_beacons_count].rssi = rssi; temp_beacons[temp_beacons_count].timestamp_last = unix_timestamp; beacon_index = ena_storage_add_temp_beacon(&temp_beacons[temp_beacons_count]); - - ESP_LOGD(ENA_BEACON_LOG, "New temporary beacon at %d with timestamp %u", temp_beacons_count, unix_timestamp); + ESP_LOGD(ENA_BEACON_LOG, "new temporary beacon %d at %u", temp_beacons_count, unix_timestamp); ESP_LOG_BUFFER_HEX_LEVEL(ENA_BEACON_LOG, rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); ESP_LOG_BUFFER_HEX_LEVEL(ENA_BEACON_LOG, aem, ENA_AEM_METADATA_LENGTH, ESP_LOG_DEBUG); ESP_LOGD(ENA_BEACON_LOG, "RSSI %d", rssi); @@ -111,9 +100,10 @@ void ena_beacon(uint32_t unix_timestamp, uint8_t *rpi, uint8_t *aem, int rssi) { temp_beacons[beacon_index].rssi = (temp_beacons[beacon_index].rssi + rssi) / 2; temp_beacons[beacon_index].timestamp_last = unix_timestamp; - ESP_LOGD(ENA_BEACON_LOG, "New Timestamp for temporary beacon %d: %u", beacon_index, unix_timestamp); - ESP_LOG_BUFFER_HEX_LEVEL(ENA_BEACON_LOG, rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); - ESP_LOG_BUFFER_HEX_LEVEL(ENA_BEACON_LOG, aem, ENA_AEM_METADATA_LENGTH, ESP_LOG_DEBUG); + ESP_LOGD(ENA_BEACON_LOG, "update temporary beacon %d at %u", beacon_index, unix_timestamp); + ESP_LOG_BUFFER_HEX_LEVEL(ENA_BEACON_LOG, temp_beacons[beacon_index].rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); + ESP_LOG_BUFFER_HEX_LEVEL(ENA_BEACON_LOG, temp_beacons[beacon_index].aem, ENA_AEM_METADATA_LENGTH, ESP_LOG_DEBUG); + ESP_LOGD(ENA_BEACON_LOG, "RSSI %d", temp_beacons[beacon_index].rssi); ena_storage_set_temp_beacon(temp_beacons_count, &temp_beacons[temp_beacons_count]); } } \ No newline at end of file diff --git a/components/ena/ena-exposure.c b/components/ena/ena-exposure.c new file mode 100644 index 0000000..33e067f --- /dev/null +++ b/components/ena/ena-exposure.c @@ -0,0 +1,280 @@ +// Copyright 2020 Lukas Haubaum +// +// Licensed under the GNU Affero General Public License, Version 3; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// https://www.gnu.org/licenses/agpl-3.0.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include + +#include "esp_log.h" + +#include "ena-crypto.h" +#include "ena-storage.h" +#include "ena-beacons.h" + +#include "ena-exposure.h" + +static ena_exposure_config_t DEFAULT_ENA_EXPOSURE_CONFIG = { + // transmission_risk_values + { + MINIMAL, // UNKNOWN + LOW, // CONFIRMED_TEST_LOW + MEDIUM, // CONFIRMED_TEST_STANDARD + HIGH, // CONFIRMED_TEST_HIGH + VERY_HIGH, // CONFIRMED_CLINICAL_DIAGNOSIS + VERY_LOW, // SELF_REPORT + ZERO, // NEGATIVE + MINIMAL // RECURSIVE + }, + // duration_risk_values + { + MINIMAL, // D = 0 min + MINIMAL, // D <= 5 min + MEDIUM, // D <= 10 min + VERY_HIGH, // D <= 15 min + VERY_HIGH, // D <= 20 min + MAXIMUM, // D <= 25 min + MAXIMUM, // D <= 30 min + MAXIMUM // D > 30 min + }, + // days_risk_values + { + MINIMAL, // >= 14 days + VERY_LOW, // 12-13 days + VERY_LOW, // 10-11 days + MEDIUM, // 8-9 days + HIGH, // 6-7 days + MAXIMUM, // 4-5 days + MAXIMUM, // 2-3 days + MAXIMUM // 0-1 days + }, + // attenuation_risk_values + { + MINIMAL, // A > 73 dB + MINIMAL, // 73 >= A > 63 + MINIMAL, // 63 >= A > 61 + MAXIMUM, // 51 >= A > 33 + MAXIMUM, // 33 >= A > 27 + MAXIMUM, // 27 >= A > 15 + MAXIMUM, // 15 >= A > 10 + MAXIMUM // A <= 10 + }, +}; + +static const char kFileHeader[] = "EK Export v1 "; +static size_t kFileHeaderSize = sizeof(kFileHeader) - 1; + +extern const uint8_t export_bin_start[] asm("_binary_export_bin_start"); +extern const uint8_t export_bin_end[] asm("_binary_export_bin_end"); + +void ena_exposure_keyfiletest(void) +{ + ESP_LOG_BUFFER_HEXDUMP(ENA_EXPOSURE_LOG, export_bin_start, (export_bin_end - export_bin_start), ESP_LOG_INFO); +} + +void ena_exposure_check(ena_tek_reported_t tek_reported) +{ + bool match = false; + ena_beacon_t beacon; + ena_exposure_information_t exposure_info; + exposure_info.duration_minutes = 0; + exposure_info.min_attenuation = INT_MAX; + exposure_info.typical_attenuation = 0; + exposure_info.report_type = tek_reported.report_type; + uint8_t rpi[ENA_KEY_LENGTH]; + uint8_t rpik[ENA_KEY_LENGTH]; + ena_crypto_rpik(rpik, tek_reported.key_data); + uint32_t beacons_count = ena_storage_beacons_count(); + for (int i = 0; i < tek_reported.rolling_period; i++) + { + ena_crypto_rpi(rpi, rpik, tek_reported.rolling_start_interval_number + i); + for (int y = 0; y < beacons_count; y++) + { + ena_storage_get_beacon(y, &beacon); + if (memcmp(beacon.rpi, rpi, sizeof(ENA_KEY_LENGTH)) == 0) + { + match = true; + exposure_info.day = tek_reported.rolling_start_interval_number * ENA_TIME_WINDOW; + exposure_info.duration_minutes += (ENA_BEACON_TRESHOLD / 60); + exposure_info.typical_attenuation = (exposure_info.typical_attenuation + beacon.rssi) / 2; + if (beacon.rssi < exposure_info.min_attenuation) + { + exposure_info.min_attenuation = beacon.rssi; + } + } + } + } + + if (match) + { + ena_storage_add_exposure_information(&exposure_info); + } +} + +int ena_exposure_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params) +{ + int score = 1; + score *= config->transmission_risk_values[params.report_type]; + + // calc duration level + int duration_level = MINUTES_0; + if (params.duration > 0) + { + if (params.duration <= 5) + { + duration_level = MINUTES_5; + } + else if (params.duration <= 10) + { + duration_level = MINUTES_10; + } + if (params.duration <= 15) + { + duration_level = MINUTES_15; + } + if (params.duration <= 20) + { + duration_level = MINUTES_20; + } + if (params.duration <= 25) + { + duration_level = MINUTES_25; + } + if (params.duration <= 30) + { + duration_level = MINUTES_30; + } + else + { + duration_level = MINUTES_LONGER; + } + } + score *= config->duration_risk_values[duration_level]; + + // calc days level + int days_level = DAYS_14; + + if (params.days < 2) + { + days_level = DAYS_0; + } + else if (params.days < 4) + { + days_level = DAYS_3; + } + else if (params.days < 6) + { + days_level = DAYS_5; + } + else if (params.days < 8) + { + days_level = DAYS_7; + } + else if (params.days < 10) + { + days_level = DAYS_9; + } + else if (params.days < 12) + { + days_level = DAYS_11; + } + else if (params.days < 14) + { + days_level = DAYS_13; + } + + score *= config->days_risk_values[days_level]; + + // calc attenuation level + int attenuation_level = ATTENUATION_73; + + if (params.attenuation <= 10) + { + attenuation_level = ATTENUATION_LOWER; + } + else if (params.attenuation <= 15) + { + attenuation_level = ATTENUATION_10; + } + else if (params.attenuation <= 27) + { + attenuation_level = ATTENUATION_15; + } + else if (params.attenuation <= 33) + { + attenuation_level = ATTENUATION_27; + } + else if (params.attenuation <= 51) + { + attenuation_level = ATTENUATION_33; + } + else if (params.attenuation <= 63) + { + attenuation_level = ATTENUATION_51; + } + else if (params.attenuation <= 73) + { + attenuation_level = ATTENUATION_63; + } + + score *= config->attenuation_risk_values[attenuation_level]; + + if (score > 255) + { + score = 255; + } + + return score; +} + +void ena_exposure_summary(ena_exposure_config_t *config, ena_exposure_summary_t *summary) +{ + uint32_t count = ena_storage_exposure_information_count(); + uint32_t current_time = (uint32_t)time(NULL); + + summary->days_since_last_exposure = INT_MAX; + summary->max_risk_score = 0; + summary->risk_score_sum = 0; + summary->num_exposures = count; + + if (count == 0) + { + summary->days_since_last_exposure = -1; + } + + ena_exposure_information_t exposure_info; + ena_exposure_parameter_t params; + for (int i = 0; i < count; i++) + { + ena_storage_get_exposure_information(i, &exposure_info); + params.days = (current_time - exposure_info.day) / (60 * 60 * 24); // difference in days + if (params.days < summary->days_since_last_exposure) + { + summary->days_since_last_exposure = params.days; + } + params.duration = exposure_info.duration_minutes; + params.attenuation = exposure_info.typical_attenuation; + int score = ena_exposure_risk_score(config, params); + if (score > summary->max_risk_score) + { + summary->max_risk_score = score; + } + summary->risk_score_sum += score; + } + + ena_exposure_keyfiletest(); +} + +ena_exposure_config_t *ena_exposure_default_config(void) +{ + return &DEFAULT_ENA_EXPOSURE_CONFIG; +} \ No newline at end of file diff --git a/components/ena/ena-storage.c b/components/ena/ena-storage.c index 77ce6c8..29b6420 100644 --- a/components/ena/ena-storage.c +++ b/components/ena/ena-storage.c @@ -24,14 +24,15 @@ const int ENA_STORAGE_TEK_COUNT_ADDRESS = (ENA_STORAGE_START_ADDRESS); // starting address for TEK COUNT const int ENA_STORAGE_TEK_START_ADDRESS = (ENA_STORAGE_TEK_COUNT_ADDRESS + sizeof(uint32_t)); -const int ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS = (ENA_STORAGE_TEK_START_ADDRESS + sizeof(ena_tek_t) * ENA_STORAGE_TEK_MAX); +const int ENA_STORAGE_EXPOSURE_INFORMATION_COUNT_ADDRESS = (ENA_STORAGE_TEK_START_ADDRESS + sizeof(ena_tek_t) * ENA_STORAGE_TEK_MAX); +const int ENA_STORAGE_EXPOSURE_INFORMATION_START_ADDRESS = (ENA_STORAGE_EXPOSURE_INFORMATION_COUNT_ADDRESS + sizeof(uint32_t)); +const int ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS = (ENA_STORAGE_EXPOSURE_INFORMATION_COUNT_ADDRESS + sizeof(ena_exposure_information_t) * ENA_STORAGE_EXPOSURE_INFORMATION_MAX); const int ENA_STORAGE_TEMP_BEACONS_START_ADDRESS = (ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS + sizeof(uint32_t)); -const int ENA_STORAGE_BEACONS_COUNT_ADDRESS = (ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + sizeof(ena_temp_beacon_t) * ENA_STORAGE_TEMP_BEACONS_MAX); +const int ENA_STORAGE_BEACONS_COUNT_ADDRESS = (ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + sizeof(ena_beacon_t) * ENA_STORAGE_TEMP_BEACONS_MAX); const int ENA_STORAGE_BEACONS_START_ADDRESS = (ENA_STORAGE_BEACONS_COUNT_ADDRESS + sizeof(uint32_t)); void ena_storage_read(size_t address, void *data, size_t size) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_read"); const esp_partition_t *partition = esp_partition_find_first( ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, ENA_STORAGE_PARTITION_NAME); assert(partition); @@ -39,12 +40,10 @@ void ena_storage_read(size_t address, void *data, size_t size) vTaskDelay(1); ESP_LOGD(ENA_STORAGE_LOG, "read data at %u", address); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, data, size, ESP_LOG_DEBUG); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_read"); } void ena_storage_write(size_t address, void *data, size_t size) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_write"); const int block_num = address / BLOCK_SIZE; // check for overflow if (address + size <= (block_num + 1) * BLOCK_SIZE) @@ -90,13 +89,10 @@ void ena_storage_write(size_t address, void *data, size_t size) ena_storage_write(block2_address, data2, data2_size); free(data2); } - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_write"); } void ena_storage_shift_delete(size_t address, size_t end_address, size_t size) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_shift_delete"); - int block_num_start = address / BLOCK_SIZE; // check for overflow if (address + size <= (block_num_start + 1) * BLOCK_SIZE) @@ -146,12 +142,10 @@ void ena_storage_shift_delete(size_t address, size_t end_address, size_t size) ena_storage_shift_delete(block1_address, block2_address, data1_size); ena_storage_shift_delete(block2_address, end_address - data1_size, data2_size); } - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_shift_delete"); } uint32_t ena_storage_read_last_tek(ena_tek_t *tek) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_read_tek"); uint32_t tek_count = 0; ena_storage_read(ENA_STORAGE_TEK_COUNT_ADDRESS, &tek_count, sizeof(uint32_t)); if (tek_count < 1) @@ -163,14 +157,11 @@ uint32_t ena_storage_read_last_tek(ena_tek_t *tek) ESP_LOGD(ENA_STORAGE_LOG, "read last tek %u:", tek->enin); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, tek->key_data, ENA_KEY_LENGTH, ESP_LOG_DEBUG); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_read_tek"); return tek_count; } void ena_storage_write_tek(ena_tek_t *tek) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_write_tek"); - uint32_t tek_count = 0; ena_storage_read(ENA_STORAGE_TEK_COUNT_ADDRESS, &tek_count, sizeof(uint32_t)); uint8_t index = (tek_count % ENA_STORAGE_TEK_MAX); @@ -181,33 +172,49 @@ void ena_storage_write_tek(ena_tek_t *tek) ESP_LOGD(ENA_STORAGE_LOG, "write tek: ENIN %u", tek->enin); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, tek->key_data, ENA_KEY_LENGTH, ESP_LOG_DEBUG); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_write_tek"); +} + +uint32_t ena_storage_exposure_information_count(void) +{ + uint32_t count = 0; + ena_storage_read(ENA_STORAGE_EXPOSURE_INFORMATION_COUNT_ADDRESS, &count, sizeof(uint32_t)); + ESP_LOGD(ENA_STORAGE_LOG, "read exposure information count: %u", count); + return count; +} + +void ena_storage_get_exposure_information(uint32_t index, ena_exposure_information_t *exposure_info) +{ + ena_storage_read(ENA_STORAGE_EXPOSURE_INFORMATION_START_ADDRESS + index * sizeof(ena_exposure_information_t), exposure_info, sizeof(ena_exposure_information_t)); + ESP_LOGD(ENA_STORAGE_LOG, "read exporuse information: day %u, duration %d", exposure_info->day, exposure_info->duration_minutes); +} + +void ena_storage_add_exposure_information(ena_exposure_information_t *exposure_info) +{ + uint32_t count = ena_storage_exposure_information_count(); + ena_storage_write(ENA_STORAGE_EXPOSURE_INFORMATION_START_ADDRESS + count * sizeof(ena_exposure_information_t), exposure_info, sizeof(ena_exposure_information_t)); + count++; + ena_storage_write(ENA_STORAGE_EXPOSURE_INFORMATION_COUNT_ADDRESS, &count, sizeof(uint32_t)); + ESP_LOGD(ENA_STORAGE_LOG, "write exposure info: day %u, duration %d", exposure_info->day, exposure_info->duration_minutes); } uint32_t ena_storage_temp_beacons_count(void) { - - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_temp_beacons_count"); uint32_t count = 0; ena_storage_read(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); - ESP_LOGD(ENA_STORAGE_LOG, "read temp contancts count: %u", count); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_temp_beacons_count"); + ESP_LOGD(ENA_STORAGE_LOG, "read temp beacons count: %u", count); return count; } -void ena_storage_get_temp_beacon(uint32_t index, ena_temp_beacon_t *beacon) +void ena_storage_get_temp_beacon(uint32_t index, ena_beacon_t *beacon) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_read_temp_beacon"); - ena_storage_read(ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + index * sizeof(ena_temp_beacon_t), beacon, sizeof(ena_temp_beacon_t)); + ena_storage_read(ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + index * sizeof(ena_beacon_t), beacon, sizeof(ena_beacon_t)); ESP_LOGD(ENA_STORAGE_LOG, "read temp beacon: first %u, last %u and rssi %d", beacon->timestamp_first, beacon->timestamp_last, beacon->rssi); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->aem, ENA_AEM_METADATA_LENGTH, ESP_LOG_DEBUG); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_read_temp_beacon"); } -uint32_t ena_storage_add_temp_beacon(ena_temp_beacon_t *beacon) +uint32_t ena_storage_add_temp_beacon(ena_beacon_t *beacon) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_add_temp_beacon"); uint32_t count = ena_storage_temp_beacons_count(); // overwrite older temporary beacons?! uint8_t index = count % ENA_STORAGE_TEMP_BEACONS_MAX; @@ -217,73 +224,60 @@ uint32_t ena_storage_add_temp_beacon(ena_temp_beacon_t *beacon) ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->aem, ENA_AEM_METADATA_LENGTH, ESP_LOG_DEBUG); count++; ena_storage_write(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_add_temp_beacon"); return count - 1; } -void ena_storage_set_temp_beacon(uint32_t index, ena_temp_beacon_t *beacon) +void ena_storage_set_temp_beacon(uint32_t index, ena_beacon_t *beacon) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_set_temp_beacon"); - ena_storage_write(ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + index * sizeof(ena_temp_beacon_t), beacon, sizeof(ena_temp_beacon_t)); + ena_storage_write(ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + index * sizeof(ena_beacon_t), beacon, sizeof(ena_beacon_t)); ESP_LOGD(ENA_STORAGE_LOG, "set temp beacon at %u: first %u, last %u and rssi %d", index, beacon->timestamp_first, beacon->timestamp_last, beacon->rssi); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->aem, ENA_AEM_METADATA_LENGTH, ESP_LOG_DEBUG); - - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_set_temp_beacon"); } void ena_storage_remove_temp_beacon(uint32_t index) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_remove_temp_beacon"); uint32_t count = ena_storage_temp_beacons_count(); - size_t address_from = ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + index * sizeof(ena_temp_beacon_t); - size_t address_to = ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + count * sizeof(ena_temp_beacon_t); + size_t address_from = ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + index * sizeof(ena_beacon_t); + size_t address_to = ENA_STORAGE_TEMP_BEACONS_START_ADDRESS + count * sizeof(ena_beacon_t); - ena_storage_shift_delete(address_from, address_to, sizeof(ena_temp_beacon_t)); + ena_storage_shift_delete(address_from, address_to, sizeof(ena_beacon_t)); count--; ena_storage_write(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); ESP_LOGD(ENA_STORAGE_LOG, "remove temp beacon: %u", index); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_remove_temp_beacon"); } uint32_t ena_storage_beacons_count(void) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_beacons_count"); uint32_t count = 0; ena_storage_read(ENA_STORAGE_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); ESP_LOGD(ENA_STORAGE_LOG, "read contancts count: %u", count); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_beacons_count"); return count; } void ena_storage_get_beacon(uint32_t index, ena_beacon_t *beacon) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_read_beacon"); ena_storage_read(ENA_STORAGE_BEACONS_START_ADDRESS + index * sizeof(ena_beacon_t), beacon, sizeof(ena_beacon_t)); - ESP_LOGD(ENA_STORAGE_LOG, "read beacon: timestamp %u and rssi %d", beacon->timestamp, beacon->rssi); + ESP_LOGD(ENA_STORAGE_LOG, "read beacon: first %u, last %u and rssi %d", beacon->timestamp_first, beacon->timestamp_last, beacon->rssi); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->aem, ENA_AEM_METADATA_LENGTH, ESP_LOG_DEBUG); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_read_beacon"); } void ena_storage_add_beacon(ena_beacon_t *beacon) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_write_beacon"); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); uint32_t count = ena_storage_beacons_count(); ena_storage_write(ENA_STORAGE_BEACONS_START_ADDRESS + count * sizeof(ena_beacon_t), beacon, sizeof(ena_beacon_t)); count++; ena_storage_write(ENA_STORAGE_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); - ESP_LOGD(ENA_STORAGE_LOG, "write beacon: timestamp %u and rssi %d", beacon->timestamp, beacon->rssi); + ESP_LOGD(ENA_STORAGE_LOG, "write beacon: first %u, last %u and rssi %d", beacon->timestamp_first, beacon->timestamp_last, beacon->rssi); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, beacon->aem, ENA_AEM_METADATA_LENGTH, ESP_LOG_DEBUG); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_write_beacon"); } void ena_storage_erase(void) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_erase"); const esp_partition_t *partition = esp_partition_find_first( ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, ENA_STORAGE_PARTITION_NAME); assert(partition); @@ -292,23 +286,20 @@ void ena_storage_erase(void) uint32_t count = 0; ena_storage_write(ENA_STORAGE_TEK_COUNT_ADDRESS, &count, sizeof(uint32_t)); + ena_storage_write(ENA_STORAGE_EXPOSURE_INFORMATION_COUNT_ADDRESS, &count, sizeof(uint32_t)); ena_storage_write(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); ena_storage_write(ENA_STORAGE_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); - - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_erase"); } void ena_storage_erase_tek(void) { + uint32_t count = 0; + ena_storage_read(ENA_STORAGE_TEK_COUNT_ADDRESS, &count, sizeof(uint32_t)); + uint32_t stored = ENA_STORAGE_TEK_MAX; - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_erase_teks"); - uint32_t tek_count = 0; - ena_storage_read(ENA_STORAGE_TEK_COUNT_ADDRESS, &tek_count, sizeof(uint32_t)); - uint8_t stored = ENA_STORAGE_TEK_MAX; - - if (tek_count < ENA_STORAGE_TEK_MAX) + if (count < ENA_STORAGE_TEK_MAX) { - stored = tek_count; + stored = count; } size_t size = sizeof(uint32_t) + stored * sizeof(ena_tek_t); @@ -316,12 +307,27 @@ void ena_storage_erase_tek(void) ena_storage_write(ENA_STORAGE_TEK_COUNT_ADDRESS, zeros, size); free(zeros); ESP_LOGI(ENA_STORAGE_LOG, "erased %d teks (size %u at %u)", stored, size, ENA_STORAGE_TEK_COUNT_ADDRESS); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_erase_teks"); +} + +void ena_storage_erase_exposure_information(void) +{ + uint32_t count = ena_storage_exposure_information_count(); + uint32_t stored = ENA_STORAGE_EXPOSURE_INFORMATION_MAX; + + if (count < ENA_STORAGE_EXPOSURE_INFORMATION_MAX) + { + stored = count; + } + + size_t size = sizeof(uint32_t) + stored * sizeof(ena_exposure_information_t); + uint8_t *zeros = calloc(size, sizeof(uint8_t)); + ena_storage_write(ENA_STORAGE_EXPOSURE_INFORMATION_COUNT_ADDRESS, zeros, size); + free(zeros); + ESP_LOGI(ENA_STORAGE_LOG, "erased %d exposure information (size %u at %u)", stored, size, ENA_STORAGE_EXPOSURE_INFORMATION_COUNT_ADDRESS); } void ena_storage_erase_temporary_beacon(void) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_erase_temporary_beacons"); uint32_t beacon_count = 0; ena_storage_read(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, &beacon_count, sizeof(uint32_t)); uint32_t stored = ENA_STORAGE_TEMP_BEACONS_MAX; @@ -331,18 +337,16 @@ void ena_storage_erase_temporary_beacon(void) stored = beacon_count; } - size_t size = sizeof(uint32_t) + stored * sizeof(ena_temp_beacon_t); + size_t size = sizeof(uint32_t) + stored * sizeof(ena_beacon_t); uint8_t *zeros = calloc(size, sizeof(uint8_t)); ena_storage_write(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, zeros, size); free(zeros); ESP_LOGI(ENA_STORAGE_LOG, "erased %d temporary beacons (size %u at %u)", stored, size, ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_erase_temporary_beacons"); } void ena_storage_erase_beacon(void) { - ESP_LOGD(ENA_STORAGE_LOG, "START ena_storage_erase_beacon"); uint32_t beacon_count = 0; ena_storage_read(ENA_STORAGE_BEACONS_COUNT_ADDRESS, &beacon_count, sizeof(uint32_t)); @@ -352,7 +356,6 @@ void ena_storage_erase_beacon(void) free(zeros); ESP_LOGI(ENA_STORAGE_LOG, "erased %d beacons (size %u at %u)", beacon_count, size, ENA_STORAGE_BEACONS_COUNT_ADDRESS); - ESP_LOGD(ENA_STORAGE_LOG, "END ena_storage_erase_beacon"); } void ena_storage_dump_hash_array(uint8_t *data, size_t size) @@ -370,12 +373,12 @@ void ena_storage_dump_hash_array(uint8_t *data, size_t size) } } -void ena_storage_dump_tek(void) +void ena_storage_dump_teks(void) { ena_tek_t tek; uint32_t tek_count = 0; ena_storage_read(ENA_STORAGE_TEK_COUNT_ADDRESS, &tek_count, sizeof(uint32_t)); - uint8_t stored = ENA_STORAGE_TEK_MAX; + uint32_t stored = ENA_STORAGE_TEK_MAX; if (tek_count < ENA_STORAGE_TEK_MAX) { @@ -395,9 +398,31 @@ void ena_storage_dump_tek(void) } } +void ena_storage_dump_exposure_information(void) +{ + ena_exposure_information_t exposure_info; + uint32_t exposure_information_count = ena_storage_exposure_information_count(); + uint32_t stored = ENA_STORAGE_EXPOSURE_INFORMATION_MAX; + + if (exposure_information_count < ENA_STORAGE_EXPOSURE_INFORMATION_MAX) + { + stored = exposure_information_count; + } + + ESP_LOGD(ENA_STORAGE_LOG, "%u exposure information (%u stored)\n", exposure_information_count, stored); + printf("#,day,typical_attenuation,min_attenuation,duration_minutes,report_type\n"); + for (int i = 0; i < stored; i++) + { + + size_t address = ENA_STORAGE_EXPOSURE_INFORMATION_START_ADDRESS + i * sizeof(ena_exposure_information_t); + ena_storage_read(address, &exposure_info, sizeof(ena_exposure_information_t)); + printf("%d,%u,%d,%d,%d,%d\n", i, exposure_info.day, exposure_info.typical_attenuation, exposure_info.min_attenuation, exposure_info.duration_minutes, exposure_info.report_type); + } +} + void ena_storage_dump_temp_beacons(void) { - ena_temp_beacon_t beacon; + ena_beacon_t beacon; uint32_t beacon_count = 0; ena_storage_read(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, &beacon_count, sizeof(uint32_t)); uint32_t stored = ENA_STORAGE_TEMP_BEACONS_MAX; @@ -426,11 +451,11 @@ void ena_storage_dump_beacons(void) uint32_t beacon_count = 0; ena_storage_read(ENA_STORAGE_BEACONS_COUNT_ADDRESS, &beacon_count, sizeof(uint32_t)); ESP_LOGD(ENA_STORAGE_LOG, "%u beacons\n", beacon_count); - printf("#,timestamp,rpi,aem,rssi\n"); + printf("#,timestamp_first,timestamp_last,rpi,aem,rssi\n"); for (int i = 0; i < beacon_count; i++) { ena_storage_get_beacon(i, &beacon); - printf("%d,%u,", i, beacon.timestamp); + printf("%d,%u,%u,", i, beacon.timestamp_first, beacon.timestamp_last); ena_storage_dump_hash_array(beacon.rpi, ENA_KEY_LENGTH); printf(","); ena_storage_dump_hash_array(beacon.aem, ENA_AEM_METADATA_LENGTH); diff --git a/components/ena/ena.c b/components/ena/ena.c index 3efac43..a672392 100644 --- a/components/ena/ena.c +++ b/components/ena/ena.c @@ -49,8 +49,8 @@ void ena_next_rpi_timestamp(uint32_t timestamp) void ena_run(void *pvParameter) { - uint32_t unix_timestamp = 0; - uint32_t current_enin = 0; + static uint32_t unix_timestamp = 0; + static uint32_t current_enin = 0; while (1) { unix_timestamp = (uint32_t)time(NULL); @@ -97,6 +97,7 @@ void ena_start(void) #if (CONFIG_ENA_STORAGE_ERASE) ena_storage_erase(); #endif + // init NVS for BLE esp_err_t ret; ret = nvs_flash_init(); @@ -146,7 +147,7 @@ void ena_start(void) ena_crypto_init(); uint32_t unix_timestamp = (uint32_t)time(NULL); - + uint32_t current_enin = ena_crypto_enin(unix_timestamp); uint32_t tek_count = ena_storage_read_last_tek(&last_tek); @@ -172,5 +173,5 @@ void ena_start(void) ena_bluetooth_scan_start(ENA_SCANNING_TIME); // what is a good stack size here? - xTaskCreate(&ena_run, "ena_run", configMINIMAL_STACK_SIZE * 8, NULL, 5, NULL); + xTaskCreate(&ena_run, "ena_run", ENA_RAM, NULL, 5, NULL); } diff --git a/components/ena/include/ena-exposure.h b/components/ena/include/ena-exposure.h new file mode 100644 index 0000000..f3013a3 --- /dev/null +++ b/components/ena/include/ena-exposure.h @@ -0,0 +1,177 @@ +// Copyright 2020 Lukas Haubaum +// +// Licensed under the GNU Affero General Public License, Version 3; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// https://www.gnu.org/licenses/agpl-3.0.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _ena_EXPOSURE_H_ +#define _ena_EXPOSURE_H_ + +#include +#include "ena-crypto.h" + +#define ENA_EXPOSURE_LOG "ESP-ENA-exposure" // TAG for Logging + +/** + * @brief report type + */ +typedef enum +{ + UNKNOWN = 0, + CONFIRMED_TEST_LOW = 1, + CONFIRMED_TEST_STANDARD = 2, + CONFIRMED_TEST_HIGH = 3, + CONFIRMED_CLINICAL_DIAGNOSIS = 4, + SELF_REPORT = 5, + NEGATIVE = 6, + RECURSIVE = 7, +} ena_report_type_t; + +/** + * @brief duration risk + */ +typedef enum +{ + MINUTES_0 = 0, // D = 0 min + MINUTES_5 = 1, // D <= 5 min + MINUTES_10 = 2, // D <= 10 min + MINUTES_15 = 3, // D <= 15 min + MINUTES_20 = 4, // D <= 20 min + MINUTES_25 = 5, // D <= 25 min + MINUTES_30 = 6, // D <= 30 min + MINUTES_LONGER = 7, // D > 30 min +} ena_duration_risk_t; + +/** + * @brief day risk + */ +typedef enum +{ + DAYS_14 = 0, // >= 14 days + DAYS_13 = 1, // 12-13 days + DAYS_11 = 2, // 10-11 days + DAYS_9 = 3, // 8-9 days + DAYS_7 = 4, // 6-7 days + DAYS_5 = 5, // 4-5 days + DAYS_3 = 6, // 2-3 days + DAYS_0 = 7, // 0-1 days +} ena_day_risk_t; + +/** + * @brief attenuation risk + */ +typedef enum +{ + ATTENUATION_73 = 0, // A > 73 dB + ATTENUATION_63 = 1, // 73 >= A > 63 + ATTENUATION_51 = 2, // 63 >= A > 61 + ATTENUATION_33 = 3, // 51 >= A > 33 + ATTENUATION_27 = 4, // 33 >= A > 27 + ATTENUATION_15 = 5, // 27 >= A > 15 + ATTENUATION_10 = 6, // 15 >= A > 10 + ATTENUATION_LOWER = 7, // A <= 10 +} ena_attenuation_risk_t; + +/** + * @brief risk level from 0-8 + */ +typedef enum +{ + ZERO = 0, + MINIMAL = 1, + VERY_LOW = 2, + LOW = 3, + MEDIUM = 4, + INCREASED = 5, + HIGH = 6, + VERY_HIGH = 7, + MAXIMUM = 8, +} ena_risk_level_t; + +/** + * @brief structure for exposure configuration + * + * The exposure configuration is used to calculate the risk score. + */ +typedef struct __attribute__((__packed__)) +{ + uint8_t transmission_risk_values[8]; + uint8_t duration_risk_values[8]; + uint8_t days_risk_values[8]; + uint8_t attenuation_risk_values[8]; +} ena_exposure_config_t; + +/** + * @brief structure for exposure parameter + * + * These parameter are obtained from an exposure information to calculate the risk score. + */ +typedef struct __attribute__((__packed__)) +{ + ena_report_type_t report_type; + int days; + int duration; + int attenuation; +} ena_exposure_parameter_t; + +/** + * @brief structure for exposure summary + * + * This represents the current state of all exposures. + */ +typedef struct __attribute__((__packed__)) +{ + int days_since_last_exposure; // Number of days since the most recent exposure. + int num_exposures; // Number of all exposure information + int max_risk_score; // max. risk score of all exposure information + int risk_score_sum; // sum of all risk_scores +} ena_exposure_summary_t; + +/** + * @brief structure for a reported TEK + */ +typedef struct __attribute__((__packed__)) +{ + uint8_t key_data[ENA_KEY_LENGTH]; // Key of infected user + uint32_t rolling_start_interval_number; // The interval number since epoch for which a key starts + uint8_t rolling_period; // Increments of 10 minutes describing how long a key is valid + ena_report_type_t report_type; // Type of diagnosis associated with a key. + uint32_t days_since_onset_of_symptoms; // Number of days elapsed between symptom onset and the TEK being used. E.g. 2 means TEK is 2 days after onset of symptoms. +} ena_tek_reported_t; + +/** + * @brief check for exposure for a reported tek and store exposure information on finding + * + * @param[in] tek_reported the reported tek to check + */ +void ena_exposure_check(ena_tek_reported_t tek_reported); + +/** + * @brief calculate risk score + * + * @param[in] config the exposure configuration used for calculating score + * @param[in] params the exposure parameter to calculate with + */ +int ena_exposure_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params); + +/** + * @brief returns the current exposure summary + * + * @param[in] config the exposure configuration used for calculating scores + * @param[out] summary pointer to exposure summary to write to + */ +void ena_exposure_summary(ena_exposure_config_t *config, ena_exposure_summary_t *summary); + +/** + * @brief return a default exposure configuration + */ +ena_exposure_config_t *ena_exposure_default_config(void); + +#endif \ No newline at end of file diff --git a/components/ena/include/ena-storage.h b/components/ena/include/ena-storage.h index 847450a..2ee4fd8 100644 --- a/components/ena/include/ena-storage.h +++ b/components/ena/include/ena-storage.h @@ -16,11 +16,12 @@ #include "ena-crypto.h" -#define ENA_STORAGE_LOG "ESP-ENA-storage" // TAG for Logging -#define ENA_STORAGE_PARTITION_NAME (CONFIG_ENA_STORAGE_PARTITION_NAME) // name of partition to use for storing -#define ENA_STORAGE_START_ADDRESS (CONFIG_ENA_STORAGE_START_ADDRESS) // start address of storage -#define ENA_STORAGE_TEK_MAX (CONFIG_ENA_STORAGE_TEK_MAX) // Period of storing TEKs // length of a stored beacon -> RPI keysize + AEM size + 4 Bytes for ENIN + 4 Bytes for RSSI -#define ENA_STORAGE_TEMP_BEACONS_MAX (CONFIG_ENA_STORAGE_TEMP_BEACONS_MAX) // Maximum number of temporary stored beacons +#define ENA_STORAGE_LOG "ESP-ENA-storage" // TAG for Logging +#define ENA_STORAGE_PARTITION_NAME (CONFIG_ENA_STORAGE_PARTITION_NAME) // name of partition to use for storing +#define ENA_STORAGE_START_ADDRESS (CONFIG_ENA_STORAGE_START_ADDRESS) // start address of storage +#define ENA_STORAGE_TEK_MAX (CONFIG_ENA_STORAGE_TEK_MAX) // Period of storing TEKs // length of a stored beacon -> RPI keysize + AEM size + 4 Bytes for ENIN + 4 Bytes for RSSI +#define ENA_STORAGE_TEMP_BEACONS_MAX (CONFIG_ENA_STORAGE_TEMP_BEACONS_MAX) // Maximum number of temporary stored beacons // length of a stored beacon -> RPI keysize + AEM size + 4 Bytes for ENIN + 4 Bytes for RSSI +#define ENA_STORAGE_EXPOSURE_INFORMATION_MAX (CONFIG_ENA_STORAGE_EXPOSURE_INFORMATION_MAX) // Maximum number of stored exposure information /** * @brief structure for TEK @@ -33,7 +34,7 @@ typedef struct __attribute__((__packed__)) } ena_tek_t; /** - * @brief sturcture for storing a temporary beacon + * @brief sturcture for storing a beacons */ typedef struct __attribute__((__packed__)) { @@ -42,18 +43,19 @@ typedef struct __attribute__((__packed__)) uint32_t timestamp_first; // timestamp of first recognition uint32_t timestamp_last; // timestamp of last recognition int rssi; // average measured RSSI -} ena_temp_beacon_t; +} ena_beacon_t; /** - * @brief sturcture for permanently storing a beacon after threshold reached + * @brief structure for storing a Exposure Information (combined ExposureInformation, ExposureWindow and ScanInstance from Google API >= 1.5) */ typedef struct __attribute__((__packed__)) { - uint8_t rpi[ENA_KEY_LENGTH]; // received RPI of beacon - uint8_t aem[ENA_AEM_METADATA_LENGTH]; // received AEM of beacon - uint32_t timestamp; // timestamp of last recognition - int rssi; // average measured RSSI -} ena_beacon_t; + uint32_t day; // Day of the exposure, using UTC, encapsulated as the time of the beginning of that day. + int typical_attenuation; // Aggregation of the attenuations of all of a given diagnosis key's beacons received during the scan, in dB. + int min_attenuation; // Minimum attenuation of all of a given diagnosis key's beacons received during the scan, in dB. + int duration_minutes; //The duration of the exposure in minutes. + int report_type; // Type of diagnosis associated with a key. +} ena_exposure_information_t; /** * @brief read bytes at given address @@ -101,6 +103,29 @@ uint32_t ena_storage_read_last_tek(ena_tek_t *tek); */ void ena_storage_write_tek(ena_tek_t *tek); +/** + * @brief get number of stored exposure information + * + * @return + * total number of exposure information stored + */ +uint32_t ena_storage_exposure_information_count(void); + +/** + * @brief get exposure information at given index + * + * @param[in] index the index of the exposure information to read + * @param[out] exposure_info pointer to exposure information to write to + */ +void ena_storage_get_exposure_information(uint32_t index, ena_exposure_information_t *exposure_info); + +/** + * @brief store exposure information + * + * @param[in] exposure_info new exposure information to store + */ +void ena_storage_add_exposure_information(ena_exposure_information_t *exposure_info); + /** * @brief get number of stored temporary beacons * @@ -113,9 +138,9 @@ uint32_t ena_storage_temp_beacons_count(void); * @brief get temporary beacon at given index * * @param[in] index the index of the temporary beacon to read - * @param[out] beacon pointer to temporary to write to + * @param[out] beacon pointer to temporary beacon to write to */ -void ena_storage_get_temp_beacon(uint32_t index, ena_temp_beacon_t *beacon); +void ena_storage_get_temp_beacon(uint32_t index, ena_beacon_t *beacon); /** * @brief store temporary beacon @@ -125,7 +150,7 @@ void ena_storage_get_temp_beacon(uint32_t index, ena_temp_beacon_t *beacon); * @return * index of new stored beacon */ -uint32_t ena_storage_add_temp_beacon(ena_temp_beacon_t *beacon); +uint32_t ena_storage_add_temp_beacon(ena_beacon_t *beacon); /** * @brief store temporary beacon at given index @@ -133,7 +158,7 @@ uint32_t ena_storage_add_temp_beacon(ena_temp_beacon_t *beacon); * @param[in] index the index of the temporary beacon to overwrite * @param[in] beacon temporary beacon to store */ -void ena_storage_set_temp_beacon(uint32_t index, ena_temp_beacon_t *beacon); +void ena_storage_set_temp_beacon(uint32_t index, ena_beacon_t *beacon); /** * @brief remove temporary beacon at given index @@ -180,6 +205,14 @@ void ena_storage_erase(void); */ void ena_storage_erase_tek(void); + +/** + * @brief erase all stored exposure information + * + * This function deletes all stored exposure information and resets counter to zero. + */ +void ena_storage_erase_exposure_information(void); + /** * @brief erase all stored temporary beacons * @@ -200,7 +233,16 @@ void ena_storage_erase_beacon(void); * This function prints all stored TEKs to serial output in * the following CSV format: #,enin,tek */ -void ena_storage_dump_tek(void); +void ena_storage_dump_teks(void); + + +/** + * @brief dump all stored exposure information to serial output + * + * This function prints all stored exposure information to serial output in + * the following CSV format: #,day,typical_attenuation,min_attenuation,duration_minutes,report_type + */ +void ena_storage_dump_exposure_information(void); /** * @brief dump all stored temporary beacons to serial output diff --git a/components/ena/include/ena.h b/components/ena/include/ena.h index f3d54b4..28a0480 100644 --- a/components/ena/include/ena.h +++ b/components/ena/include/ena.h @@ -15,6 +15,7 @@ #define _ena_H_ #define ENA_LOG "ESP-ENA" // TAG for Logging +#define ENA_RAM (CONFIG_ENA_RAM) // change advertising payload and therefore the BT address #define ENA_BT_ROTATION_TIMEOUT_INTERVAL (CONFIG_ENA_BT_ROTATION_TIMEOUT_INTERVAL) // change advertising payload and therefore the BT address #define ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL (CONFIG_ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL) // random intervall change for BT address change diff --git a/components/ena/test/export.bin b/components/ena/test/export.bin new file mode 100644 index 0000000..0a10445 Binary files /dev/null and b/components/ena/test/export.bin differ diff --git a/components/ena/test/export.sig b/components/ena/test/export.sig new file mode 100644 index 0000000..05a7d19 Binary files /dev/null and b/components/ena/test/export.sig differ diff --git a/components/i2c-main/i2c-main.c b/components/i2c-main/i2c-main.c index 54fb6e5..8e0af99 100644 --- a/components/i2c-main/i2c-main.c +++ b/components/i2c-main/i2c-main.c @@ -31,6 +31,7 @@ void i2c_main_init() .master.clk_speed = I2C_CLK_SPEED}; ESP_ERROR_CHECK(i2c_param_config(I2C_NUM_0, &i2c_config)); ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0)); + i2c_initialized = true; } bool i2c_is_initialized() diff --git a/components/nanopb/CMakeLists.txt b/components/nanopb/CMakeLists.txt new file mode 100644 index 0000000..a68c497 --- /dev/null +++ b/components/nanopb/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register( + SRCS + "pb_common.c" + "pb_decode.c" + "pb_encode.c" + "TemporaryExposureKeyExport.pb.c" + INCLUDE_DIRS "." +) \ No newline at end of file diff --git a/components/nanopb/TemporaryExposureKeyExport.pb.c b/components/nanopb/TemporaryExposureKeyExport.pb.c new file mode 100644 index 0000000..cb565a9 --- /dev/null +++ b/components/nanopb/TemporaryExposureKeyExport.pb.c @@ -0,0 +1,19 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.2 */ + +#include "TemporaryExposureKeyExport.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(TemporaryExposureKeyExport, TemporaryExposureKeyExport, AUTO) + + +PB_BIND(SignatureInfo, SignatureInfo, AUTO) + + +PB_BIND(TemporaryExposureKey, TemporaryExposureKey, AUTO) + + + + diff --git a/components/nanopb/TemporaryExposureKeyExport.pb.h b/components/nanopb/TemporaryExposureKeyExport.pb.h new file mode 100644 index 0000000..dac4f8d --- /dev/null +++ b/components/nanopb/TemporaryExposureKeyExport.pb.h @@ -0,0 +1,147 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.2 */ + +#ifndef PB_TEMPORARYEXPOSUREKEYEXPORT_PB_H_INCLUDED +#define PB_TEMPORARYEXPOSUREKEYEXPORT_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Enum definitions */ +typedef enum _TemporaryExposureKey_ReportType { + TemporaryExposureKey_ReportType_UNKNOWN = 0, + TemporaryExposureKey_ReportType_CONFIRMED_TEST = 1, + TemporaryExposureKey_ReportType_CONFIRMED_CLINICAL_DIAGNOSIS = 2, + TemporaryExposureKey_ReportType_SELF_REPORT = 3, + TemporaryExposureKey_ReportType_RECURSIVE = 4, + TemporaryExposureKey_ReportType_REVOKED = 5 +} TemporaryExposureKey_ReportType; + +/* Struct definitions */ +typedef struct _SignatureInfo { + pb_callback_t verification_key_version; + pb_callback_t verification_key_id; + pb_callback_t signature_algorithm; +} SignatureInfo; + +typedef struct _TemporaryExposureKey { + pb_callback_t key_data; + bool has_transmission_risk_level; + int32_t transmission_risk_level; + bool has_rolling_start_interval_number; + int32_t rolling_start_interval_number; + bool has_rolling_period; + int32_t rolling_period; + bool has_report_type; + TemporaryExposureKey_ReportType report_type; + bool has_days_since_onset_of_symptoms; + int32_t days_since_onset_of_symptoms; +} TemporaryExposureKey; + +typedef struct _TemporaryExposureKeyExport { + bool has_start_timestamp; + uint64_t start_timestamp; + bool has_end_timestamp; + uint64_t end_timestamp; + pb_callback_t region; + bool has_batch_num; + int32_t batch_num; + bool has_batch_size; + int32_t batch_size; + pb_callback_t signature_infos; + pb_callback_t keys; + pb_callback_t revised_keys; +} TemporaryExposureKeyExport; + + +/* Helper constants for enums */ +#define _TemporaryExposureKey_ReportType_MIN TemporaryExposureKey_ReportType_UNKNOWN +#define _TemporaryExposureKey_ReportType_MAX TemporaryExposureKey_ReportType_REVOKED +#define _TemporaryExposureKey_ReportType_ARRAYSIZE ((TemporaryExposureKey_ReportType)(TemporaryExposureKey_ReportType_REVOKED+1)) + + +/* Initializer values for message structs */ +#define TemporaryExposureKeyExport_init_default {false, 0, false, 0, {{NULL}, NULL}, false, 0, false, 0, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} +#define SignatureInfo_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} +#define TemporaryExposureKey_init_default {{{NULL}, NULL}, false, 0, false, 0, false, 144, false, _TemporaryExposureKey_ReportType_MIN, false, 0} +#define TemporaryExposureKeyExport_init_zero {false, 0, false, 0, {{NULL}, NULL}, false, 0, false, 0, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} +#define SignatureInfo_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} +#define TemporaryExposureKey_init_zero {{{NULL}, NULL}, false, 0, false, 0, false, 0, false, _TemporaryExposureKey_ReportType_MIN, false, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define SignatureInfo_verification_key_version_tag 3 +#define SignatureInfo_verification_key_id_tag 4 +#define SignatureInfo_signature_algorithm_tag 5 +#define TemporaryExposureKey_key_data_tag 1 +#define TemporaryExposureKey_transmission_risk_level_tag 2 +#define TemporaryExposureKey_rolling_start_interval_number_tag 3 +#define TemporaryExposureKey_rolling_period_tag 4 +#define TemporaryExposureKey_report_type_tag 5 +#define TemporaryExposureKey_days_since_onset_of_symptoms_tag 6 +#define TemporaryExposureKeyExport_start_timestamp_tag 1 +#define TemporaryExposureKeyExport_end_timestamp_tag 2 +#define TemporaryExposureKeyExport_region_tag 3 +#define TemporaryExposureKeyExport_batch_num_tag 4 +#define TemporaryExposureKeyExport_batch_size_tag 5 +#define TemporaryExposureKeyExport_signature_infos_tag 6 +#define TemporaryExposureKeyExport_keys_tag 7 +#define TemporaryExposureKeyExport_revised_keys_tag 8 + +/* Struct field encoding specification for nanopb */ +#define TemporaryExposureKeyExport_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, FIXED64, start_timestamp, 1) \ +X(a, STATIC, OPTIONAL, FIXED64, end_timestamp, 2) \ +X(a, CALLBACK, OPTIONAL, STRING, region, 3) \ +X(a, STATIC, OPTIONAL, INT32, batch_num, 4) \ +X(a, STATIC, OPTIONAL, INT32, batch_size, 5) \ +X(a, CALLBACK, REPEATED, MESSAGE, signature_infos, 6) \ +X(a, CALLBACK, REPEATED, MESSAGE, keys, 7) \ +X(a, CALLBACK, REPEATED, MESSAGE, revised_keys, 8) +#define TemporaryExposureKeyExport_CALLBACK pb_default_field_callback +#define TemporaryExposureKeyExport_DEFAULT NULL +#define TemporaryExposureKeyExport_signature_infos_MSGTYPE SignatureInfo +#define TemporaryExposureKeyExport_keys_MSGTYPE TemporaryExposureKey +#define TemporaryExposureKeyExport_revised_keys_MSGTYPE TemporaryExposureKey + +#define SignatureInfo_FIELDLIST(X, a) \ +X(a, CALLBACK, OPTIONAL, STRING, verification_key_version, 3) \ +X(a, CALLBACK, OPTIONAL, STRING, verification_key_id, 4) \ +X(a, CALLBACK, OPTIONAL, STRING, signature_algorithm, 5) +#define SignatureInfo_CALLBACK pb_default_field_callback +#define SignatureInfo_DEFAULT NULL + +#define TemporaryExposureKey_FIELDLIST(X, a) \ +X(a, CALLBACK, OPTIONAL, BYTES, key_data, 1) \ +X(a, STATIC, OPTIONAL, INT32, transmission_risk_level, 2) \ +X(a, STATIC, OPTIONAL, INT32, rolling_start_interval_number, 3) \ +X(a, STATIC, OPTIONAL, INT32, rolling_period, 4) \ +X(a, STATIC, OPTIONAL, UENUM, report_type, 5) \ +X(a, STATIC, OPTIONAL, SINT32, days_since_onset_of_symptoms, 6) +#define TemporaryExposureKey_CALLBACK pb_default_field_callback +#define TemporaryExposureKey_DEFAULT (const pb_byte_t*)"\x20\x90\x01\x00" + +extern const pb_msgdesc_t TemporaryExposureKeyExport_msg; +extern const pb_msgdesc_t SignatureInfo_msg; +extern const pb_msgdesc_t TemporaryExposureKey_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define TemporaryExposureKeyExport_fields &TemporaryExposureKeyExport_msg +#define SignatureInfo_fields &SignatureInfo_msg +#define TemporaryExposureKey_fields &TemporaryExposureKey_msg + +/* Maximum encoded size of messages (where known) */ +/* TemporaryExposureKeyExport_size depends on runtime parameters */ +/* SignatureInfo_size depends on runtime parameters */ +/* TemporaryExposureKey_size depends on runtime parameters */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/components/nanopb/TemporaryExposureKeyExport.proto b/components/nanopb/TemporaryExposureKeyExport.proto new file mode 100644 index 0000000..fd1f1b2 --- /dev/null +++ b/components/nanopb/TemporaryExposureKeyExport.proto @@ -0,0 +1,65 @@ +syntax = "proto2"; +message TemporaryExposureKeyExport { + // Time window of keys in this batch based on arrival to server, in UTC seconds. + optional fixed64 start_timestamp = 1; + optional fixed64 end_timestamp = 2; + // Region for which these keys came from, such as country. + optional string region = 3; + // For example, file 2 in batch size of 10. Ordinal, 1-based numbering. + // Note: Not yet supported on iOS. + optional int32 batch_num = 4; + optional int32 batch_size = 5; + // Information about associated signatures + repeated SignatureInfo signature_infos = 6; + // The TemporaryExposureKeys for initial release of keys. + // Keys should be included in this list for initial release, + // whereas revised or revoked keys should go in revised_keys. + repeated TemporaryExposureKey keys = 7; + // TemporaryExposureKeys that have changed status. + // Keys should be included in this list if they have changed status + // or have been revoked. + repeated TemporaryExposureKey revised_keys = 8; +} +message SignatureInfo { + // The first two fields have been deprecated + reserved 1, 2; + reserved "app_bundle_id", "android_package"; + // Key version for rollovers + // Must be in character class [a-zA-Z0-9_]. For example, 'v1' + optional string verification_key_version = 3; + // Alias with which to identify public key to be used for verification + // Must be in character class [a-zA-Z0-9_.] + // For cross-compatibility with Apple, you can use your region's three-digit + // mobile country code (MCC). If your region has more than one MCC, choose the + // one that Apple has configured. + optional string verification_key_id = 4; + // ASN.1 OID for Algorithm Identifier. For example, `1.2.840.10045.4.3.2' + optional string signature_algorithm = 5; +} +message TemporaryExposureKey { + // Key of infected user + optional bytes key_data = 1; + // Varying risk associated with a key depending on diagnosis method + optional int32 transmission_risk_level = 2 [deprecated = true]; + // The interval number since epoch for which a key starts + optional int32 rolling_start_interval_number = 3; + // Increments of 10 minutes describing how long a key is valid + optional int32 rolling_period = 4 + [default = 144]; // defaults to 24 hours + // Data type representing why this key was published. + enum ReportType { + UNKNOWN = 0; // Never returned by the client API. + CONFIRMED_TEST = 1; + CONFIRMED_CLINICAL_DIAGNOSIS = 2; + SELF_REPORT = 3; + RECURSIVE = 4; // Reserved for future use. + REVOKED = 5; // Used to revoke a key, never returned by client API. + } + + // Type of diagnosis associated with a key. + optional ReportType report_type = 5; + + // Number of days elapsed between symptom onset and the TEK being used. + // E.g. 2 means TEK is 2 days after onset of symptoms. + optional sint32 days_since_onset_of_symptoms = 6; +} diff --git a/components/nanopb/pb.h b/components/nanopb/pb.h new file mode 100644 index 0000000..65787c7 --- /dev/null +++ b/components/nanopb/pb.h @@ -0,0 +1,868 @@ +/* Common parts of the nanopb library. Most of these are quite low-level + * stuff. For the high-level interface, see pb_encode.h and pb_decode.h. + */ + +#ifndef PB_H_INCLUDED +#define PB_H_INCLUDED + +/***************************************************************** + * Nanopb compilation time options. You can change these here by * + * uncommenting the lines, or on the compiler command line. * + *****************************************************************/ + +/* Enable support for dynamically allocated fields */ +/* #define PB_ENABLE_MALLOC 1 */ + +/* Define this if your CPU / compiler combination does not support + * unaligned memory access to packed structures. */ +/* #define PB_NO_PACKED_STRUCTS 1 */ + +/* Increase the number of required fields that are tracked. + * A compiler warning will tell if you need this. */ +/* #define PB_MAX_REQUIRED_FIELDS 256 */ + +/* Add support for tag numbers > 65536 and fields larger than 65536 bytes. */ +/* #define PB_FIELD_32BIT 1 */ + +/* Disable support for error messages in order to save some code space. */ +/* #define PB_NO_ERRMSG 1 */ + +/* Disable support for custom streams (support only memory buffers). */ +/* #define PB_BUFFER_ONLY 1 */ + +/* Disable support for 64-bit datatypes, for compilers without int64_t + or to save some code space. */ +/* #define PB_WITHOUT_64BIT 1 */ + +/* Don't encode scalar arrays as packed. This is only to be used when + * the decoder on the receiving side cannot process packed scalar arrays. + * Such example is older protobuf.js. */ +/* #define PB_ENCODE_ARRAYS_UNPACKED 1 */ + +/* Enable conversion of doubles to floats for platforms that do not + * support 64-bit doubles. Most commonly AVR. */ +/* #define PB_CONVERT_DOUBLE_FLOAT 1 */ + +/* Check whether incoming strings are valid UTF-8 sequences. Slows down + * the string processing slightly and slightly increases code size. */ +/* #define PB_VALIDATE_UTF8 1 */ + +/****************************************************************** + * You usually don't need to change anything below this line. * + * Feel free to look around and use the defined macros, though. * + ******************************************************************/ + + +/* Version of the nanopb library. Just in case you want to check it in + * your own program. */ +#define NANOPB_VERSION nanopb-0.4.2 + +/* Include all the system headers needed by nanopb. You will need the + * definitions of the following: + * - strlen, memcpy, memset functions + * - [u]int_least8_t, uint_fast8_t, [u]int_least16_t, [u]int32_t, [u]int64_t + * - size_t + * - bool + * + * If you don't have the standard header files, you can instead provide + * a custom header that defines or includes all this. In that case, + * define PB_SYSTEM_HEADER to the path of this file. + */ +#ifdef PB_SYSTEM_HEADER +#include PB_SYSTEM_HEADER +#else +#include +#include +#include +#include +#include + +#ifdef PB_ENABLE_MALLOC +#include +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Macro for defining packed structures (compiler dependent). + * This just reduces memory requirements, but is not required. + */ +#if defined(PB_NO_PACKED_STRUCTS) + /* Disable struct packing */ +# define PB_PACKED_STRUCT_START +# define PB_PACKED_STRUCT_END +# define pb_packed +#elif defined(__GNUC__) || defined(__clang__) + /* For GCC and clang */ +# define PB_PACKED_STRUCT_START +# define PB_PACKED_STRUCT_END +# define pb_packed __attribute__((packed)) +#elif defined(__ICCARM__) || defined(__CC_ARM) + /* For IAR ARM and Keil MDK-ARM compilers */ +# define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)") +# define PB_PACKED_STRUCT_END _Pragma("pack(pop)") +# define pb_packed +#elif defined(_MSC_VER) && (_MSC_VER >= 1500) + /* For Microsoft Visual C++ */ +# define PB_PACKED_STRUCT_START __pragma(pack(push, 1)) +# define PB_PACKED_STRUCT_END __pragma(pack(pop)) +# define pb_packed +#else + /* Unknown compiler */ +# define PB_PACKED_STRUCT_START +# define PB_PACKED_STRUCT_END +# define pb_packed +#endif + +/* Handly macro for suppressing unreferenced-parameter compiler warnings. */ +#ifndef PB_UNUSED +#define PB_UNUSED(x) (void)(x) +#endif + +/* Harvard-architecture processors may need special attributes for storing + * field information in program memory. */ +#ifndef PB_PROGMEM +#ifdef __AVR__ +#include +#define PB_PROGMEM PROGMEM +#define PB_PROGMEM_READU32(x) pgm_read_dword(&x) +#else +#define PB_PROGMEM +#define PB_PROGMEM_READU32(x) (x) +#endif +#endif + +/* Compile-time assertion, used for checking compatible compilation options. + * If this does not work properly on your compiler, use + * #define PB_NO_STATIC_ASSERT to disable it. + * + * But before doing that, check carefully the error message / place where it + * comes from to see if the error has a real cause. Unfortunately the error + * message is not always very clear to read, but you can see the reason better + * in the place where the PB_STATIC_ASSERT macro was called. + */ +#ifndef PB_NO_STATIC_ASSERT +# ifndef PB_STATIC_ASSERT +# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + /* C11 standard _Static_assert mechanism */ +# define PB_STATIC_ASSERT(COND,MSG) _Static_assert(COND,#MSG); +# else + /* Classic negative-size-array static assert mechanism */ +# define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1]; +# define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) +# define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##_##LINE##_##COUNTER +# endif +# endif +#else + /* Static asserts disabled by PB_NO_STATIC_ASSERT */ +# define PB_STATIC_ASSERT(COND,MSG) +#endif + +/* Number of required fields to keep track of. */ +#ifndef PB_MAX_REQUIRED_FIELDS +#define PB_MAX_REQUIRED_FIELDS 64 +#endif + +#if PB_MAX_REQUIRED_FIELDS < 64 +#error You should not lower PB_MAX_REQUIRED_FIELDS from the default value (64). +#endif + +#ifdef PB_WITHOUT_64BIT +#ifdef PB_CONVERT_DOUBLE_FLOAT +/* Cannot use doubles without 64-bit types */ +#undef PB_CONVERT_DOUBLE_FLOAT +#endif +#endif + +/* List of possible field types. These are used in the autogenerated code. + * Least-significant 4 bits tell the scalar type + * Most-significant 4 bits specify repeated/required/packed etc. + */ + +typedef uint_least8_t pb_type_t; + +/**** Field data types ****/ + +/* Numeric types */ +#define PB_LTYPE_BOOL 0x00U /* bool */ +#define PB_LTYPE_VARINT 0x01U /* int32, int64, enum, bool */ +#define PB_LTYPE_UVARINT 0x02U /* uint32, uint64 */ +#define PB_LTYPE_SVARINT 0x03U /* sint32, sint64 */ +#define PB_LTYPE_FIXED32 0x04U /* fixed32, sfixed32, float */ +#define PB_LTYPE_FIXED64 0x05U /* fixed64, sfixed64, double */ + +/* Marker for last packable field type. */ +#define PB_LTYPE_LAST_PACKABLE 0x05U + +/* Byte array with pre-allocated buffer. + * data_size is the length of the allocated PB_BYTES_ARRAY structure. */ +#define PB_LTYPE_BYTES 0x06U + +/* String with pre-allocated buffer. + * data_size is the maximum length. */ +#define PB_LTYPE_STRING 0x07U + +/* Submessage + * submsg_fields is pointer to field descriptions */ +#define PB_LTYPE_SUBMESSAGE 0x08U + +/* Submessage with pre-decoding callback + * The pre-decoding callback is stored as pb_callback_t right before pSize. + * submsg_fields is pointer to field descriptions */ +#define PB_LTYPE_SUBMSG_W_CB 0x09U + +/* Extension pseudo-field + * The field contains a pointer to pb_extension_t */ +#define PB_LTYPE_EXTENSION 0x0AU + +/* Byte array with inline, pre-allocated byffer. + * data_size is the length of the inline, allocated buffer. + * This differs from PB_LTYPE_BYTES by defining the element as + * pb_byte_t[data_size] rather than pb_bytes_array_t. */ +#define PB_LTYPE_FIXED_LENGTH_BYTES 0x0BU + +/* Number of declared LTYPES */ +#define PB_LTYPES_COUNT 0x0CU +#define PB_LTYPE_MASK 0x0FU + +/**** Field repetition rules ****/ + +#define PB_HTYPE_REQUIRED 0x00U +#define PB_HTYPE_OPTIONAL 0x10U +#define PB_HTYPE_SINGULAR 0x10U +#define PB_HTYPE_REPEATED 0x20U +#define PB_HTYPE_FIXARRAY 0x20U +#define PB_HTYPE_ONEOF 0x30U +#define PB_HTYPE_MASK 0x30U + +/**** Field allocation types ****/ + +#define PB_ATYPE_STATIC 0x00U +#define PB_ATYPE_POINTER 0x80U +#define PB_ATYPE_CALLBACK 0x40U +#define PB_ATYPE_MASK 0xC0U + +#define PB_ATYPE(x) ((x) & PB_ATYPE_MASK) +#define PB_HTYPE(x) ((x) & PB_HTYPE_MASK) +#define PB_LTYPE(x) ((x) & PB_LTYPE_MASK) +#define PB_LTYPE_IS_SUBMSG(x) (PB_LTYPE(x) == PB_LTYPE_SUBMESSAGE || \ + PB_LTYPE(x) == PB_LTYPE_SUBMSG_W_CB) + +/* Data type used for storing sizes of struct fields + * and array counts. + */ +#if defined(PB_FIELD_32BIT) + typedef uint32_t pb_size_t; + typedef int32_t pb_ssize_t; +#else + typedef uint_least16_t pb_size_t; + typedef int_least16_t pb_ssize_t; +#endif +#define PB_SIZE_MAX ((pb_size_t)-1) + +/* Data type for storing encoded data and other byte streams. + * This typedef exists to support platforms where uint8_t does not exist. + * You can regard it as equivalent on uint8_t on other platforms. + */ +typedef uint_least8_t pb_byte_t; + +/* Forward declaration of struct types */ +typedef struct pb_istream_s pb_istream_t; +typedef struct pb_ostream_s pb_ostream_t; +typedef struct pb_field_iter_s pb_field_iter_t; + +/* This structure is used in auto-generated constants + * to specify struct fields. + */ +PB_PACKED_STRUCT_START +typedef struct pb_msgdesc_s pb_msgdesc_t; +struct pb_msgdesc_s { + pb_size_t field_count; + const uint32_t *field_info; + const pb_msgdesc_t * const * submsg_info; + const pb_byte_t *default_value; + + bool (*field_callback)(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field); +} pb_packed; +PB_PACKED_STRUCT_END + +/* Iterator for message descriptor */ +struct pb_field_iter_s { + const pb_msgdesc_t *descriptor; /* Pointer to message descriptor constant */ + void *message; /* Pointer to start of the structure */ + + pb_size_t index; /* Index of the field */ + pb_size_t field_info_index; /* Index to descriptor->field_info array */ + pb_size_t required_field_index; /* Index that counts only the required fields */ + pb_size_t submessage_index; /* Index that counts only submessages */ + + pb_size_t tag; /* Tag of current field */ + pb_size_t data_size; /* sizeof() of a single item */ + pb_size_t array_size; /* Number of array entries */ + pb_type_t type; /* Type of current field */ + + void *pField; /* Pointer to current field in struct */ + void *pData; /* Pointer to current data contents. Different than pField for arrays and pointers. */ + void *pSize; /* Pointer to count/has field */ + + const pb_msgdesc_t *submsg_desc; /* For submessage fields, pointer to field descriptor for the submessage. */ +}; + +/* For compatibility with legacy code */ +typedef pb_field_iter_t pb_field_t; + +/* Make sure that the standard integer types are of the expected sizes. + * Otherwise fixed32/fixed64 fields can break. + * + * If you get errors here, it probably means that your stdint.h is not + * correct for your platform. + */ +#ifndef PB_WITHOUT_64BIT +PB_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), INT64_T_WRONG_SIZE) +PB_STATIC_ASSERT(sizeof(uint64_t) == 2 * sizeof(uint32_t), UINT64_T_WRONG_SIZE) +#endif + +/* This structure is used for 'bytes' arrays. + * It has the number of bytes in the beginning, and after that an array. + * Note that actual structs used will have a different length of bytes array. + */ +#define PB_BYTES_ARRAY_T(n) struct { pb_size_t size; pb_byte_t bytes[n]; } +#define PB_BYTES_ARRAY_T_ALLOCSIZE(n) ((size_t)n + offsetof(pb_bytes_array_t, bytes)) + +struct pb_bytes_array_s { + pb_size_t size; + pb_byte_t bytes[1]; +}; +typedef struct pb_bytes_array_s pb_bytes_array_t; + +/* This structure is used for giving the callback function. + * It is stored in the message structure and filled in by the method that + * calls pb_decode. + * + * The decoding callback will be given a limited-length stream + * If the wire type was string, the length is the length of the string. + * If the wire type was a varint/fixed32/fixed64, the length is the length + * of the actual value. + * The function may be called multiple times (especially for repeated types, + * but also otherwise if the message happens to contain the field multiple + * times.) + * + * The encoding callback will receive the actual output stream. + * It should write all the data in one call, including the field tag and + * wire type. It can write multiple fields. + * + * The callback can be null if you want to skip a field. + */ +typedef struct pb_callback_s pb_callback_t; +struct pb_callback_s { + /* Callback functions receive a pointer to the arg field. + * You can access the value of the field as *arg, and modify it if needed. + */ + union { + bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg); + bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg); + } funcs; + + /* Free arg for use by callback */ + void *arg; +}; + +extern bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field); + +/* Wire types. Library user needs these only in encoder callbacks. */ +typedef enum { + PB_WT_VARINT = 0, + PB_WT_64BIT = 1, + PB_WT_STRING = 2, + PB_WT_32BIT = 5 +} pb_wire_type_t; + +/* Structure for defining the handling of unknown/extension fields. + * Usually the pb_extension_type_t structure is automatically generated, + * while the pb_extension_t structure is created by the user. However, + * if you want to catch all unknown fields, you can also create a custom + * pb_extension_type_t with your own callback. + */ +typedef struct pb_extension_type_s pb_extension_type_t; +typedef struct pb_extension_s pb_extension_t; +struct pb_extension_type_s { + /* Called for each unknown field in the message. + * If you handle the field, read off all of its data and return true. + * If you do not handle the field, do not read anything and return true. + * If you run into an error, return false. + * Set to NULL for default handler. + */ + bool (*decode)(pb_istream_t *stream, pb_extension_t *extension, + uint32_t tag, pb_wire_type_t wire_type); + + /* Called once after all regular fields have been encoded. + * If you have something to write, do so and return true. + * If you do not have anything to write, just return true. + * If you run into an error, return false. + * Set to NULL for default handler. + */ + bool (*encode)(pb_ostream_t *stream, const pb_extension_t *extension); + + /* Free field for use by the callback. */ + const void *arg; +}; + +struct pb_extension_s { + /* Type describing the extension field. Usually you'll initialize + * this to a pointer to the automatically generated structure. */ + const pb_extension_type_t *type; + + /* Destination for the decoded data. This must match the datatype + * of the extension field. */ + void *dest; + + /* Pointer to the next extension handler, or NULL. + * If this extension does not match a field, the next handler is + * automatically called. */ + pb_extension_t *next; + + /* The decoder sets this to true if the extension was found. + * Ignored for encoding. */ + bool found; +}; + +#define pb_extension_init_zero {NULL,NULL,NULL,false} + +/* Memory allocation functions to use. You can define pb_realloc and + * pb_free to custom functions if you want. */ +#ifdef PB_ENABLE_MALLOC +# ifndef pb_realloc +# define pb_realloc(ptr, size) realloc(ptr, size) +# endif +# ifndef pb_free +# define pb_free(ptr) free(ptr) +# endif +#endif + +/* This is used to inform about need to regenerate .pb.h/.pb.c files. */ +#define PB_PROTO_HEADER_VERSION 40 + +/* These macros are used to declare pb_field_t's in the constant array. */ +/* Size of a structure member, in bytes. */ +#define pb_membersize(st, m) (sizeof ((st*)0)->m) +/* Number of entries in an array. */ +#define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0])) +/* Delta from start of one member to the start of another member. */ +#define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2)) + +/* Force expansion of macro value */ +#define PB_EXPAND(x) x + +/* Binding of a message field set into a specific structure */ +#define PB_BIND(msgname, structname, width) \ + const uint32_t structname ## _field_info[] PB_PROGMEM = \ + { \ + msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ ## width, structname) \ + 0 \ + }; \ + const pb_msgdesc_t* const structname ## _submsg_info[] = \ + { \ + msgname ## _FIELDLIST(PB_GEN_SUBMSG_INFO, structname) \ + NULL \ + }; \ + const pb_msgdesc_t structname ## _msg = \ + { \ + 0 msgname ## _FIELDLIST(PB_GEN_FIELD_COUNT, structname), \ + structname ## _field_info, \ + structname ## _submsg_info, \ + msgname ## _DEFAULT, \ + msgname ## _CALLBACK, \ + }; \ + msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ASSERT_ ## width, structname) + +#define PB_GEN_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) +1 + +/* X-macro for generating the entries in struct_field_info[] array. */ +#define PB_GEN_FIELD_INFO_1(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_2(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_4(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_8(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_AUTO(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \ + tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_FIELDINFO_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \ + PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) + +#define PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \ + PB_FIELDINFO_ ## width(tag, type, data_offset, data_size, size_offset, array_size) + +/* X-macro for generating asserts that entries fit in struct_field_info[] array. + * The structure of macros here must match the structure above in PB_GEN_FIELD_INFO_x(), + * but it is not easily reused because of how macro substitutions work. */ +#define PB_GEN_FIELD_INFO_ASSERT_1(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_ASSERT_2(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_ASSERT_4(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_ASSERT_8(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_ASSERT_AUTO(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \ + tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_FIELDINFO_ASSERT_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \ + PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) + +#define PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \ + PB_FIELDINFO_ASSERT_ ## width(tag, type, data_offset, data_size, size_offset, array_size) + +#define PB_DATA_OFFSET_STATIC(htype, structname, fieldname) PB_DO ## htype(structname, fieldname) +#define PB_DATA_OFFSET_POINTER(htype, structname, fieldname) PB_DO ## htype(structname, fieldname) +#define PB_DATA_OFFSET_CALLBACK(htype, structname, fieldname) PB_DO ## htype(structname, fieldname) +#define PB_DO_PB_HTYPE_REQUIRED(structname, fieldname) offsetof(structname, fieldname) +#define PB_DO_PB_HTYPE_SINGULAR(structname, fieldname) offsetof(structname, fieldname) +#define PB_DO_PB_HTYPE_ONEOF(structname, fieldname) offsetof(structname, PB_ONEOF_NAME(FULL, fieldname)) +#define PB_DO_PB_HTYPE_OPTIONAL(structname, fieldname) offsetof(structname, fieldname) +#define PB_DO_PB_HTYPE_REPEATED(structname, fieldname) offsetof(structname, fieldname) +#define PB_DO_PB_HTYPE_FIXARRAY(structname, fieldname) offsetof(structname, fieldname) + +#define PB_SIZE_OFFSET_STATIC(htype, structname, fieldname) PB_SO ## htype(structname, fieldname) +#define PB_SIZE_OFFSET_POINTER(htype, structname, fieldname) PB_SO_PTR ## htype(structname, fieldname) +#define PB_SIZE_OFFSET_CALLBACK(htype, structname, fieldname) PB_SO_CB ## htype(structname, fieldname) +#define PB_SO_PB_HTYPE_REQUIRED(structname, fieldname) 0 +#define PB_SO_PB_HTYPE_SINGULAR(structname, fieldname) 0 +#define PB_SO_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF2(structname, PB_ONEOF_NAME(FULL, fieldname), PB_ONEOF_NAME(UNION, fieldname)) +#define PB_SO_PB_HTYPE_ONEOF2(structname, fullname, unionname) PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) +#define PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) pb_delta(structname, fullname, which_ ## unionname) +#define PB_SO_PB_HTYPE_OPTIONAL(structname, fieldname) pb_delta(structname, fieldname, has_ ## fieldname) +#define PB_SO_PB_HTYPE_REPEATED(structname, fieldname) pb_delta(structname, fieldname, fieldname ## _count) +#define PB_SO_PB_HTYPE_FIXARRAY(structname, fieldname) 0 +#define PB_SO_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 0 +#define PB_SO_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 0 +#define PB_SO_PTR_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname) +#define PB_SO_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 0 +#define PB_SO_PTR_PB_HTYPE_REPEATED(structname, fieldname) PB_SO_PB_HTYPE_REPEATED(structname, fieldname) +#define PB_SO_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_REQUIRED(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_SINGULAR(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname) +#define PB_SO_CB_PB_HTYPE_OPTIONAL(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_REPEATED(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_FIXARRAY(structname, fieldname) 0 + +#define PB_ARRAY_SIZE_STATIC(htype, structname, fieldname) PB_AS ## htype(structname, fieldname) +#define PB_ARRAY_SIZE_POINTER(htype, structname, fieldname) PB_AS_PTR ## htype(structname, fieldname) +#define PB_ARRAY_SIZE_CALLBACK(htype, structname, fieldname) 1 +#define PB_AS_PB_HTYPE_REQUIRED(structname, fieldname) 1 +#define PB_AS_PB_HTYPE_SINGULAR(structname, fieldname) 1 +#define PB_AS_PB_HTYPE_OPTIONAL(structname, fieldname) 1 +#define PB_AS_PB_HTYPE_ONEOF(structname, fieldname) 1 +#define PB_AS_PB_HTYPE_REPEATED(structname, fieldname) pb_arraysize(structname, fieldname) +#define PB_AS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname) +#define PB_AS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_ONEOF(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_REPEATED(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname[0]) + +#define PB_DATA_SIZE_STATIC(htype, structname, fieldname) PB_DS ## htype(structname, fieldname) +#define PB_DATA_SIZE_POINTER(htype, structname, fieldname) PB_DS_PTR ## htype(structname, fieldname) +#define PB_DATA_SIZE_CALLBACK(htype, structname, fieldname) PB_DS_CB ## htype(structname, fieldname) +#define PB_DS_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)) +#define PB_DS_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)[0]) +#define PB_DS_PTR_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0][0]) +#define PB_DS_CB_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_CB_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_CB_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_CB_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)) +#define PB_DS_CB_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_CB_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname) + +#define PB_ONEOF_NAME(type, tuple) PB_EXPAND(PB_ONEOF_NAME_ ## type tuple) +#define PB_ONEOF_NAME_UNION(unionname,membername,fullname) unionname +#define PB_ONEOF_NAME_MEMBER(unionname,membername,fullname) membername +#define PB_ONEOF_NAME_FULL(unionname,membername,fullname) fullname + +#define PB_GEN_SUBMSG_INFO(structname, atype, htype, ltype, fieldname, tag) \ + PB_SUBMSG_INFO_ ## htype(_PB_LTYPE_ ## ltype, structname, fieldname) + +#define PB_SUBMSG_INFO_REQUIRED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SUBMSG_INFO_SINGULAR(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SUBMSG_INFO_OPTIONAL(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SUBMSG_INFO_ONEOF(ltype, structname, fieldname) PB_SUBMSG_INFO_ONEOF2(ltype, structname, PB_ONEOF_NAME(UNION, fieldname), PB_ONEOF_NAME(MEMBER, fieldname)) +#define PB_SUBMSG_INFO_ONEOF2(ltype, structname, unionname, membername) PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) +#define PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) PB_SI ## ltype(structname ## _ ## unionname ## _ ## membername ## _MSGTYPE) +#define PB_SUBMSG_INFO_REPEATED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SUBMSG_INFO_FIXARRAY(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SI_PB_LTYPE_BOOL(t) +#define PB_SI_PB_LTYPE_BYTES(t) +#define PB_SI_PB_LTYPE_DOUBLE(t) +#define PB_SI_PB_LTYPE_ENUM(t) +#define PB_SI_PB_LTYPE_UENUM(t) +#define PB_SI_PB_LTYPE_FIXED32(t) +#define PB_SI_PB_LTYPE_FIXED64(t) +#define PB_SI_PB_LTYPE_FLOAT(t) +#define PB_SI_PB_LTYPE_INT32(t) +#define PB_SI_PB_LTYPE_INT64(t) +#define PB_SI_PB_LTYPE_MESSAGE(t) PB_SUBMSG_DESCRIPTOR(t) +#define PB_SI_PB_LTYPE_MSG_W_CB(t) PB_SUBMSG_DESCRIPTOR(t) +#define PB_SI_PB_LTYPE_SFIXED32(t) +#define PB_SI_PB_LTYPE_SFIXED64(t) +#define PB_SI_PB_LTYPE_SINT32(t) +#define PB_SI_PB_LTYPE_SINT64(t) +#define PB_SI_PB_LTYPE_STRING(t) +#define PB_SI_PB_LTYPE_UINT32(t) +#define PB_SI_PB_LTYPE_UINT64(t) +#define PB_SI_PB_LTYPE_EXTENSION(t) +#define PB_SI_PB_LTYPE_FIXED_LENGTH_BYTES(t) +#define PB_SUBMSG_DESCRIPTOR(t) &(t ## _msg), + +/* The field descriptors use a variable width format, with width of either + * 1, 2, 4 or 8 of 32-bit words. The two lowest bytes of the first byte always + * encode the descriptor size, 6 lowest bits of field tag number, and 8 bits + * of the field type. + * + * Descriptor size is encoded as 0 = 1 word, 1 = 2 words, 2 = 4 words, 3 = 8 words. + * + * Formats, listed starting with the least significant bit of the first word. + * 1 word: [2-bit len] [6-bit tag] [8-bit type] [8-bit data_offset] [4-bit size_offset] [4-bit data_size] + * + * 2 words: [2-bit len] [6-bit tag] [8-bit type] [12-bit array_size] [4-bit size_offset] + * [16-bit data_offset] [12-bit data_size] [4-bit tag>>6] + * + * 4 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit array_size] + * [8-bit size_offset] [24-bit tag>>6] + * [32-bit data_offset] + * [32-bit data_size] + * + * 8 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit reserved] + * [8-bit size_offset] [24-bit tag>>6] + * [32-bit data_offset] + * [32-bit data_size] + * [32-bit array_size] + * [32-bit reserved] + * [32-bit reserved] + * [32-bit reserved] + */ + +#define PB_FIELDINFO_1(tag, type, data_offset, data_size, size_offset, array_size) \ + (0 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(data_offset) & 0xFF) << 16) | \ + (((uint32_t)(size_offset) & 0x0F) << 24) | (((uint32_t)(data_size) & 0x0F) << 28)), + +#define PB_FIELDINFO_2(tag, type, data_offset, data_size, size_offset, array_size) \ + (1 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFF) << 16) | (((uint32_t)(size_offset) & 0x0F) << 28)), \ + (((uint32_t)(data_offset) & 0xFFFF) | (((uint32_t)(data_size) & 0xFFF) << 16) | (((uint32_t)(tag) & 0x3c0) << 22)), + +#define PB_FIELDINFO_4(tag, type, data_offset, data_size, size_offset, array_size) \ + (2 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFFF) << 16)), \ + ((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \ + (data_offset), (data_size), + +#define PB_FIELDINFO_8(tag, type, data_offset, data_size, size_offset, array_size) \ + (3 | (((tag) << 2) & 0xFF) | ((type) << 8)), \ + ((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \ + (data_offset), (data_size), (array_size), 0, 0, 0, + +/* These assertions verify that the field information fits in the allocated space. + * The generator tries to automatically determine the correct width that can fit all + * data associated with a message. These asserts will fail only if there has been a + * problem in the automatic logic - this may be worth reporting as a bug. As a workaround, + * you can increase the descriptor width by defining PB_FIELDINFO_WIDTH or by setting + * descriptorsize option in .options file. + */ +#define PB_FITS(value,bits) ((uint32_t)(value) < ((uint32_t)1<2GB messages with nanopb anyway. + */ +#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \ + PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width4_field ## tag) + +#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \ + PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,31), FIELDINFO_DOES_NOT_FIT_width8_field ## tag) +#endif + + +/* Automatic picking of FIELDINFO width: + * Uses width 1 when possible, otherwise resorts to width 2. + * This is used when PB_BIND() is called with "AUTO" as the argument. + * The generator will give explicit size argument when it knows that a message + * structure grows beyond 1-word format limits. + */ +#define PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype) PB_FI_WIDTH ## atype(htype, ltype) +#define PB_FI_WIDTH_PB_ATYPE_STATIC(htype, ltype) PB_FI_WIDTH ## htype(ltype) +#define PB_FI_WIDTH_PB_ATYPE_POINTER(htype, ltype) PB_FI_WIDTH ## htype(ltype) +#define PB_FI_WIDTH_PB_ATYPE_CALLBACK(htype, ltype) 2 +#define PB_FI_WIDTH_PB_HTYPE_REQUIRED(ltype) PB_FI_WIDTH ## ltype +#define PB_FI_WIDTH_PB_HTYPE_SINGULAR(ltype) PB_FI_WIDTH ## ltype +#define PB_FI_WIDTH_PB_HTYPE_OPTIONAL(ltype) PB_FI_WIDTH ## ltype +#define PB_FI_WIDTH_PB_HTYPE_ONEOF(ltype) PB_FI_WIDTH ## ltype +#define PB_FI_WIDTH_PB_HTYPE_REPEATED(ltype) 2 +#define PB_FI_WIDTH_PB_HTYPE_FIXARRAY(ltype) 2 +#define PB_FI_WIDTH_PB_LTYPE_BOOL 1 +#define PB_FI_WIDTH_PB_LTYPE_BYTES 2 +#define PB_FI_WIDTH_PB_LTYPE_DOUBLE 1 +#define PB_FI_WIDTH_PB_LTYPE_ENUM 1 +#define PB_FI_WIDTH_PB_LTYPE_UENUM 1 +#define PB_FI_WIDTH_PB_LTYPE_FIXED32 1 +#define PB_FI_WIDTH_PB_LTYPE_FIXED64 1 +#define PB_FI_WIDTH_PB_LTYPE_FLOAT 1 +#define PB_FI_WIDTH_PB_LTYPE_INT32 1 +#define PB_FI_WIDTH_PB_LTYPE_INT64 1 +#define PB_FI_WIDTH_PB_LTYPE_MESSAGE 2 +#define PB_FI_WIDTH_PB_LTYPE_MSG_W_CB 2 +#define PB_FI_WIDTH_PB_LTYPE_SFIXED32 1 +#define PB_FI_WIDTH_PB_LTYPE_SFIXED64 1 +#define PB_FI_WIDTH_PB_LTYPE_SINT32 1 +#define PB_FI_WIDTH_PB_LTYPE_SINT64 1 +#define PB_FI_WIDTH_PB_LTYPE_STRING 2 +#define PB_FI_WIDTH_PB_LTYPE_UINT32 1 +#define PB_FI_WIDTH_PB_LTYPE_UINT64 1 +#define PB_FI_WIDTH_PB_LTYPE_EXTENSION 1 +#define PB_FI_WIDTH_PB_LTYPE_FIXED_LENGTH_BYTES 2 + +/* The mapping from protobuf types to LTYPEs is done using these macros. */ +#define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL +#define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES +#define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64 +#define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT +#define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT +#define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32 +#define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64 +#define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32 +#define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT +#define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT +#define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE +#define PB_LTYPE_MAP_MSG_W_CB PB_LTYPE_SUBMSG_W_CB +#define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32 +#define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64 +#define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT +#define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT +#define PB_LTYPE_MAP_STRING PB_LTYPE_STRING +#define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT +#define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT +#define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION +#define PB_LTYPE_MAP_FIXED_LENGTH_BYTES PB_LTYPE_FIXED_LENGTH_BYTES + +/* These macros are used for giving out error messages. + * They are mostly a debugging aid; the main error information + * is the true/false return value from functions. + * Some code space can be saved by disabling the error + * messages if not used. + * + * PB_SET_ERROR() sets the error message if none has been set yet. + * msg must be a constant string literal. + * PB_GET_ERROR() always returns a pointer to a string. + * PB_RETURN_ERROR() sets the error and returns false from current + * function. + */ +#ifdef PB_NO_ERRMSG +#define PB_SET_ERROR(stream, msg) PB_UNUSED(stream) +#define PB_GET_ERROR(stream) "(errmsg disabled)" +#else +#define PB_SET_ERROR(stream, msg) (stream->errmsg = (stream)->errmsg ? (stream)->errmsg : (msg)) +#define PB_GET_ERROR(stream) ((stream)->errmsg ? (stream)->errmsg : "(none)") +#endif + +#define PB_RETURN_ERROR(stream, msg) return PB_SET_ERROR(stream, msg), false + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#ifdef __cplusplus +#if __cplusplus >= 201103L +#define PB_CONSTEXPR constexpr +#else // __cplusplus >= 201103L +#define PB_CONSTEXPR +#endif // __cplusplus >= 201103L + +#if __cplusplus >= 201703L +#define PB_INLINE_CONSTEXPR inline constexpr +#else // __cplusplus >= 201703L +#define PB_INLINE_CONSTEXPR PB_CONSTEXPR +#endif // __cplusplus >= 201703L + +namespace nanopb { +// Each type will be partially specialized by the generator. +template struct MessageDescriptor; +} // namespace nanopb +#endif /* __cplusplus */ + +#endif + diff --git a/components/nanopb/pb_common.c b/components/nanopb/pb_common.c new file mode 100644 index 0000000..911ae4c --- /dev/null +++ b/components/nanopb/pb_common.c @@ -0,0 +1,345 @@ +/* pb_common.c: Common support functions for pb_encode.c and pb_decode.c. + * + * 2014 Petteri Aimonen + */ + +#include "pb_common.h" + +static bool load_descriptor_values(pb_field_iter_t *iter) +{ + uint32_t word0; + uint32_t data_offset; + uint_least8_t format; + int_least8_t size_offset; + + if (iter->index >= iter->descriptor->field_count) + return false; + + word0 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); + format = word0 & 3; + iter->tag = (pb_size_t)((word0 >> 2) & 0x3F); + iter->type = (pb_type_t)((word0 >> 8) & 0xFF); + + if (format == 0) + { + /* 1-word format */ + iter->array_size = 1; + size_offset = (int_least8_t)((word0 >> 24) & 0x0F); + data_offset = (word0 >> 16) & 0xFF; + iter->data_size = (pb_size_t)((word0 >> 28) & 0x0F); + } + else if (format == 1) + { + /* 2-word format */ + uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]); + + iter->array_size = (pb_size_t)((word0 >> 16) & 0x0FFF); + iter->tag = (pb_size_t)(iter->tag | ((word1 >> 28) << 6)); + size_offset = (int_least8_t)((word0 >> 28) & 0x0F); + data_offset = word1 & 0xFFFF; + iter->data_size = (pb_size_t)((word1 >> 16) & 0x0FFF); + } + else if (format == 2) + { + /* 4-word format */ + uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]); + uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]); + uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]); + + iter->array_size = (pb_size_t)(word0 >> 16); + iter->tag = (pb_size_t)(iter->tag | ((word1 >> 8) << 6)); + size_offset = (int_least8_t)(word1 & 0xFF); + data_offset = word2; + iter->data_size = (pb_size_t)word3; + } + else + { + /* 8-word format */ + uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]); + uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]); + uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]); + uint32_t word4 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 4]); + + iter->array_size = (pb_size_t)word4; + iter->tag = (pb_size_t)(iter->tag | ((word1 >> 8) << 6)); + size_offset = (int_least8_t)(word1 & 0xFF); + data_offset = word2; + iter->data_size = (pb_size_t)word3; + } + + if (!iter->message) + { + /* Avoid doing arithmetic on null pointers, it is undefined */ + iter->pField = NULL; + iter->pSize = NULL; + } + else + { + iter->pField = (char*)iter->message + data_offset; + + if (size_offset) + { + iter->pSize = (char*)iter->pField - size_offset; + } + else if (PB_HTYPE(iter->type) == PB_HTYPE_REPEATED && + (PB_ATYPE(iter->type) == PB_ATYPE_STATIC || + PB_ATYPE(iter->type) == PB_ATYPE_POINTER)) + { + /* Fixed count array */ + iter->pSize = &iter->array_size; + } + else + { + iter->pSize = NULL; + } + + if (PB_ATYPE(iter->type) == PB_ATYPE_POINTER && iter->pField != NULL) + { + iter->pData = *(void**)iter->pField; + } + else + { + iter->pData = iter->pField; + } + } + + if (PB_LTYPE_IS_SUBMSG(iter->type)) + { + iter->submsg_desc = iter->descriptor->submsg_info[iter->submessage_index]; + } + else + { + iter->submsg_desc = NULL; + } + + return true; +} + +static void advance_iterator(pb_field_iter_t *iter) +{ + iter->index++; + + if (iter->index >= iter->descriptor->field_count) + { + /* Restart */ + iter->index = 0; + iter->field_info_index = 0; + iter->submessage_index = 0; + iter->required_field_index = 0; + } + else + { + /* Increment indexes based on previous field type. + * All field info formats have the following fields: + * - lowest 2 bits tell the amount of words in the descriptor (2^n words) + * - bits 2..7 give the lowest bits of tag number. + * - bits 8..15 give the field type. + */ + uint32_t prev_descriptor = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); + pb_type_t prev_type = (prev_descriptor >> 8) & 0xFF; + pb_size_t descriptor_len = (pb_size_t)(1 << (prev_descriptor & 3)); + + iter->field_info_index = (pb_size_t)(iter->field_info_index + descriptor_len); + + if (PB_HTYPE(prev_type) == PB_HTYPE_REQUIRED) + { + iter->required_field_index++; + } + + if (PB_LTYPE_IS_SUBMSG(prev_type)) + { + iter->submessage_index++; + } + } +} + +bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message) +{ + memset(iter, 0, sizeof(*iter)); + + iter->descriptor = desc; + iter->message = message; + + return load_descriptor_values(iter); +} + +bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension) +{ + const pb_msgdesc_t *msg = (const pb_msgdesc_t*)extension->type->arg; + bool status; + + uint32_t word0 = PB_PROGMEM_READU32(msg->field_info[0]); + if (PB_ATYPE(word0 >> 8) == PB_ATYPE_POINTER) + { + /* For pointer extensions, the pointer is stored directly + * in the extension structure. This avoids having an extra + * indirection. */ + status = pb_field_iter_begin(iter, msg, &extension->dest); + } + else + { + status = pb_field_iter_begin(iter, msg, extension->dest); + } + + iter->pSize = &extension->found; + return status; +} + +bool pb_field_iter_next(pb_field_iter_t *iter) +{ + advance_iterator(iter); + (void)load_descriptor_values(iter); + return iter->index != 0; +} + +bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag) +{ + if (iter->tag == tag) + { + return true; /* Nothing to do, correct field already. */ + } + else + { + pb_size_t start = iter->index; + uint32_t fieldinfo; + + do + { + /* Advance iterator but don't load values yet */ + advance_iterator(iter); + + /* Do fast check for tag number match */ + fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); + + if (((fieldinfo >> 2) & 0x3F) == (tag & 0x3F)) + { + /* Good candidate, check further */ + (void)load_descriptor_values(iter); + + if (iter->tag == tag && + PB_LTYPE(iter->type) != PB_LTYPE_EXTENSION) + { + /* Found it */ + return true; + } + } + } while (iter->index != start); + + /* Searched all the way back to start, and found nothing. */ + (void)load_descriptor_values(iter); + return false; + } +} + +static void *pb_const_cast(const void *p) +{ + /* Note: this casts away const, in order to use the common field iterator + * logic for both encoding and decoding. The cast is done using union + * to avoid spurious compiler warnings. */ + union { + void *p1; + const void *p2; + } t; + t.p2 = p; + return t.p1; +} + +bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message) +{ + return pb_field_iter_begin(iter, desc, pb_const_cast(message)); +} + +bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension) +{ + return pb_field_iter_begin_extension(iter, (pb_extension_t*)pb_const_cast(extension)); +} + +bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field) +{ + if (field->data_size == sizeof(pb_callback_t)) + { + pb_callback_t *pCallback = (pb_callback_t*)field->pData; + + if (pCallback != NULL) + { + if (istream != NULL && pCallback->funcs.decode != NULL) + { + return pCallback->funcs.decode(istream, field, &pCallback->arg); + } + + if (ostream != NULL && pCallback->funcs.encode != NULL) + { + return pCallback->funcs.encode(ostream, field, &pCallback->arg); + } + } + } + + return true; /* Success, but didn't do anything */ + +} + +#ifdef PB_VALIDATE_UTF8 + +/* This function checks whether a string is valid UTF-8 text. + * + * Algorithm is adapted from https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c + * Original copyright: Markus Kuhn 2005-03-30 + * Licensed under "Short code license", which allows use under MIT license or + * any compatible with it. + */ + +bool pb_validate_utf8(const char *str) +{ + const pb_byte_t *s = (const pb_byte_t*)str; + while (*s) + { + if (*s < 0x80) + { + /* 0xxxxxxx */ + s++; + } + else if ((s[0] & 0xe0) == 0xc0) + { + /* 110XXXXx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + (s[0] & 0xfe) == 0xc0) /* overlong? */ + return false; + else + s += 2; + } + else if ((s[0] & 0xf0) == 0xe0) + { + /* 1110XXXX 10Xxxxxx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */ + (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */ + (s[0] == 0xef && s[1] == 0xbf && + (s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */ + return false; + else + s += 3; + } + else if ((s[0] & 0xf8) == 0xf0) + { + /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + (s[3] & 0xc0) != 0x80 || + (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */ + (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) /* > U+10FFFF? */ + return false; + else + s += 4; + } + else + { + return false; + } + } + + return true; +} + +#endif + diff --git a/components/nanopb/pb_common.h b/components/nanopb/pb_common.h new file mode 100644 index 0000000..47fa2c9 --- /dev/null +++ b/components/nanopb/pb_common.h @@ -0,0 +1,45 @@ +/* pb_common.h: Common support functions for pb_encode.c and pb_decode.c. + * These functions are rarely needed by applications directly. + */ + +#ifndef PB_COMMON_H_INCLUDED +#define PB_COMMON_H_INCLUDED + +#include "pb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initialize the field iterator structure to beginning. + * Returns false if the message type is empty. */ +bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message); + +/* Get a field iterator for extension field. */ +bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension); + +/* Same as pb_field_iter_begin(), but for const message pointer. + * Note that the pointers in pb_field_iter_t will be non-const but shouldn't + * be written to when using these functions. */ +bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message); +bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension); + +/* Advance the iterator to the next field. + * Returns false when the iterator wraps back to the first field. */ +bool pb_field_iter_next(pb_field_iter_t *iter); + +/* Advance the iterator until it points at a field with the given tag. + * Returns false if no such field exists. */ +bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag); + +#ifdef PB_VALIDATE_UTF8 +/* Validate UTF-8 text string */ +bool pb_validate_utf8(const char *s); +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif + diff --git a/components/nanopb/pb_decode.c b/components/nanopb/pb_decode.c new file mode 100644 index 0000000..4244fe0 --- /dev/null +++ b/components/nanopb/pb_decode.c @@ -0,0 +1,1745 @@ +/* pb_decode.c -- decode a protobuf using minimal resources + * + * 2011 Petteri Aimonen + */ + +/* Use the GCC warn_unused_result attribute to check that all return values + * are propagated correctly. On other compilers and gcc before 3.4.0 just + * ignore the annotation. + */ +#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) + #define checkreturn +#else + #define checkreturn __attribute__((warn_unused_result)) +#endif + +#include "pb.h" +#include "pb_decode.h" +#include "pb_common.h" + +/************************************** + * Declarations internal to this file * + **************************************/ + +static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); +static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof); +static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size); +static bool checkreturn check_wire_type(pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_field_iter_t *field); +static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn default_extension_decoder(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type); +static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter); +static bool checkreturn find_extension_field(pb_field_iter_t *iter); +static bool pb_message_set_to_defaults(pb_field_iter_t *iter); +static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_fixed(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_skip_varint(pb_istream_t *stream); +static bool checkreturn pb_skip_string(pb_istream_t *stream); + +#ifdef PB_ENABLE_MALLOC +static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size); +static void initialize_pointer_field(void *pItem, pb_field_iter_t *field); +static bool checkreturn pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *field); +static void pb_release_single_field(pb_field_iter_t *field); +#endif + +#ifdef PB_WITHOUT_64BIT +#define pb_int64_t int32_t +#define pb_uint64_t uint32_t +#else +#define pb_int64_t int64_t +#define pb_uint64_t uint64_t +#endif + +typedef struct { + uint32_t bitfield[(PB_MAX_REQUIRED_FIELDS + 31) / 32]; +} pb_fields_seen_t; + +/******************************* + * pb_istream_t implementation * + *******************************/ + +static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) +{ + size_t i; + const pb_byte_t *source = (const pb_byte_t*)stream->state; + stream->state = (pb_byte_t*)stream->state + count; + + if (buf != NULL) + { + for (i = 0; i < count; i++) + buf[i] = source[i]; + } + + return true; +} + +bool checkreturn pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) +{ + if (count == 0) + return true; + +#ifndef PB_BUFFER_ONLY + if (buf == NULL && stream->callback != buf_read) + { + /* Skip input bytes */ + pb_byte_t tmp[16]; + while (count > 16) + { + if (!pb_read(stream, tmp, 16)) + return false; + + count -= 16; + } + + return pb_read(stream, tmp, count); + } +#endif + + if (stream->bytes_left < count) + PB_RETURN_ERROR(stream, "end-of-stream"); + +#ifndef PB_BUFFER_ONLY + if (!stream->callback(stream, buf, count)) + PB_RETURN_ERROR(stream, "io error"); +#else + if (!buf_read(stream, buf, count)) + return false; +#endif + + stream->bytes_left -= count; + return true; +} + +/* Read a single byte from input stream. buf may not be NULL. + * This is an optimization for the varint decoding. */ +static bool checkreturn pb_readbyte(pb_istream_t *stream, pb_byte_t *buf) +{ + if (stream->bytes_left == 0) + PB_RETURN_ERROR(stream, "end-of-stream"); + +#ifndef PB_BUFFER_ONLY + if (!stream->callback(stream, buf, 1)) + PB_RETURN_ERROR(stream, "io error"); +#else + *buf = *(const pb_byte_t*)stream->state; + stream->state = (pb_byte_t*)stream->state + 1; +#endif + + stream->bytes_left--; + + return true; +} + +pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen) +{ + pb_istream_t stream; + /* Cast away the const from buf without a compiler error. We are + * careful to use it only in a const manner in the callbacks. + */ + union { + void *state; + const void *c_state; + } state; +#ifdef PB_BUFFER_ONLY + stream.callback = NULL; +#else + stream.callback = &buf_read; +#endif + state.c_state = buf; + stream.state = state.state; + stream.bytes_left = msglen; +#ifndef PB_NO_ERRMSG + stream.errmsg = NULL; +#endif + return stream; +} + +/******************** + * Helper functions * + ********************/ + +static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof) +{ + pb_byte_t byte; + uint32_t result; + + if (!pb_readbyte(stream, &byte)) + { + if (stream->bytes_left == 0) + { + if (eof) + { + *eof = true; + } + } + + return false; + } + + if ((byte & 0x80) == 0) + { + /* Quick case, 1 byte value */ + result = byte; + } + else + { + /* Multibyte case */ + uint_fast8_t bitpos = 7; + result = byte & 0x7F; + + do + { + if (!pb_readbyte(stream, &byte)) + return false; + + if (bitpos >= 32) + { + /* Note: The varint could have trailing 0x80 bytes, or 0xFF for negative. */ + pb_byte_t sign_extension = (bitpos < 63) ? 0xFF : 0x01; + + if ((byte & 0x7F) != 0x00 && ((result >> 31) == 0 || byte != sign_extension)) + { + PB_RETURN_ERROR(stream, "varint overflow"); + } + } + else + { + result |= (uint32_t)(byte & 0x7F) << bitpos; + } + bitpos = (uint_fast8_t)(bitpos + 7); + } while (byte & 0x80); + + if (bitpos == 35 && (byte & 0x70) != 0) + { + /* The last byte was at bitpos=28, so only bottom 4 bits fit. */ + PB_RETURN_ERROR(stream, "varint overflow"); + } + } + + *dest = result; + return true; +} + +bool checkreturn pb_decode_varint32(pb_istream_t *stream, uint32_t *dest) +{ + return pb_decode_varint32_eof(stream, dest, NULL); +} + +#ifndef PB_WITHOUT_64BIT +bool checkreturn pb_decode_varint(pb_istream_t *stream, uint64_t *dest) +{ + pb_byte_t byte; + uint_fast8_t bitpos = 0; + uint64_t result = 0; + + do + { + if (bitpos >= 64) + PB_RETURN_ERROR(stream, "varint overflow"); + + if (!pb_readbyte(stream, &byte)) + return false; + + result |= (uint64_t)(byte & 0x7F) << bitpos; + bitpos = (uint_fast8_t)(bitpos + 7); + } while (byte & 0x80); + + *dest = result; + return true; +} +#endif + +bool checkreturn pb_skip_varint(pb_istream_t *stream) +{ + pb_byte_t byte; + do + { + if (!pb_read(stream, &byte, 1)) + return false; + } while (byte & 0x80); + return true; +} + +bool checkreturn pb_skip_string(pb_istream_t *stream) +{ + uint32_t length; + if (!pb_decode_varint32(stream, &length)) + return false; + + if ((size_t)length != length) + { + PB_RETURN_ERROR(stream, "size too large"); + } + + return pb_read(stream, NULL, (size_t)length); +} + +bool checkreturn pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof) +{ + uint32_t temp; + *eof = false; + *wire_type = (pb_wire_type_t) 0; + *tag = 0; + + if (!pb_decode_varint32_eof(stream, &temp, eof)) + { + return false; + } + + *tag = temp >> 3; + *wire_type = (pb_wire_type_t)(temp & 7); + return true; +} + +bool checkreturn pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type) +{ + switch (wire_type) + { + case PB_WT_VARINT: return pb_skip_varint(stream); + case PB_WT_64BIT: return pb_read(stream, NULL, 8); + case PB_WT_STRING: return pb_skip_string(stream); + case PB_WT_32BIT: return pb_read(stream, NULL, 4); + default: PB_RETURN_ERROR(stream, "invalid wire_type"); + } +} + +/* Read a raw value to buffer, for the purpose of passing it to callback as + * a substream. Size is maximum size on call, and actual size on return. + */ +static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size) +{ + size_t max_size = *size; + switch (wire_type) + { + case PB_WT_VARINT: + *size = 0; + do + { + (*size)++; + if (*size > max_size) + PB_RETURN_ERROR(stream, "varint overflow"); + + if (!pb_read(stream, buf, 1)) + return false; + } while (*buf++ & 0x80); + return true; + + case PB_WT_64BIT: + *size = 8; + return pb_read(stream, buf, 8); + + case PB_WT_32BIT: + *size = 4; + return pb_read(stream, buf, 4); + + case PB_WT_STRING: + /* Calling read_raw_value with a PB_WT_STRING is an error. + * Explicitly handle this case and fallthrough to default to avoid + * compiler warnings. + */ + + default: PB_RETURN_ERROR(stream, "invalid wire_type"); + } +} + +/* Decode string length from stream and return a substream with limited length. + * Remember to close the substream using pb_close_string_substream(). + */ +bool checkreturn pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream) +{ + uint32_t size; + if (!pb_decode_varint32(stream, &size)) + return false; + + *substream = *stream; + if (substream->bytes_left < size) + PB_RETURN_ERROR(stream, "parent stream too short"); + + substream->bytes_left = (size_t)size; + stream->bytes_left -= (size_t)size; + return true; +} + +bool checkreturn pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream) +{ + if (substream->bytes_left) { + if (!pb_read(substream, NULL, substream->bytes_left)) + return false; + } + + stream->state = substream->state; + +#ifndef PB_NO_ERRMSG + stream->errmsg = substream->errmsg; +#endif + return true; +} + +/************************* + * Decode a single field * + *************************/ + +static bool checkreturn check_wire_type(pb_wire_type_t wire_type, pb_field_iter_t *field) +{ + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_BOOL: + case PB_LTYPE_VARINT: + case PB_LTYPE_UVARINT: + case PB_LTYPE_SVARINT: + return wire_type == PB_WT_VARINT; + + case PB_LTYPE_FIXED32: + return wire_type == PB_WT_32BIT; + + case PB_LTYPE_FIXED64: + return wire_type == PB_WT_64BIT; + + case PB_LTYPE_BYTES: + case PB_LTYPE_STRING: + case PB_LTYPE_SUBMESSAGE: + case PB_LTYPE_SUBMSG_W_CB: + case PB_LTYPE_FIXED_LENGTH_BYTES: + return wire_type == PB_WT_STRING; + + default: + return false; + } +} + +static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_field_iter_t *field) +{ + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_BOOL: + return pb_dec_bool(stream, field); + + case PB_LTYPE_VARINT: + case PB_LTYPE_UVARINT: + case PB_LTYPE_SVARINT: + return pb_dec_varint(stream, field); + + case PB_LTYPE_FIXED32: + case PB_LTYPE_FIXED64: + return pb_dec_fixed(stream, field); + + case PB_LTYPE_BYTES: + return pb_dec_bytes(stream, field); + + case PB_LTYPE_STRING: + return pb_dec_string(stream, field); + + case PB_LTYPE_SUBMESSAGE: + case PB_LTYPE_SUBMSG_W_CB: + return pb_dec_submessage(stream, field); + + case PB_LTYPE_FIXED_LENGTH_BYTES: + return pb_dec_fixed_length_bytes(stream, field); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) +{ + switch (PB_HTYPE(field->type)) + { + case PB_HTYPE_REQUIRED: + if (!check_wire_type(wire_type, field)) + PB_RETURN_ERROR(stream, "wrong wire type"); + + return decode_basic_field(stream, field); + + case PB_HTYPE_OPTIONAL: + if (!check_wire_type(wire_type, field)) + PB_RETURN_ERROR(stream, "wrong wire type"); + + if (field->pSize != NULL) + *(bool*)field->pSize = true; + return decode_basic_field(stream, field); + + case PB_HTYPE_REPEATED: + if (wire_type == PB_WT_STRING + && PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Packed array */ + bool status = true; + pb_istream_t substream; + pb_size_t *size = (pb_size_t*)field->pSize; + field->pData = (char*)field->pField + field->data_size * (*size); + + if (!pb_make_string_substream(stream, &substream)) + return false; + + while (substream.bytes_left > 0 && *size < field->array_size) + { + if (!decode_basic_field(&substream, field)) + { + status = false; + break; + } + (*size)++; + field->pData = (char*)field->pData + field->data_size; + } + + if (substream.bytes_left != 0) + PB_RETURN_ERROR(stream, "array overflow"); + if (!pb_close_string_substream(stream, &substream)) + return false; + + return status; + } + else + { + /* Repeated field */ + pb_size_t *size = (pb_size_t*)field->pSize; + field->pData = (char*)field->pField + field->data_size * (*size); + + if (!check_wire_type(wire_type, field)) + PB_RETURN_ERROR(stream, "wrong wire type"); + + if ((*size)++ >= field->array_size) + PB_RETURN_ERROR(stream, "array overflow"); + + return decode_basic_field(stream, field); + } + + case PB_HTYPE_ONEOF: + *(pb_size_t*)field->pSize = field->tag; + if (PB_LTYPE_IS_SUBMSG(field->type)) + { + /* We memset to zero so that any callbacks are set to NULL. + * This is because the callbacks might otherwise have values + * from some other union field. + * If callbacks are needed inside oneof field, use .proto + * option submsg_callback to have a separate callback function + * that can set the fields before submessage is decoded. + * pb_dec_submessage() will set any default values. */ + memset(field->pData, 0, (size_t)field->data_size); + } + + if (!check_wire_type(wire_type, field)) + PB_RETURN_ERROR(stream, "wrong wire type"); + + return decode_basic_field(stream, field); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +#ifdef PB_ENABLE_MALLOC +/* Allocate storage for the field and store the pointer at iter->pData. + * array_size is the number of entries to reserve in an array. + * Zero size is not allowed, use pb_free() for releasing. + */ +static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size) +{ + void *ptr = *(void**)pData; + + if (data_size == 0 || array_size == 0) + PB_RETURN_ERROR(stream, "invalid size"); + +#ifdef __AVR__ + /* Workaround for AVR libc bug 53284: http://savannah.nongnu.org/bugs/?53284 + * Realloc to size of 1 byte can cause corruption of the malloc structures. + */ + if (data_size == 1 && array_size == 1) + { + data_size = 2; + } +#endif + + /* Check for multiplication overflows. + * This code avoids the costly division if the sizes are small enough. + * Multiplication is safe as long as only half of bits are set + * in either multiplicand. + */ + { + const size_t check_limit = (size_t)1 << (sizeof(size_t) * 4); + if (data_size >= check_limit || array_size >= check_limit) + { + const size_t size_max = (size_t)-1; + if (size_max / array_size < data_size) + { + PB_RETURN_ERROR(stream, "size too large"); + } + } + } + + /* Allocate new or expand previous allocation */ + /* Note: on failure the old pointer will remain in the structure, + * the message must be freed by caller also on error return. */ + ptr = pb_realloc(ptr, array_size * data_size); + if (ptr == NULL) + PB_RETURN_ERROR(stream, "realloc failed"); + + *(void**)pData = ptr; + return true; +} + +/* Clear a newly allocated item in case it contains a pointer, or is a submessage. */ +static void initialize_pointer_field(void *pItem, pb_field_iter_t *field) +{ + if (PB_LTYPE(field->type) == PB_LTYPE_STRING || + PB_LTYPE(field->type) == PB_LTYPE_BYTES) + { + *(void**)pItem = NULL; + } + else if (PB_LTYPE_IS_SUBMSG(field->type)) + { + /* We memset to zero so that any callbacks are set to NULL. + * Then set any default values. */ + pb_field_iter_t submsg_iter; + memset(pItem, 0, field->data_size); + + if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, pItem)) + { + (void)pb_message_set_to_defaults(&submsg_iter); + } + } +} +#endif + +static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) +{ +#ifndef PB_ENABLE_MALLOC + PB_UNUSED(wire_type); + PB_UNUSED(field); + PB_RETURN_ERROR(stream, "no malloc support"); +#else + switch (PB_HTYPE(field->type)) + { + case PB_HTYPE_REQUIRED: + case PB_HTYPE_OPTIONAL: + case PB_HTYPE_ONEOF: + if (!check_wire_type(wire_type, field)) + PB_RETURN_ERROR(stream, "wrong wire type"); + + if (PB_LTYPE_IS_SUBMSG(field->type) && *(void**)field->pField != NULL) + { + /* Duplicate field, have to release the old allocation first. */ + /* FIXME: Does this work correctly for oneofs? */ + pb_release_single_field(field); + } + + if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) + { + *(pb_size_t*)field->pSize = field->tag; + } + + if (PB_LTYPE(field->type) == PB_LTYPE_STRING || + PB_LTYPE(field->type) == PB_LTYPE_BYTES) + { + /* pb_dec_string and pb_dec_bytes handle allocation themselves */ + field->pData = field->pField; + return decode_basic_field(stream, field); + } + else + { + if (!allocate_field(stream, field->pField, field->data_size, 1)) + return false; + + field->pData = *(void**)field->pField; + initialize_pointer_field(field->pData, field); + return decode_basic_field(stream, field); + } + + case PB_HTYPE_REPEATED: + if (wire_type == PB_WT_STRING + && PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Packed array, multiple items come in at once. */ + bool status = true; + pb_size_t *size = (pb_size_t*)field->pSize; + size_t allocated_size = *size; + pb_istream_t substream; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + while (substream.bytes_left) + { + if (*size == PB_SIZE_MAX) + { +#ifndef PB_NO_ERRMSG + stream->errmsg = "too many array entries"; +#endif + status = false; + break; + } + + if ((size_t)*size + 1 > allocated_size) + { + /* Allocate more storage. This tries to guess the + * number of remaining entries. Round the division + * upwards. */ + size_t remain = (substream.bytes_left - 1) / field->data_size + 1; + if (remain < PB_SIZE_MAX - allocated_size) + allocated_size += remain; + else + allocated_size += 1; + + if (!allocate_field(&substream, field->pField, field->data_size, allocated_size)) + { + status = false; + break; + } + } + + /* Decode the array entry */ + field->pData = *(char**)field->pField + field->data_size * (*size); + initialize_pointer_field(field->pData, field); + if (!decode_basic_field(&substream, field)) + { + status = false; + break; + } + + (*size)++; + } + if (!pb_close_string_substream(stream, &substream)) + return false; + + return status; + } + else + { + /* Normal repeated field, i.e. only one item at a time. */ + pb_size_t *size = (pb_size_t*)field->pSize; + + if (*size == PB_SIZE_MAX) + PB_RETURN_ERROR(stream, "too many array entries"); + + if (!check_wire_type(wire_type, field)) + PB_RETURN_ERROR(stream, "wrong wire type"); + + if (!allocate_field(stream, field->pField, field->data_size, (size_t)(*size + 1))) + return false; + + field->pData = *(char**)field->pField + field->data_size * (*size); + (*size)++; + initialize_pointer_field(field->pData, field); + return decode_basic_field(stream, field); + } + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +#endif +} + +static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) +{ + if (!field->descriptor->field_callback) + return pb_skip_field(stream, wire_type); + + if (wire_type == PB_WT_STRING) + { + pb_istream_t substream; + size_t prev_bytes_left; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + do + { + prev_bytes_left = substream.bytes_left; + if (!field->descriptor->field_callback(&substream, NULL, field)) + PB_RETURN_ERROR(stream, "callback failed"); + } while (substream.bytes_left > 0 && substream.bytes_left < prev_bytes_left); + + if (!pb_close_string_substream(stream, &substream)) + return false; + + return true; + } + else + { + /* Copy the single scalar value to stack. + * This is required so that we can limit the stream length, + * which in turn allows to use same callback for packed and + * not-packed fields. */ + pb_istream_t substream; + pb_byte_t buffer[10]; + size_t size = sizeof(buffer); + + if (!read_raw_value(stream, wire_type, buffer, &size)) + return false; + substream = pb_istream_from_buffer(buffer, size); + + return field->descriptor->field_callback(&substream, NULL, field); + } +} + +static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) +{ +#ifdef PB_ENABLE_MALLOC + /* When decoding an oneof field, check if there is old data that must be + * released first. */ + if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) + { + if (!pb_release_union_field(stream, field)) + return false; + } +#endif + + switch (PB_ATYPE(field->type)) + { + case PB_ATYPE_STATIC: + return decode_static_field(stream, wire_type, field); + + case PB_ATYPE_POINTER: + return decode_pointer_field(stream, wire_type, field); + + case PB_ATYPE_CALLBACK: + return decode_callback_field(stream, wire_type, field); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +/* Default handler for extension fields. Expects to have a pb_msgdesc_t + * pointer in the extension->type->arg field, pointing to a message with + * only one field in it. */ +static bool checkreturn default_extension_decoder(pb_istream_t *stream, + pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type) +{ + pb_field_iter_t iter; + + if (!pb_field_iter_begin_extension(&iter, extension)) + PB_RETURN_ERROR(stream, "invalid extension"); + + if (iter.tag != tag || !iter.message) + return true; + + extension->found = true; + return decode_field(stream, wire_type, &iter); +} + +/* Try to decode an unknown field as an extension field. Tries each extension + * decoder in turn, until one of them handles the field or loop ends. */ +static bool checkreturn decode_extension(pb_istream_t *stream, + uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter) +{ + pb_extension_t *extension = *(pb_extension_t* const *)iter->pData; + size_t pos = stream->bytes_left; + + while (extension != NULL && pos == stream->bytes_left) + { + bool status; + if (extension->type->decode) + status = extension->type->decode(stream, extension, tag, wire_type); + else + status = default_extension_decoder(stream, extension, tag, wire_type); + + if (!status) + return false; + + extension = extension->next; + } + + return true; +} + +/* Step through the iterator until an extension field is found or until all + * entries have been checked. There can be only one extension field per + * message. Returns false if no extension field is found. */ +static bool checkreturn find_extension_field(pb_field_iter_t *iter) +{ + pb_size_t start = iter->index; + + do { + if (PB_LTYPE(iter->type) == PB_LTYPE_EXTENSION) + return true; + (void)pb_field_iter_next(iter); + } while (iter->index != start); + + return false; +} + +/* Initialize message fields to default values, recursively */ +static bool pb_field_set_to_default(pb_field_iter_t *field) +{ + pb_type_t type; + type = field->type; + + if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) + { + pb_extension_t *ext = *(pb_extension_t* const *)field->pData; + while (ext != NULL) + { + pb_field_iter_t ext_iter; + if (pb_field_iter_begin_extension(&ext_iter, ext)) + { + ext->found = false; + if (!pb_message_set_to_defaults(&ext_iter)) + return false; + } + ext = ext->next; + } + } + else if (PB_ATYPE(type) == PB_ATYPE_STATIC) + { + bool init_data = true; + if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->pSize != NULL) + { + /* Set has_field to false. Still initialize the optional field + * itself also. */ + *(bool*)field->pSize = false; + } + else if (PB_HTYPE(type) == PB_HTYPE_REPEATED || + PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + /* REPEATED: Set array count to 0, no need to initialize contents. + ONEOF: Set which_field to 0. */ + *(pb_size_t*)field->pSize = 0; + init_data = false; + } + + if (init_data) + { + if (PB_LTYPE_IS_SUBMSG(field->type)) + { + /* Initialize submessage to defaults */ + pb_field_iter_t submsg_iter; + if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, field->pData)) + { + if (!pb_message_set_to_defaults(&submsg_iter)) + return false; + } + } + else + { + /* Initialize to zeros */ + memset(field->pData, 0, (size_t)field->data_size); + } + } + } + else if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + /* Initialize the pointer to NULL. */ + *(void**)field->pField = NULL; + + /* Initialize array count to 0. */ + if (PB_HTYPE(type) == PB_HTYPE_REPEATED || + PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + *(pb_size_t*)field->pSize = 0; + } + } + else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) + { + /* Don't overwrite callback */ + } + + return true; +} + +static bool pb_message_set_to_defaults(pb_field_iter_t *iter) +{ + pb_istream_t defstream = PB_ISTREAM_EMPTY; + uint32_t tag = 0; + pb_wire_type_t wire_type = PB_WT_VARINT; + bool eof; + + if (iter->descriptor->default_value) + { + defstream = pb_istream_from_buffer(iter->descriptor->default_value, (size_t)-1); + if (!pb_decode_tag(&defstream, &wire_type, &tag, &eof)) + return false; + } + + do + { + if (!pb_field_set_to_default(iter)) + return false; + + if (tag != 0 && iter->tag == tag) + { + /* We have a default value for this field in the defstream */ + if (!decode_field(&defstream, wire_type, iter)) + return false; + if (!pb_decode_tag(&defstream, &wire_type, &tag, &eof)) + return false; + + if (iter->pSize) + *(bool*)iter->pSize = false; + } + } while (pb_field_iter_next(iter)); + + return true; +} + +/********************* + * Decode all fields * + *********************/ + +static bool checkreturn pb_decode_inner(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags) +{ + uint32_t extension_range_start = 0; + + /* 'fixed_count_field' and 'fixed_count_size' track position of a repeated fixed + * count field. This can only handle _one_ repeated fixed count field that + * is unpacked and unordered among other (non repeated fixed count) fields. + */ + pb_size_t fixed_count_field = PB_SIZE_MAX; + pb_size_t fixed_count_size = 0; + pb_size_t fixed_count_total_size = 0; + + pb_fields_seen_t fields_seen = {{0, 0}}; + const uint32_t allbits = ~(uint32_t)0; + pb_field_iter_t iter; + + if (pb_field_iter_begin(&iter, fields, dest_struct)) + { + if ((flags & PB_DECODE_NOINIT) == 0) + { + if (!pb_message_set_to_defaults(&iter)) + PB_RETURN_ERROR(stream, "failed to set defaults"); + } + } + + while (stream->bytes_left) + { + uint32_t tag; + pb_wire_type_t wire_type; + bool eof; + + if (!pb_decode_tag(stream, &wire_type, &tag, &eof)) + { + if (eof) + break; + else + return false; + } + + if (tag == 0) + { + if (flags & PB_DECODE_NULLTERMINATED) + { + break; + } + else + { + PB_RETURN_ERROR(stream, "zero tag"); + } + } + + if (!pb_field_iter_find(&iter, tag) || PB_LTYPE(iter.type) == PB_LTYPE_EXTENSION) + { + /* No match found, check if it matches an extension. */ + if (tag >= extension_range_start) + { + if (!find_extension_field(&iter)) + extension_range_start = (uint32_t)-1; + else + extension_range_start = iter.tag; + + if (tag >= extension_range_start) + { + size_t pos = stream->bytes_left; + + if (!decode_extension(stream, tag, wire_type, &iter)) + return false; + + if (pos != stream->bytes_left) + { + /* The field was handled */ + continue; + } + } + } + + /* No match found, skip data */ + if (!pb_skip_field(stream, wire_type)) + return false; + continue; + } + + /* If a repeated fixed count field was found, get size from + * 'fixed_count_field' as there is no counter contained in the struct. + */ + if (PB_HTYPE(iter.type) == PB_HTYPE_REPEATED && iter.pSize == &iter.array_size) + { + if (fixed_count_field != iter.index) { + /* If the new fixed count field does not match the previous one, + * check that the previous one is NULL or that it finished + * receiving all the expected data. + */ + if (fixed_count_field != PB_SIZE_MAX && + fixed_count_size != fixed_count_total_size) + { + PB_RETURN_ERROR(stream, "wrong size for fixed count field"); + } + + fixed_count_field = iter.index; + fixed_count_size = 0; + fixed_count_total_size = iter.array_size; + } + + iter.pSize = &fixed_count_size; + } + + if (PB_HTYPE(iter.type) == PB_HTYPE_REQUIRED + && iter.required_field_index < PB_MAX_REQUIRED_FIELDS) + { + uint32_t tmp = ((uint32_t)1 << (iter.required_field_index & 31)); + fields_seen.bitfield[iter.required_field_index >> 5] |= tmp; + } + + if (!decode_field(stream, wire_type, &iter)) + return false; + } + + /* Check that all elements of the last decoded fixed count field were present. */ + if (fixed_count_field != PB_SIZE_MAX && + fixed_count_size != fixed_count_total_size) + { + PB_RETURN_ERROR(stream, "wrong size for fixed count field"); + } + + /* Check that all required fields were present. */ + { + /* First figure out the number of required fields by + * seeking to the end of the field array. Usually we + * are already close to end after decoding. + */ + pb_size_t req_field_count; + pb_type_t last_type; + pb_size_t i; + do { + req_field_count = iter.required_field_index; + last_type = iter.type; + } while (pb_field_iter_next(&iter)); + + /* Fixup if last field was also required. */ + if (PB_HTYPE(last_type) == PB_HTYPE_REQUIRED && iter.tag != 0) + req_field_count++; + + if (req_field_count > PB_MAX_REQUIRED_FIELDS) + req_field_count = PB_MAX_REQUIRED_FIELDS; + + if (req_field_count > 0) + { + /* Check the whole words */ + for (i = 0; i < (req_field_count >> 5); i++) + { + if (fields_seen.bitfield[i] != allbits) + PB_RETURN_ERROR(stream, "missing required field"); + } + + /* Check the remaining bits (if any) */ + if ((req_field_count & 31) != 0) + { + if (fields_seen.bitfield[req_field_count >> 5] != + (allbits >> (uint_least8_t)(32 - (req_field_count & 31)))) + { + PB_RETURN_ERROR(stream, "missing required field"); + } + } + } + } + + return true; +} + +bool checkreturn pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags) +{ + bool status; + + if ((flags & PB_DECODE_DELIMITED) == 0) + { + status = pb_decode_inner(stream, fields, dest_struct, flags); + } + else + { + pb_istream_t substream; + if (!pb_make_string_substream(stream, &substream)) + return false; + + status = pb_decode_inner(&substream, fields, dest_struct, flags); + + if (!pb_close_string_substream(stream, &substream)) + return false; + } + +#ifdef PB_ENABLE_MALLOC + if (!status) + pb_release(fields, dest_struct); +#endif + + return status; +} + +bool checkreturn pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct) +{ + bool status; + + status = pb_decode_inner(stream, fields, dest_struct, 0); + +#ifdef PB_ENABLE_MALLOC + if (!status) + pb_release(fields, dest_struct); +#endif + + return status; +} + +#ifdef PB_ENABLE_MALLOC +/* Given an oneof field, if there has already been a field inside this oneof, + * release it before overwriting with a different one. */ +static bool pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *field) +{ + pb_field_iter_t old_field = *field; + pb_size_t old_tag = *(pb_size_t*)field->pSize; /* Previous which_ value */ + pb_size_t new_tag = field->tag; /* New which_ value */ + + if (old_tag == 0) + return true; /* Ok, no old data in union */ + + if (old_tag == new_tag) + return true; /* Ok, old data is of same type => merge */ + + /* Release old data. The find can fail if the message struct contains + * invalid data. */ + if (!pb_field_iter_find(&old_field, old_tag)) + PB_RETURN_ERROR(stream, "invalid union tag"); + + pb_release_single_field(&old_field); + + return true; +} + +static void pb_release_single_field(pb_field_iter_t *field) +{ + pb_type_t type; + type = field->type; + + if (PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + if (*(pb_size_t*)field->pSize != field->tag) + return; /* This is not the current field in the union */ + } + + /* Release anything contained inside an extension or submsg. + * This has to be done even if the submsg itself is statically + * allocated. */ + if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) + { + /* Release fields from all extensions in the linked list */ + pb_extension_t *ext = *(pb_extension_t**)field->pData; + while (ext != NULL) + { + pb_field_iter_t ext_iter; + if (pb_field_iter_begin_extension(&ext_iter, ext)) + { + pb_release_single_field(&ext_iter); + } + ext = ext->next; + } + } + else if (PB_LTYPE_IS_SUBMSG(type) && PB_ATYPE(type) != PB_ATYPE_CALLBACK) + { + /* Release fields in submessage or submsg array */ + pb_size_t count = 1; + + if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + field->pData = *(void**)field->pField; + } + else + { + field->pData = field->pField; + } + + if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + count = *(pb_size_t*)field->pSize; + + if (PB_ATYPE(type) == PB_ATYPE_STATIC && count > field->array_size) + { + /* Protect against corrupted _count fields */ + count = field->array_size; + } + } + + if (field->pData) + { + for (; count > 0; count--) + { + pb_release(field->submsg_desc, field->pData); + field->pData = (char*)field->pData + field->data_size; + } + } + } + + if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + if (PB_HTYPE(type) == PB_HTYPE_REPEATED && + (PB_LTYPE(type) == PB_LTYPE_STRING || + PB_LTYPE(type) == PB_LTYPE_BYTES)) + { + /* Release entries in repeated string or bytes array */ + void **pItem = *(void***)field->pField; + pb_size_t count = *(pb_size_t*)field->pSize; + for (; count > 0; count--) + { + pb_free(*pItem); + *pItem++ = NULL; + } + } + + if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + /* We are going to release the array, so set the size to 0 */ + *(pb_size_t*)field->pSize = 0; + } + + /* Release main pointer */ + pb_free(*(void**)field->pField); + *(void**)field->pField = NULL; + } +} + +void pb_release(const pb_msgdesc_t *fields, void *dest_struct) +{ + pb_field_iter_t iter; + + if (!dest_struct) + return; /* Ignore NULL pointers, similar to free() */ + + if (!pb_field_iter_begin(&iter, fields, dest_struct)) + return; /* Empty message type */ + + do + { + pb_release_single_field(&iter); + } while (pb_field_iter_next(&iter)); +} +#endif + +/* Field decoders */ + +bool pb_decode_bool(pb_istream_t *stream, bool *dest) +{ + uint32_t value; + if (!pb_decode_varint32(stream, &value)) + return false; + + *(bool*)dest = (value != 0); + return true; +} + +bool pb_decode_svarint(pb_istream_t *stream, pb_int64_t *dest) +{ + pb_uint64_t value; + if (!pb_decode_varint(stream, &value)) + return false; + + if (value & 1) + *dest = (pb_int64_t)(~(value >> 1)); + else + *dest = (pb_int64_t)(value >> 1); + + return true; +} + +bool pb_decode_fixed32(pb_istream_t *stream, void *dest) +{ + union { + uint32_t fixed32; + pb_byte_t bytes[4]; + } u; + + if (!pb_read(stream, u.bytes, 4)) + return false; + +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN && CHAR_BIT == 8 + /* fast path - if we know that we're on little endian, assign directly */ + *(uint32_t*)dest = u.fixed32; +#else + *(uint32_t*)dest = ((uint32_t)u.bytes[0] << 0) | + ((uint32_t)u.bytes[1] << 8) | + ((uint32_t)u.bytes[2] << 16) | + ((uint32_t)u.bytes[3] << 24); +#endif + return true; +} + +#ifndef PB_WITHOUT_64BIT +bool pb_decode_fixed64(pb_istream_t *stream, void *dest) +{ + union { + uint64_t fixed64; + pb_byte_t bytes[8]; + } u; + + if (!pb_read(stream, u.bytes, 8)) + return false; + +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN && CHAR_BIT == 8 + /* fast path - if we know that we're on little endian, assign directly */ + *(uint64_t*)dest = u.fixed64; +#else + *(uint64_t*)dest = ((uint64_t)u.bytes[0] << 0) | + ((uint64_t)u.bytes[1] << 8) | + ((uint64_t)u.bytes[2] << 16) | + ((uint64_t)u.bytes[3] << 24) | + ((uint64_t)u.bytes[4] << 32) | + ((uint64_t)u.bytes[5] << 40) | + ((uint64_t)u.bytes[6] << 48) | + ((uint64_t)u.bytes[7] << 56); +#endif + return true; +} +#endif + +static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_iter_t *field) +{ + return pb_decode_bool(stream, (bool*)field->pData); +} + +static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_t *field) +{ + if (PB_LTYPE(field->type) == PB_LTYPE_UVARINT) + { + pb_uint64_t value, clamped; + if (!pb_decode_varint(stream, &value)) + return false; + + /* Cast to the proper field size, while checking for overflows */ + if (field->data_size == sizeof(pb_uint64_t)) + clamped = *(pb_uint64_t*)field->pData = value; + else if (field->data_size == sizeof(uint32_t)) + clamped = *(uint32_t*)field->pData = (uint32_t)value; + else if (field->data_size == sizeof(uint_least16_t)) + clamped = *(uint_least16_t*)field->pData = (uint_least16_t)value; + else if (field->data_size == sizeof(uint_least8_t)) + clamped = *(uint_least8_t*)field->pData = (uint_least8_t)value; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + if (clamped != value) + PB_RETURN_ERROR(stream, "integer too large"); + + return true; + } + else + { + pb_uint64_t value; + pb_int64_t svalue; + pb_int64_t clamped; + + if (PB_LTYPE(field->type) == PB_LTYPE_SVARINT) + { + if (!pb_decode_svarint(stream, &svalue)) + return false; + } + else + { + if (!pb_decode_varint(stream, &value)) + return false; + + /* See issue 97: Google's C++ protobuf allows negative varint values to + * be cast as int32_t, instead of the int64_t that should be used when + * encoding. Previous nanopb versions had a bug in encoding. In order to + * not break decoding of such messages, we cast <=32 bit fields to + * int32_t first to get the sign correct. + */ + if (field->data_size == sizeof(pb_int64_t)) + svalue = (pb_int64_t)value; + else + svalue = (int32_t)value; + } + + /* Cast to the proper field size, while checking for overflows */ + if (field->data_size == sizeof(pb_int64_t)) + clamped = *(pb_int64_t*)field->pData = svalue; + else if (field->data_size == sizeof(int32_t)) + clamped = *(int32_t*)field->pData = (int32_t)svalue; + else if (field->data_size == sizeof(int_least16_t)) + clamped = *(int_least16_t*)field->pData = (int_least16_t)svalue; + else if (field->data_size == sizeof(int_least8_t)) + clamped = *(int_least8_t*)field->pData = (int_least8_t)svalue; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + if (clamped != svalue) + PB_RETURN_ERROR(stream, "integer too large"); + + return true; + } +} + +static bool checkreturn pb_dec_fixed(pb_istream_t *stream, const pb_field_iter_t *field) +{ +#ifdef PB_CONVERT_DOUBLE_FLOAT + if (field->data_size == sizeof(float) && PB_LTYPE(field->type) == PB_LTYPE_FIXED64) + { + return pb_decode_double_as_float(stream, (float*)field->pData); + } +#endif + + if (field->data_size == sizeof(uint32_t)) + { + return pb_decode_fixed32(stream, field->pData); + } +#ifndef PB_WITHOUT_64BIT + else if (field->data_size == sizeof(uint64_t)) + { + return pb_decode_fixed64(stream, field->pData); + } +#endif + else + { + PB_RETURN_ERROR(stream, "invalid data_size"); + } +} + +static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_iter_t *field) +{ + uint32_t size; + size_t alloc_size; + pb_bytes_array_t *dest; + + if (!pb_decode_varint32(stream, &size)) + return false; + + if (size > PB_SIZE_MAX) + PB_RETURN_ERROR(stream, "bytes overflow"); + + alloc_size = PB_BYTES_ARRAY_T_ALLOCSIZE(size); + if (size > alloc_size) + PB_RETURN_ERROR(stream, "size too large"); + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { +#ifndef PB_ENABLE_MALLOC + PB_RETURN_ERROR(stream, "no malloc support"); +#else + if (stream->bytes_left < size) + PB_RETURN_ERROR(stream, "end-of-stream"); + + if (!allocate_field(stream, field->pData, alloc_size, 1)) + return false; + dest = *(pb_bytes_array_t**)field->pData; +#endif + } + else + { + if (alloc_size > field->data_size) + PB_RETURN_ERROR(stream, "bytes overflow"); + dest = (pb_bytes_array_t*)field->pData; + } + + dest->size = (pb_size_t)size; + return pb_read(stream, dest->bytes, (size_t)size); +} + +static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_iter_t *field) +{ + uint32_t size; + size_t alloc_size; + pb_byte_t *dest = (pb_byte_t*)field->pData; + + if (!pb_decode_varint32(stream, &size)) + return false; + + if (size == (uint32_t)-1) + PB_RETURN_ERROR(stream, "size too large"); + + /* Space for null terminator */ + alloc_size = (size_t)(size + 1); + + if (alloc_size < size) + PB_RETURN_ERROR(stream, "size too large"); + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { +#ifndef PB_ENABLE_MALLOC + PB_RETURN_ERROR(stream, "no malloc support"); +#else + if (stream->bytes_left < size) + PB_RETURN_ERROR(stream, "end-of-stream"); + + if (!allocate_field(stream, field->pData, alloc_size, 1)) + return false; + dest = *(pb_byte_t**)field->pData; +#endif + } + else + { + if (alloc_size > field->data_size) + PB_RETURN_ERROR(stream, "string overflow"); + } + + dest[size] = 0; + + if (!pb_read(stream, dest, (size_t)size)) + return false; + +#ifdef PB_VALIDATE_UTF8 + if (!pb_validate_utf8((const char*)dest)) + PB_RETURN_ERROR(stream, "invalid utf8"); +#endif + + return true; +} + +static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_iter_t *field) +{ + bool status = true; + bool submsg_consumed = false; + pb_istream_t substream; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + if (field->submsg_desc == NULL) + PB_RETURN_ERROR(stream, "invalid field descriptor"); + + /* New array entries need to be initialized, while required and optional + * submessages have already been initialized in the top-level pb_decode. */ + if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED || + PB_HTYPE(field->type) == PB_HTYPE_ONEOF) + { + pb_field_iter_t submsg_iter; + if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, field->pData)) + { + if (!pb_message_set_to_defaults(&submsg_iter)) + PB_RETURN_ERROR(stream, "failed to set defaults"); + } + } + + /* Submessages can have a separate message-level callback that is called + * before decoding the message. Typically it is used to set callback fields + * inside oneofs. */ + if (PB_LTYPE(field->type) == PB_LTYPE_SUBMSG_W_CB && field->pSize != NULL) + { + /* Message callback is stored right before pSize. */ + pb_callback_t *callback = (pb_callback_t*)field->pSize - 1; + if (callback->funcs.decode) + { + status = callback->funcs.decode(&substream, field, &callback->arg); + + if (substream.bytes_left == 0) + { + submsg_consumed = true; + } + } + } + + /* Now decode the submessage contents */ + if (status && !submsg_consumed) + { + status = pb_decode_inner(&substream, field->submsg_desc, field->pData, 0); + } + + if (!pb_close_string_substream(stream, &substream)) + return false; + + return status; +} + +static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_iter_t *field) +{ + uint32_t size; + + if (!pb_decode_varint32(stream, &size)) + return false; + + if (size > PB_SIZE_MAX) + PB_RETURN_ERROR(stream, "bytes overflow"); + + if (size == 0) + { + /* As a special case, treat empty bytes string as all zeros for fixed_length_bytes. */ + memset(field->pData, 0, (size_t)field->data_size); + return true; + } + + if (size != field->data_size) + PB_RETURN_ERROR(stream, "incorrect fixed length bytes size"); + + return pb_read(stream, (pb_byte_t*)field->pData, (size_t)field->data_size); +} + +#ifdef PB_CONVERT_DOUBLE_FLOAT +bool pb_decode_double_as_float(pb_istream_t *stream, float *dest) +{ + uint_least8_t sign; + int exponent; + uint32_t mantissa; + uint64_t value; + union { float f; uint32_t i; } out; + + if (!pb_decode_fixed64(stream, &value)) + return false; + + /* Decompose input value */ + sign = (uint_least8_t)((value >> 63) & 1); + exponent = (int)((value >> 52) & 0x7FF) - 1023; + mantissa = (value >> 28) & 0xFFFFFF; /* Highest 24 bits */ + + /* Figure if value is in range representable by floats. */ + if (exponent == 1024) + { + /* Special value */ + exponent = 128; + mantissa >>= 1; + } + else + { + if (exponent > 127) + { + /* Too large, convert to infinity */ + exponent = 128; + mantissa = 0; + } + else if (exponent < -150) + { + /* Too small, convert to zero */ + exponent = -127; + mantissa = 0; + } + else if (exponent < -126) + { + /* Denormalized */ + mantissa |= 0x1000000; + mantissa >>= (-126 - exponent); + exponent = -127; + } + + /* Round off mantissa */ + mantissa = (mantissa + 1) >> 1; + + /* Check if mantissa went over 2.0 */ + if (mantissa & 0x800000) + { + exponent += 1; + mantissa &= 0x7FFFFF; + mantissa >>= 1; + } + } + + /* Combine fields */ + out.i = mantissa; + out.i |= (uint32_t)(exponent + 127) << 23; + out.i |= (uint32_t)sign << 31; + + *dest = out.f; + return true; +} +#endif diff --git a/components/nanopb/pb_decode.h b/components/nanopb/pb_decode.h new file mode 100644 index 0000000..9b70c8b --- /dev/null +++ b/components/nanopb/pb_decode.h @@ -0,0 +1,196 @@ +/* pb_decode.h: Functions to decode protocol buffers. Depends on pb_decode.c. + * The main function is pb_decode. You also need an input stream, and the + * field descriptions created by nanopb_generator.py. + */ + +#ifndef PB_DECODE_H_INCLUDED +#define PB_DECODE_H_INCLUDED + +#include "pb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structure for defining custom input streams. You will need to provide + * a callback function to read the bytes from your storage, which can be + * for example a file or a network socket. + * + * The callback must conform to these rules: + * + * 1) Return false on IO errors. This will cause decoding to abort. + * 2) You can use state to store your own data (e.g. buffer pointer), + * and rely on pb_read to verify that no-body reads past bytes_left. + * 3) Your callback may be used with substreams, in which case bytes_left + * is different than from the main stream. Don't use bytes_left to compute + * any pointers. + */ +struct pb_istream_s +{ +#ifdef PB_BUFFER_ONLY + /* Callback pointer is not used in buffer-only configuration. + * Having an int pointer here allows binary compatibility but + * gives an error if someone tries to assign callback function. + */ + int *callback; +#else + bool (*callback)(pb_istream_t *stream, pb_byte_t *buf, size_t count); +#endif + + void *state; /* Free field for use by callback implementation */ + size_t bytes_left; + +#ifndef PB_NO_ERRMSG + const char *errmsg; +#endif +}; + +#ifndef PB_NO_ERRMSG +#define PB_ISTREAM_EMPTY {0,0,0,0} +#else +#define PB_ISTREAM_EMPTY {0,0,0} +#endif + +/*************************** + * Main decoding functions * + ***************************/ + +/* Decode a single protocol buffers message from input stream into a C structure. + * Returns true on success, false on any failure. + * The actual struct pointed to by dest must match the description in fields. + * Callback fields of the destination structure must be initialized by caller. + * All other fields will be initialized by this function. + * + * Example usage: + * MyMessage msg = {}; + * uint8_t buffer[64]; + * pb_istream_t stream; + * + * // ... read some data into buffer ... + * + * stream = pb_istream_from_buffer(buffer, count); + * pb_decode(&stream, MyMessage_fields, &msg); + */ +bool pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct); + +/* Extended version of pb_decode, with several options to control + * the decoding process: + * + * PB_DECODE_NOINIT: Do not initialize the fields to default values. + * This is slightly faster if you do not need the default + * values and instead initialize the structure to 0 using + * e.g. memset(). This can also be used for merging two + * messages, i.e. combine already existing data with new + * values. + * + * PB_DECODE_DELIMITED: Input message starts with the message size as varint. + * Corresponds to parseDelimitedFrom() in Google's + * protobuf API. + * + * PB_DECODE_NULLTERMINATED: Stop reading when field tag is read as 0. This allows + * reading null terminated messages. + * NOTE: Until nanopb-0.4.0, pb_decode() also allows + * null-termination. This behaviour is not supported in + * most other protobuf implementations, so PB_DECODE_DELIMITED + * is a better option for compatibility. + * + * Multiple flags can be combined with bitwise or (| operator) + */ +#define PB_DECODE_NOINIT 0x01U +#define PB_DECODE_DELIMITED 0x02U +#define PB_DECODE_NULLTERMINATED 0x04U +bool pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags); + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define pb_decode_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NOINIT) +#define pb_decode_delimited(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED) +#define pb_decode_delimited_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED | PB_DECODE_NOINIT) +#define pb_decode_nullterminated(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NULLTERMINATED) + +#ifdef PB_ENABLE_MALLOC +/* Release any allocated pointer fields. If you use dynamic allocation, you should + * call this for any successfully decoded message when you are done with it. If + * pb_decode() returns with an error, the message is already released. + */ +void pb_release(const pb_msgdesc_t *fields, void *dest_struct); +#endif + + +/************************************** + * Functions for manipulating streams * + **************************************/ + +/* Create an input stream for reading from a memory buffer. + * + * msglen should be the actual length of the message, not the full size of + * allocated buffer. + * + * Alternatively, you can use a custom stream that reads directly from e.g. + * a file or a network socket. + */ +pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen); + +/* Function to read from a pb_istream_t. You can use this if you need to + * read some custom header data, or to read data in field callbacks. + */ +bool pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); + + +/************************************************ + * Helper functions for writing field callbacks * + ************************************************/ + +/* Decode the tag for the next field in the stream. Gives the wire type and + * field tag. At end of the message, returns false and sets eof to true. */ +bool pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof); + +/* Skip the field payload data, given the wire type. */ +bool pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type); + +/* Decode an integer in the varint format. This works for enum, int32, + * int64, uint32 and uint64 field types. */ +#ifndef PB_WITHOUT_64BIT +bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest); +#else +#define pb_decode_varint pb_decode_varint32 +#endif + +/* Decode an integer in the varint format. This works for enum, int32, + * and uint32 field types. */ +bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest); + +/* Decode a bool value in varint format. */ +bool pb_decode_bool(pb_istream_t *stream, bool *dest); + +/* Decode an integer in the zig-zagged svarint format. This works for sint32 + * and sint64. */ +#ifndef PB_WITHOUT_64BIT +bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest); +#else +bool pb_decode_svarint(pb_istream_t *stream, int32_t *dest); +#endif + +/* Decode a fixed32, sfixed32 or float value. You need to pass a pointer to + * a 4-byte wide C variable. */ +bool pb_decode_fixed32(pb_istream_t *stream, void *dest); + +#ifndef PB_WITHOUT_64BIT +/* Decode a fixed64, sfixed64 or double value. You need to pass a pointer to + * a 8-byte wide C variable. */ +bool pb_decode_fixed64(pb_istream_t *stream, void *dest); +#endif + +#ifdef PB_CONVERT_DOUBLE_FLOAT +/* Decode a double value into float variable. */ +bool pb_decode_double_as_float(pb_istream_t *stream, float *dest); +#endif + +/* Make a limited-length substream for reading a PB_WT_STRING field. */ +bool pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream); +bool pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/components/nanopb/pb_encode.c b/components/nanopb/pb_encode.c new file mode 100644 index 0000000..54cd5ba --- /dev/null +++ b/components/nanopb/pb_encode.c @@ -0,0 +1,978 @@ +/* pb_encode.c -- encode a protobuf using minimal resources + * + * 2011 Petteri Aimonen + */ + +#include "pb.h" +#include "pb_encode.h" +#include "pb_common.h" + +/* Use the GCC warn_unused_result attribute to check that all return values + * are propagated correctly. On other compilers and gcc before 3.4.0 just + * ignore the annotation. + */ +#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) + #define checkreturn +#else + #define checkreturn __attribute__((warn_unused_result)) +#endif + +/************************************** + * Declarations internal to this file * + **************************************/ +static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); +static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field); +static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field); +static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field); +static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension); +static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high); +static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field); + +#ifdef PB_WITHOUT_64BIT +#define pb_int64_t int32_t +#define pb_uint64_t uint32_t +#else +#define pb_int64_t int64_t +#define pb_uint64_t uint64_t +#endif + +/******************************* + * pb_ostream_t implementation * + *******************************/ + +static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) +{ + size_t i; + pb_byte_t *dest = (pb_byte_t*)stream->state; + stream->state = dest + count; + + for (i = 0; i < count; i++) + dest[i] = buf[i]; + + return true; +} + +pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize) +{ + pb_ostream_t stream; +#ifdef PB_BUFFER_ONLY + stream.callback = (void*)1; /* Just a marker value */ +#else + stream.callback = &buf_write; +#endif + stream.state = buf; + stream.max_size = bufsize; + stream.bytes_written = 0; +#ifndef PB_NO_ERRMSG + stream.errmsg = NULL; +#endif + return stream; +} + +bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) +{ + if (count > 0 && stream->callback != NULL) + { + if (stream->bytes_written + count < stream->bytes_written || + stream->bytes_written + count > stream->max_size) + { + PB_RETURN_ERROR(stream, "stream full"); + } + +#ifdef PB_BUFFER_ONLY + if (!buf_write(stream, buf, count)) + PB_RETURN_ERROR(stream, "io error"); +#else + if (!stream->callback(stream, buf, count)) + PB_RETURN_ERROR(stream, "io error"); +#endif + } + + stream->bytes_written += count; + return true; +} + +/************************* + * Encode a single field * + *************************/ + +/* Read a bool value without causing undefined behavior even if the value + * is invalid. See issue #434 and + * https://stackoverflow.com/questions/27661768/weird-results-for-conditional + */ +static bool safe_read_bool(const void *pSize) +{ + const char *p = (const char *)pSize; + size_t i; + for (i = 0; i < sizeof(bool); i++) + { + if (p[i] != 0) + return true; + } + return false; +} + +/* Encode a static array. Handles the size calculations and possible packing. */ +static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field) +{ + pb_size_t i; + pb_size_t count; +#ifndef PB_ENCODE_ARRAYS_UNPACKED + size_t size; +#endif + + count = *(pb_size_t*)field->pSize; + + if (count == 0) + return true; + + if (PB_ATYPE(field->type) != PB_ATYPE_POINTER && count > field->array_size) + PB_RETURN_ERROR(stream, "array max size exceeded"); + +#ifndef PB_ENCODE_ARRAYS_UNPACKED + /* We always pack arrays if the datatype allows it. */ + if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) + { + if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) + return false; + + /* Determine the total size of packed array. */ + if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32) + { + size = 4 * (size_t)count; + } + else if (PB_LTYPE(field->type) == PB_LTYPE_FIXED64) + { + size = 8 * (size_t)count; + } + else + { + pb_ostream_t sizestream = PB_OSTREAM_SIZING; + void *pData_orig = field->pData; + for (i = 0; i < count; i++) + { + if (!pb_enc_varint(&sizestream, field)) + PB_RETURN_ERROR(stream, PB_GET_ERROR(&sizestream)); + field->pData = (char*)field->pData + field->data_size; + } + field->pData = pData_orig; + size = sizestream.bytes_written; + } + + if (!pb_encode_varint(stream, (pb_uint64_t)size)) + return false; + + if (stream->callback == NULL) + return pb_write(stream, NULL, size); /* Just sizing.. */ + + /* Write the data */ + for (i = 0; i < count; i++) + { + if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32 || PB_LTYPE(field->type) == PB_LTYPE_FIXED64) + { + if (!pb_enc_fixed(stream, field)) + return false; + } + else + { + if (!pb_enc_varint(stream, field)) + return false; + } + + field->pData = (char*)field->pData + field->data_size; + } + } + else /* Unpacked fields */ +#endif + { + for (i = 0; i < count; i++) + { + /* Normally the data is stored directly in the array entries, but + * for pointer-type string and bytes fields, the array entries are + * actually pointers themselves also. So we have to dereference once + * more to get to the actual data. */ + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER && + (PB_LTYPE(field->type) == PB_LTYPE_STRING || + PB_LTYPE(field->type) == PB_LTYPE_BYTES)) + { + bool status; + void *pData_orig = field->pData; + field->pData = *(void* const*)field->pData; + + if (!field->pData) + { + /* Null pointer in array is treated as empty string / bytes */ + status = pb_encode_tag_for_field(stream, field) && + pb_encode_varint(stream, 0); + } + else + { + status = encode_basic_field(stream, field); + } + + field->pData = pData_orig; + + if (!status) + return false; + } + else + { + if (!encode_basic_field(stream, field)) + return false; + } + field->pData = (char*)field->pData + field->data_size; + } + } + + return true; +} + +/* In proto3, all fields are optional and are only encoded if their value is "non-zero". + * This function implements the check for the zero value. */ +static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field) +{ + pb_type_t type = field->type; + + if (PB_ATYPE(type) == PB_ATYPE_STATIC) + { + if (PB_HTYPE(type) == PB_HTYPE_REQUIRED) + { + /* Required proto2 fields inside proto3 submessage, pretty rare case */ + return false; + } + else if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + /* Repeated fields inside proto3 submessage: present if count != 0 */ + return *(const pb_size_t*)field->pSize == 0; + } + else if (PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + /* Oneof fields */ + return *(const pb_size_t*)field->pSize == 0; + } + else if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->pSize != NULL) + { + /* Proto2 optional fields inside proto3 message, or proto3 + * submessage fields. */ + return safe_read_bool(field->pSize) == false; + } + + /* Rest is proto3 singular fields */ + if (PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Simple integer / float fields */ + pb_size_t i; + const char *p = (const char*)field->pData; + for (i = 0; i < field->data_size; i++) + { + if (p[i] != 0) + { + return false; + } + } + + return true; + } + else if (PB_LTYPE(type) == PB_LTYPE_BYTES) + { + const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)field->pData; + return bytes->size == 0; + } + else if (PB_LTYPE(type) == PB_LTYPE_STRING) + { + return *(const char*)field->pData == '\0'; + } + else if (PB_LTYPE(type) == PB_LTYPE_FIXED_LENGTH_BYTES) + { + /* Fixed length bytes is only empty if its length is fixed + * as 0. Which would be pretty strange, but we can check + * it anyway. */ + return field->data_size == 0; + } + else if (PB_LTYPE_IS_SUBMSG(type)) + { + /* Check all fields in the submessage to find if any of them + * are non-zero. The comparison cannot be done byte-per-byte + * because the C struct may contain padding bytes that must + * be skipped. Note that usually proto3 submessages have + * a separate has_field that is checked earlier in this if. + */ + pb_field_iter_t iter; + if (pb_field_iter_begin(&iter, field->submsg_desc, field->pData)) + { + do + { + if (!pb_check_proto3_default_value(&iter)) + { + return false; + } + } while (pb_field_iter_next(&iter)); + } + return true; + } + } + else if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + return field->pData == NULL; + } + else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) + { + if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) + { + const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData; + return extension == NULL; + } + else if (field->descriptor->field_callback == pb_default_field_callback) + { + pb_callback_t *pCallback = (pb_callback_t*)field->pData; + return pCallback->funcs.encode == NULL; + } + else + { + return field->descriptor->field_callback == NULL; + } + } + + return false; /* Not typically reached, safe default for weird special cases. */ +} + +/* Encode a field with static or pointer allocation, i.e. one whose data + * is available to the encoder directly. */ +static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + if (!field->pData) + { + /* Missing pointer field */ + return true; + } + + if (!pb_encode_tag_for_field(stream, field)) + return false; + + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_BOOL: + return pb_enc_bool(stream, field); + + case PB_LTYPE_VARINT: + case PB_LTYPE_UVARINT: + case PB_LTYPE_SVARINT: + return pb_enc_varint(stream, field); + + case PB_LTYPE_FIXED32: + case PB_LTYPE_FIXED64: + return pb_enc_fixed(stream, field); + + case PB_LTYPE_BYTES: + return pb_enc_bytes(stream, field); + + case PB_LTYPE_STRING: + return pb_enc_string(stream, field); + + case PB_LTYPE_SUBMESSAGE: + case PB_LTYPE_SUBMSG_W_CB: + return pb_enc_submessage(stream, field); + + case PB_LTYPE_FIXED_LENGTH_BYTES: + return pb_enc_fixed_length_bytes(stream, field); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +/* Encode a field with callback semantics. This means that a user function is + * called to provide and encode the actual data. */ +static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + if (field->descriptor->field_callback != NULL) + { + if (!field->descriptor->field_callback(NULL, stream, field)) + PB_RETURN_ERROR(stream, "callback error"); + } + return true; +} + +/* Encode a single field of any callback, pointer or static type. */ +static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field) +{ + /* Check field presence */ + if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) + { + if (*(const pb_size_t*)field->pSize != field->tag) + { + /* Different type oneof field */ + return true; + } + } + else if (PB_HTYPE(field->type) == PB_HTYPE_OPTIONAL) + { + if (field->pSize) + { + if (safe_read_bool(field->pSize) == false) + { + /* Missing optional field */ + return true; + } + } + else if (PB_ATYPE(field->type) == PB_ATYPE_STATIC) + { + /* Proto3 singular field */ + if (pb_check_proto3_default_value(field)) + return true; + } + } + + if (!field->pData) + { + if (PB_HTYPE(field->type) == PB_HTYPE_REQUIRED) + PB_RETURN_ERROR(stream, "missing required field"); + + /* Pointer field set to NULL */ + return true; + } + + /* Then encode field contents */ + if (PB_ATYPE(field->type) == PB_ATYPE_CALLBACK) + { + return encode_callback_field(stream, field); + } + else if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED) + { + return encode_array(stream, field); + } + else + { + return encode_basic_field(stream, field); + } +} + +/* Default handler for extension fields. Expects to have a pb_msgdesc_t + * pointer in the extension->type->arg field, pointing to a message with + * only one field in it. */ +static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension) +{ + pb_field_iter_t iter; + + if (!pb_field_iter_begin_extension_const(&iter, extension)) + PB_RETURN_ERROR(stream, "invalid extension"); + + return encode_field(stream, &iter); +} + + +/* Walk through all the registered extensions and give them a chance + * to encode themselves. */ +static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData; + + while (extension) + { + bool status; + if (extension->type->encode) + status = extension->type->encode(stream, extension); + else + status = default_extension_encoder(stream, extension); + + if (!status) + return false; + + extension = extension->next; + } + + return true; +} + +/********************* + * Encode all fields * + *********************/ + +bool checkreturn pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct) +{ + pb_field_iter_t iter; + if (!pb_field_iter_begin_const(&iter, fields, src_struct)) + return true; /* Empty message type */ + + do { + if (PB_LTYPE(iter.type) == PB_LTYPE_EXTENSION) + { + /* Special case for the extension field placeholder */ + if (!encode_extension_field(stream, &iter)) + return false; + } + else + { + /* Regular field */ + if (!encode_field(stream, &iter)) + return false; + } + } while (pb_field_iter_next(&iter)); + + return true; +} + +bool checkreturn pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags) +{ + if ((flags & PB_ENCODE_DELIMITED) != 0) + { + return pb_encode_submessage(stream, fields, src_struct); + } + else if ((flags & PB_ENCODE_NULLTERMINATED) != 0) + { + const pb_byte_t zero = 0; + + if (!pb_encode(stream, fields, src_struct)) + return false; + + return pb_write(stream, &zero, 1); + } + else + { + return pb_encode(stream, fields, src_struct); + } +} + +bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct) +{ + pb_ostream_t stream = PB_OSTREAM_SIZING; + + if (!pb_encode(&stream, fields, src_struct)) + return false; + + *size = stream.bytes_written; + return true; +} + +/******************** + * Helper functions * + ********************/ + +/* This function avoids 64-bit shifts as they are quite slow on many platforms. */ +static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high) +{ + size_t i = 0; + pb_byte_t buffer[10]; + pb_byte_t byte = (pb_byte_t)(low & 0x7F); + low >>= 7; + + while (i < 4 && (low != 0 || high != 0)) + { + byte |= 0x80; + buffer[i++] = byte; + byte = (pb_byte_t)(low & 0x7F); + low >>= 7; + } + + if (high) + { + byte = (pb_byte_t)(byte | ((high & 0x07) << 4)); + high >>= 3; + + while (high) + { + byte |= 0x80; + buffer[i++] = byte; + byte = (pb_byte_t)(high & 0x7F); + high >>= 7; + } + } + + buffer[i++] = byte; + + return pb_write(stream, buffer, i); +} + +bool checkreturn pb_encode_varint(pb_ostream_t *stream, pb_uint64_t value) +{ + if (value <= 0x7F) + { + /* Fast path: single byte */ + pb_byte_t byte = (pb_byte_t)value; + return pb_write(stream, &byte, 1); + } + else + { +#ifdef PB_WITHOUT_64BIT + return pb_encode_varint_32(stream, value, 0); +#else + return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)(value >> 32)); +#endif + } +} + +bool checkreturn pb_encode_svarint(pb_ostream_t *stream, pb_int64_t value) +{ + pb_uint64_t zigzagged; + if (value < 0) + zigzagged = ~((pb_uint64_t)value << 1); + else + zigzagged = (pb_uint64_t)value << 1; + + return pb_encode_varint(stream, zigzagged); +} + +bool checkreturn pb_encode_fixed32(pb_ostream_t *stream, const void *value) +{ + uint32_t val = *(const uint32_t*)value; + pb_byte_t bytes[4]; + bytes[0] = (pb_byte_t)(val & 0xFF); + bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); + bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); + bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); + return pb_write(stream, bytes, 4); +} + +#ifndef PB_WITHOUT_64BIT +bool checkreturn pb_encode_fixed64(pb_ostream_t *stream, const void *value) +{ + uint64_t val = *(const uint64_t*)value; + pb_byte_t bytes[8]; + bytes[0] = (pb_byte_t)(val & 0xFF); + bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); + bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); + bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); + bytes[4] = (pb_byte_t)((val >> 32) & 0xFF); + bytes[5] = (pb_byte_t)((val >> 40) & 0xFF); + bytes[6] = (pb_byte_t)((val >> 48) & 0xFF); + bytes[7] = (pb_byte_t)((val >> 56) & 0xFF); + return pb_write(stream, bytes, 8); +} +#endif + +bool checkreturn pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number) +{ + pb_uint64_t tag = ((pb_uint64_t)field_number << 3) | wiretype; + return pb_encode_varint(stream, tag); +} + +bool pb_encode_tag_for_field ( pb_ostream_t* stream, const pb_field_iter_t* field ) +{ + pb_wire_type_t wiretype; + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_BOOL: + case PB_LTYPE_VARINT: + case PB_LTYPE_UVARINT: + case PB_LTYPE_SVARINT: + wiretype = PB_WT_VARINT; + break; + + case PB_LTYPE_FIXED32: + wiretype = PB_WT_32BIT; + break; + + case PB_LTYPE_FIXED64: + wiretype = PB_WT_64BIT; + break; + + case PB_LTYPE_BYTES: + case PB_LTYPE_STRING: + case PB_LTYPE_SUBMESSAGE: + case PB_LTYPE_SUBMSG_W_CB: + case PB_LTYPE_FIXED_LENGTH_BYTES: + wiretype = PB_WT_STRING; + break; + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } + + return pb_encode_tag(stream, wiretype, field->tag); +} + +bool checkreturn pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size) +{ + if (!pb_encode_varint(stream, (pb_uint64_t)size)) + return false; + + return pb_write(stream, buffer, size); +} + +bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct) +{ + /* First calculate the message size using a non-writing substream. */ + pb_ostream_t substream = PB_OSTREAM_SIZING; + size_t size; + bool status; + + if (!pb_encode(&substream, fields, src_struct)) + { +#ifndef PB_NO_ERRMSG + stream->errmsg = substream.errmsg; +#endif + return false; + } + + size = substream.bytes_written; + + if (!pb_encode_varint(stream, (pb_uint64_t)size)) + return false; + + if (stream->callback == NULL) + return pb_write(stream, NULL, size); /* Just sizing */ + + if (stream->bytes_written + size > stream->max_size) + PB_RETURN_ERROR(stream, "stream full"); + + /* Use a substream to verify that a callback doesn't write more than + * what it did the first time. */ + substream.callback = stream->callback; + substream.state = stream->state; + substream.max_size = size; + substream.bytes_written = 0; +#ifndef PB_NO_ERRMSG + substream.errmsg = NULL; +#endif + + status = pb_encode(&substream, fields, src_struct); + + stream->bytes_written += substream.bytes_written; + stream->state = substream.state; +#ifndef PB_NO_ERRMSG + stream->errmsg = substream.errmsg; +#endif + + if (substream.bytes_written != size) + PB_RETURN_ERROR(stream, "submsg size changed"); + + return status; +} + +/* Field encoders */ + +static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + uint32_t value = safe_read_bool(field->pData) ? 1 : 0; + PB_UNUSED(field); + return pb_encode_varint(stream, value); +} + +static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + if (PB_LTYPE(field->type) == PB_LTYPE_UVARINT) + { + /* Perform unsigned integer extension */ + pb_uint64_t value = 0; + + if (field->data_size == sizeof(uint_least8_t)) + value = *(const uint_least8_t*)field->pData; + else if (field->data_size == sizeof(uint_least16_t)) + value = *(const uint_least16_t*)field->pData; + else if (field->data_size == sizeof(uint32_t)) + value = *(const uint32_t*)field->pData; + else if (field->data_size == sizeof(pb_uint64_t)) + value = *(const pb_uint64_t*)field->pData; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + return pb_encode_varint(stream, value); + } + else + { + /* Perform signed integer extension */ + pb_int64_t value = 0; + + if (field->data_size == sizeof(int_least8_t)) + value = *(const int_least8_t*)field->pData; + else if (field->data_size == sizeof(int_least16_t)) + value = *(const int_least16_t*)field->pData; + else if (field->data_size == sizeof(int32_t)) + value = *(const int32_t*)field->pData; + else if (field->data_size == sizeof(pb_int64_t)) + value = *(const pb_int64_t*)field->pData; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + if (PB_LTYPE(field->type) == PB_LTYPE_SVARINT) + return pb_encode_svarint(stream, value); +#ifdef PB_WITHOUT_64BIT + else if (value < 0) + return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)-1); +#endif + else + return pb_encode_varint(stream, (pb_uint64_t)value); + + } +} + +static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field) +{ +#ifdef PB_CONVERT_DOUBLE_FLOAT + if (field->data_size == sizeof(float) && PB_LTYPE(field->type) == PB_LTYPE_FIXED64) + { + return pb_encode_float_as_double(stream, *(float*)field->pData); + } +#endif + + if (field->data_size == sizeof(uint32_t)) + { + return pb_encode_fixed32(stream, field->pData); + } +#ifndef PB_WITHOUT_64BIT + else if (field->data_size == sizeof(uint64_t)) + { + return pb_encode_fixed64(stream, field->pData); + } +#endif + else + { + PB_RETURN_ERROR(stream, "invalid data_size"); + } +} + +static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + const pb_bytes_array_t *bytes = NULL; + + bytes = (const pb_bytes_array_t*)field->pData; + + if (bytes == NULL) + { + /* Treat null pointer as an empty bytes field */ + return pb_encode_string(stream, NULL, 0); + } + + if (PB_ATYPE(field->type) == PB_ATYPE_STATIC && + bytes->size > field->data_size - offsetof(pb_bytes_array_t, bytes)) + { + PB_RETURN_ERROR(stream, "bytes size exceeded"); + } + + return pb_encode_string(stream, bytes->bytes, (size_t)bytes->size); +} + +static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + size_t size = 0; + size_t max_size = (size_t)field->data_size; + const char *str = (const char*)field->pData; + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { + max_size = (size_t)-1; + } + else + { + /* pb_dec_string() assumes string fields end with a null + * terminator when the type isn't PB_ATYPE_POINTER, so we + * shouldn't allow more than max-1 bytes to be written to + * allow space for the null terminator. + */ + if (max_size == 0) + PB_RETURN_ERROR(stream, "zero-length string"); + + max_size -= 1; + } + + + if (str == NULL) + { + size = 0; /* Treat null pointer as an empty string */ + } + else + { + const char *p = str; + + /* strnlen() is not always available, so just use a loop */ + while (size < max_size && *p != '\0') + { + size++; + p++; + } + + if (*p != '\0') + { + PB_RETURN_ERROR(stream, "unterminated string"); + } + } + +#ifdef PB_VALIDATE_UTF8 + if (!pb_validate_utf8(str)) + PB_RETURN_ERROR(stream, "invalid utf8"); +#endif + + return pb_encode_string(stream, (const pb_byte_t*)str, size); +} + +static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + if (field->submsg_desc == NULL) + PB_RETURN_ERROR(stream, "invalid field descriptor"); + + if (PB_LTYPE(field->type) == PB_LTYPE_SUBMSG_W_CB && field->pSize != NULL) + { + /* Message callback is stored right before pSize. */ + pb_callback_t *callback = (pb_callback_t*)field->pSize - 1; + if (callback->funcs.encode) + { + if (!callback->funcs.encode(stream, field, &callback->arg)) + return false; + } + } + + return pb_encode_submessage(stream, field->submsg_desc, field->pData); +} + +static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + return pb_encode_string(stream, (const pb_byte_t*)field->pData, (size_t)field->data_size); +} + +#ifdef PB_CONVERT_DOUBLE_FLOAT +bool pb_encode_float_as_double(pb_ostream_t *stream, float value) +{ + union { float f; uint32_t i; } in; + uint_least8_t sign; + int exponent; + uint64_t mantissa; + + in.f = value; + + /* Decompose input value */ + sign = (uint_least8_t)((in.i >> 31) & 1); + exponent = (int)((in.i >> 23) & 0xFF) - 127; + mantissa = in.i & 0x7FFFFF; + + if (exponent == 128) + { + /* Special value (NaN etc.) */ + exponent = 1024; + } + else if (exponent == -127) + { + if (!mantissa) + { + /* Zero */ + exponent = -1023; + } + else + { + /* Denormalized */ + mantissa <<= 1; + while (!(mantissa & 0x800000)) + { + mantissa <<= 1; + exponent--; + } + mantissa &= 0x7FFFFF; + } + } + + /* Combine fields */ + mantissa <<= 29; + mantissa |= (uint64_t)(exponent + 1023) << 52; + mantissa |= (uint64_t)sign << 63; + + return pb_encode_fixed64(stream, &mantissa); +} +#endif diff --git a/components/nanopb/pb_encode.h b/components/nanopb/pb_encode.h new file mode 100644 index 0000000..88e246a --- /dev/null +++ b/components/nanopb/pb_encode.h @@ -0,0 +1,185 @@ +/* pb_encode.h: Functions to encode protocol buffers. Depends on pb_encode.c. + * The main function is pb_encode. You also need an output stream, and the + * field descriptions created by nanopb_generator.py. + */ + +#ifndef PB_ENCODE_H_INCLUDED +#define PB_ENCODE_H_INCLUDED + +#include "pb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structure for defining custom output streams. You will need to provide + * a callback function to write the bytes to your storage, which can be + * for example a file or a network socket. + * + * The callback must conform to these rules: + * + * 1) Return false on IO errors. This will cause encoding to abort. + * 2) You can use state to store your own data (e.g. buffer pointer). + * 3) pb_write will update bytes_written after your callback runs. + * 4) Substreams will modify max_size and bytes_written. Don't use them + * to calculate any pointers. + */ +struct pb_ostream_s +{ +#ifdef PB_BUFFER_ONLY + /* Callback pointer is not used in buffer-only configuration. + * Having an int pointer here allows binary compatibility but + * gives an error if someone tries to assign callback function. + * Also, NULL pointer marks a 'sizing stream' that does not + * write anything. + */ + int *callback; +#else + bool (*callback)(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); +#endif + void *state; /* Free field for use by callback implementation. */ + size_t max_size; /* Limit number of output bytes written (or use SIZE_MAX). */ + size_t bytes_written; /* Number of bytes written so far. */ + +#ifndef PB_NO_ERRMSG + const char *errmsg; +#endif +}; + +/*************************** + * Main encoding functions * + ***************************/ + +/* Encode a single protocol buffers message from C structure into a stream. + * Returns true on success, false on any failure. + * The actual struct pointed to by src_struct must match the description in fields. + * All required fields in the struct are assumed to have been filled in. + * + * Example usage: + * MyMessage msg = {}; + * uint8_t buffer[64]; + * pb_ostream_t stream; + * + * msg.field1 = 42; + * stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); + * pb_encode(&stream, MyMessage_fields, &msg); + */ +bool pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct); + +/* Extended version of pb_encode, with several options to control the + * encoding process: + * + * PB_ENCODE_DELIMITED: Prepend the length of message as a varint. + * Corresponds to writeDelimitedTo() in Google's + * protobuf API. + * + * PB_ENCODE_NULLTERMINATED: Append a null byte to the message for termination. + * NOTE: This behaviour is not supported in most other + * protobuf implementations, so PB_ENCODE_DELIMITED + * is a better option for compatibility. + */ +#define PB_ENCODE_DELIMITED 0x02U +#define PB_ENCODE_NULLTERMINATED 0x04U +bool pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags); + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define pb_encode_delimited(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_DELIMITED) +#define pb_encode_nullterminated(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_NULLTERMINATED) + +/* Encode the message to get the size of the encoded data, but do not store + * the data. */ +bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct); + +/************************************** + * Functions for manipulating streams * + **************************************/ + +/* Create an output stream for writing into a memory buffer. + * The number of bytes written can be found in stream.bytes_written after + * encoding the message. + * + * Alternatively, you can use a custom stream that writes directly to e.g. + * a file or a network socket. + */ +pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize); + +/* Pseudo-stream for measuring the size of a message without actually storing + * the encoded data. + * + * Example usage: + * MyMessage msg = {}; + * pb_ostream_t stream = PB_OSTREAM_SIZING; + * pb_encode(&stream, MyMessage_fields, &msg); + * printf("Message size is %d\n", stream.bytes_written); + */ +#ifndef PB_NO_ERRMSG +#define PB_OSTREAM_SIZING {0,0,0,0,0} +#else +#define PB_OSTREAM_SIZING {0,0,0,0} +#endif + +/* Function to write into a pb_ostream_t stream. You can use this if you need + * to append or prepend some custom headers to the message. + */ +bool pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); + + +/************************************************ + * Helper functions for writing field callbacks * + ************************************************/ + +/* Encode field header based on type and field number defined in the field + * structure. Call this from the callback before writing out field contents. */ +bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_iter_t *field); + +/* Encode field header by manually specifing wire type. You need to use this + * if you want to write out packed arrays from a callback field. */ +bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number); + +/* Encode an integer in the varint format. + * This works for bool, enum, int32, int64, uint32 and uint64 field types. */ +#ifndef PB_WITHOUT_64BIT +bool pb_encode_varint(pb_ostream_t *stream, uint64_t value); +#else +bool pb_encode_varint(pb_ostream_t *stream, uint32_t value); +#endif + +/* Encode an integer in the zig-zagged svarint format. + * This works for sint32 and sint64. */ +#ifndef PB_WITHOUT_64BIT +bool pb_encode_svarint(pb_ostream_t *stream, int64_t value); +#else +bool pb_encode_svarint(pb_ostream_t *stream, int32_t value); +#endif + +/* Encode a string or bytes type field. For strings, pass strlen(s) as size. */ +bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size); + +/* Encode a fixed32, sfixed32 or float value. + * You need to pass a pointer to a 4-byte wide C variable. */ +bool pb_encode_fixed32(pb_ostream_t *stream, const void *value); + +#ifndef PB_WITHOUT_64BIT +/* Encode a fixed64, sfixed64 or double value. + * You need to pass a pointer to a 8-byte wide C variable. */ +bool pb_encode_fixed64(pb_ostream_t *stream, const void *value); +#endif + +#ifdef PB_CONVERT_DOUBLE_FLOAT +/* Encode a float value so that it appears like a double in the encoded + * message. */ +bool pb_encode_float_as_double(pb_ostream_t *stream, float value); +#endif + +/* Encode a submessage field. + * You need to pass the pb_field_t array and pointer to struct, just like + * with pb_encode(). This internally encodes the submessage twice, first to + * calculate message size and then to actually write it out. + */ +bool pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/components/ssd1306/include/ssd1306.h b/components/ssd1306/include/ssd1306.h index 89c48a5..6e5a531 100644 --- a/components/ssd1306/include/ssd1306.h +++ b/components/ssd1306/include/ssd1306.h @@ -91,8 +91,27 @@ void ssd1306_clear_line(uint8_t i2address, uint8_t line, bool invert); */ void ssd1306_clear(uint8_t i2address); + /** - * @brief write text to display + * @brief set display on or offf + * + * @param[in] i2address I2C address of SSD1306 + * @param[in] on true if display on, false if display off + */ +void ssd1306_on(uint8_t i2address, bool on); + +/** + * @brief write text to display line at starting column + * + * @param[in] i2address I2C address of SSD1306 + * @param[in] text text to display + * @param[in] line the line to write to + * @param[in] offset number of offset chars to start + */ +void ssd1306_text_line_column(uint8_t i2address, char *text, uint8_t line, uint8_t offset, bool invert); + +/** + * @brief write text to display line * * @param[in] i2address I2C address of SSD1306 * @param[in] text text to display diff --git a/components/ssd1306/ssd1306.c b/components/ssd1306/ssd1306.c index 12a3fcb..53d07c4 100644 --- a/components/ssd1306/ssd1306.c +++ b/components/ssd1306/ssd1306.c @@ -80,7 +80,7 @@ void ssd1306_start(uint8_t i2address) // Turn the Display ON i2c_master_write_byte(cmd, SSD1306_CMD_ON, true); i2c_master_stop(cmd); - ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS)); + ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS)); i2c_cmd_link_delete(cmd); } @@ -101,7 +101,7 @@ void ssd1306_init_data(uint8_t i2address) i2c_master_write_byte(cmd, SSD1306_CMD_PAGE, true); i2c_master_stop(cmd); - ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS)); + ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS)); i2c_cmd_link_delete(cmd); } @@ -144,8 +144,46 @@ void ssd1306_clear(uint8_t i2address) } } -void ssd1306_text_line(uint8_t i2address, char *text, uint8_t line, bool invert) +void ssd1306_on(uint8_t i2address, bool on) { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + + i2c_master_start(cmd); + // Begin the I2C comm with SSD1306's address (SLA+Write) + i2c_master_write_byte(cmd, (i2address << 1) | I2C_MASTER_WRITE, true); + // Tell the SSD1306 that a command stream is incoming + i2c_master_write_byte(cmd, SSD1306_CONTROL_CMD_STREAM, true); + if (on) + { + // Turn the Display ON + i2c_master_write_byte(cmd, SSD1306_CMD_ON, true); + } + else + { + // Turn the Display OFF + i2c_master_write_byte(cmd, SSD1306_CMD_OFF, true); + } + + i2c_master_stop(cmd); + i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); +} + +void ssd1306_text_line_column(uint8_t i2address, char *text, uint8_t line, uint8_t offset, bool invert) +{ + + uint8_t font_width = sizeof(ascii_font_5x8[0]); + uint8_t column = offset * font_width; + if (column > SSD1306_COLUMNS) + { + column = 0; + } + size_t columns = strlen(text) * font_width; + if (columns > (SSD1306_COLUMNS - column)) + { + columns = (SSD1306_COLUMNS - column); + } + ssd1306_init_data(i2address); i2c_cmd_handle_t cmd; @@ -155,24 +193,23 @@ void ssd1306_text_line(uint8_t i2address, char *text, uint8_t line, bool invert) i2c_master_write_byte(cmd, (i2address << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(cmd, SSD1306_CONTROL_CMD_STREAM, true); - i2c_master_write_byte(cmd, SSD1306_CMD_COLUMN_LOW, true); - i2c_master_write_byte(cmd, SSD1306_CMD_COLUMN_HIGH, true); + i2c_master_write_byte(cmd, SSD1306_CMD_COLUMN_LOW | (column & 0XF), true); + i2c_master_write_byte(cmd, SSD1306_CMD_COLUMN_HIGH | (column >> 4), true); i2c_master_write_byte(cmd, SSD1306_CMD_PAGE | line, true); i2c_master_stop(cmd); i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS); i2c_cmd_link_delete(cmd); - uint8_t *linedata = calloc(SSD1306_COLUMNS, sizeof(uint8_t)); - uint8_t font_width = sizeof(ascii_font_5x8[0]); - for (uint8_t i = 0; i < strlen(text); i++) + uint8_t *linedata = calloc(columns, sizeof(uint8_t)); + for (uint8_t i = 0; i < (columns / font_width); i++) { memcpy(&linedata[i * font_width], ascii_font_5x8[(uint8_t)text[i]], font_width); } if (invert) { - for (uint8_t i = 0; i < SSD1306_COLUMNS; i++) + for (uint8_t i = 0; i < columns; i++) { linedata[i] = ~linedata[i]; } @@ -183,11 +220,16 @@ void ssd1306_text_line(uint8_t i2address, char *text, uint8_t line, bool invert) i2c_master_write_byte(cmd, (i2address << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(cmd, SSD1306_CONTROL_DATA_STREAM, true); - i2c_master_write(cmd, linedata, SSD1306_COLUMNS, true); + i2c_master_write(cmd, linedata, columns, true); i2c_master_stop(cmd); i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS); i2c_cmd_link_delete(cmd); free(linedata); +} + +void ssd1306_text_line(uint8_t i2address, char *text, uint8_t line, bool invert) +{ + ssd1306_text_line_column(i2address, text, line, 0, invert); } \ No newline at end of file diff --git a/main/main.c b/main/main.c index 4bc27ce..fa2e7a3 100644 --- a/main/main.c +++ b/main/main.c @@ -22,34 +22,151 @@ #include "ena.h" #include "ena-storage.h" +#include "ena-beacons.h" +#include "ena-exposure.h" #include "ena-interface.h" #include "ena-interface-menu.h" +#include "ena-interface-datetime.h" #include "ssd1306.h" +#include "ds3231.h" #include "sdkconfig.h" +static time_t curtime; + +void interface_display_time(void *pvParameter) +{ + static char *curtime_text; + static struct tm rtc_time; + static bool edit_invert = false; + while (1) + { + curtime = time(NULL); + localtime_r(&curtime, &rtc_time); + curtime_text = asctime(&rtc_time); + ssd1306_text_line(SSD1306_ADDRESS, curtime_text, 0, false); + gmtime_r(&curtime, &rtc_time); + curtime_text = asctime(&rtc_time); + ssd1306_text_line(SSD1306_ADDRESS, curtime_text, 1, false); + if (ena_interface_get_state() == ENA_INTERFACE_STATE_SET_DATETIME) + { + edit_invert = !edit_invert; + ds3231_set_time(&rtc_time); + char edit_year[4] = ""; + char edit_month[3] = ""; + char edit_day[2] = ""; + char edit_hour[2] = ""; + char edit_minute[2] = ""; + char edit_second[2] = ""; + switch (ena_interface_datetime_state()) + { + + case ENA_INTERFACE_DATETIME_STATE_YEAR: + memcpy(&edit_year, &curtime_text[20], 4); + ssd1306_text_line_column(SSD1306_ADDRESS, edit_year, 0, 20, edit_invert); + break; + case ENA_INTERFACE_DATETIME_STATE_MONTH: + memcpy(&edit_month, &curtime_text[4], 3); + ssd1306_text_line_column(SSD1306_ADDRESS, edit_month, 0, 4, edit_invert); + break; + case ENA_INTERFACE_DATETIME_STATE_DAY: + memcpy(&edit_day, &curtime_text[8], 2); + ssd1306_text_line_column(SSD1306_ADDRESS, edit_day, 0, 8, edit_invert); + break; + case ENA_INTERFACE_DATETIME_STATE_HOUR: + memcpy(&edit_hour, &curtime_text[11], 2); + ssd1306_text_line_column(SSD1306_ADDRESS, edit_hour, 0, 11, edit_invert); + break; + case ENA_INTERFACE_DATETIME_STATE_MINUTE: + memcpy(&edit_minute, &curtime_text[14], 2); + ssd1306_text_line_column(SSD1306_ADDRESS, edit_minute, 0, 14, edit_invert); + break; + case ENA_INTERFACE_DATETIME_STATE_SECONDS: + memcpy(&edit_second[0], &curtime_text[17], 2); + ssd1306_text_line_column(SSD1306_ADDRESS, edit_second, 0, 17, edit_invert); + break; + } + } + vTaskDelay(500 / portTICK_PERIOD_MS); + } +} + +void interface_display_status(void *pvParameter) +{ + static bool get_status = true; + while (1) + { + if (ena_interface_get_state() == ENA_INTERFACE_STATE_STATUS) + { + if (get_status) + { + ena_exposure_summary_t summary; + ena_exposure_summary(ena_exposure_default_config(), &summary); + char buffer[23]; + sprintf(buffer, "Days: %d", summary.days_since_last_exposure); + ssd1306_text_line(SSD1306_ADDRESS, buffer, 3, false); + sprintf(buffer, "Exposures: %d", summary.num_exposures); + ssd1306_text_line(SSD1306_ADDRESS, buffer, 4, false); + sprintf(buffer, "Score: %d, Max: %d", summary.risk_score_sum, summary.max_risk_score); + ssd1306_text_line(SSD1306_ADDRESS, buffer, 5, false); + get_status = false; + } + } + else if (!get_status) + { + ssd1306_clear_line(SSD1306_ADDRESS, 2, false); + ssd1306_clear_line(SSD1306_ADDRESS, 3, false); + ssd1306_clear_line(SSD1306_ADDRESS, 4, false); + get_status = true; + } + vTaskDelay(500 / portTICK_PERIOD_MS); + } +} + +void interface_display_idle(void *pvParameter) +{ + static bool set_status = true; + while (1) + { + if (ena_interface_get_state() == ENA_INTERFACE_STATE_IDLE) + { + if (set_status) + { + ssd1306_on(SSD1306_ADDRESS, false); + set_status = false; + } + } + else if (!set_status) + { + ssd1306_on(SSD1306_ADDRESS, true); + set_status = true; + } + vTaskDelay(500 / portTICK_PERIOD_MS); + } +} + void app_main(void) { - // DEBUG set time - struct timeval tv = {1594843200, 0}; // current hardcoded timestamp ¯\_(ツ)_/¯ + struct tm rtc_time; + ds3231_get_time(&rtc_time); + curtime = mktime(&rtc_time); + struct timeval tv = {0}; + tv.tv_sec = curtime; settimeofday(&tv, NULL); - esp_log_level_set(ENA_STORAGE_LOG, ESP_LOG_INFO); + setenv("TZ", "UTC-2", 1); + tzset(); + ssd1306_start(SSD1306_ADDRESS); ssd1306_clear(SSD1306_ADDRESS); - // TODO - ssd1306_text_line(SSD1306_ADDRESS, " TODO TODO", 0, false); - ssd1306_text_line(SSD1306_ADDRESS, " TODO", 1, true); - ssd1306_text_line(SSD1306_ADDRESS, " TODO TODO", 2, false); - ssd1306_text_line(SSD1306_ADDRESS, " TODO", 3, true); - ssd1306_text_line(SSD1306_ADDRESS, " TODO TODO", 4, false); - ssd1306_text_line(SSD1306_ADDRESS, " TODO", 5, true); - ssd1306_text_line(SSD1306_ADDRESS, " TODO TODO", 6, false); - ssd1306_text_line(SSD1306_ADDRESS, " TODO", 7, true); - ena_interface_start(); ena_interface_menu_start(); + ena_start(); + + xTaskCreate(&interface_display_time, "interface_display_time", 4096, NULL, 5, NULL); + xTaskCreate(&interface_display_status, "interface_display_status", 4096, NULL, 5, NULL); + xTaskCreate(&interface_display_idle, "interface_display_idle", 4096, NULL, 5, NULL); }