From 4029099d2a054addec899b9783f6dcb67d1dae94 Mon Sep 17 00:00:00 2001 From: Lurkars Date: Wed, 21 Oct 2020 19:18:08 +0200 Subject: [PATCH] re-added ena-components, update interface --- CMakeLists.txt | 8 - README.md | 2 +- components/ena-binary-export/CMakeLists.txt | 10 + .../ena-binary-export/ena-binary-export.c | 90 + .../ena-binary-export/ena-binary-export.h | 37 + components/ena-binary-export/test/export.bin | Bin 0 -> 5913 bytes .../ena-binary-export/test/export_g.bin | Bin 0 -> 135 bytes .../ena-binary-export/test/export_g.sig | Bin 0 -> 115 bytes .../ena-binary-export/test/export_tel.bin | Bin 0 -> 21696 bytes .../ena-binary-export/test/export_tel.sig | Bin 0 -> 134 bytes components/ena/CMakeLists.txt | 14 + components/ena/Kconfig.projbuild | 94 + components/ena/ena-beacons.c | 123 ++ components/ena/ena-bluetooth-advertise.c | 88 + components/ena/ena-bluetooth-scan.c | 113 ++ components/ena/ena-crypto.c | 96 + components/ena/ena-exposure.c | 327 +++ components/ena/ena-storage.c | 504 +++++ components/ena/ena.c | 185 ++ components/ena/include/ena-beacons.h | 62 + .../ena/include/ena-bluetooth-advertise.h | 52 + components/ena/include/ena-bluetooth-scan.h | 69 + components/ena/include/ena-crypto.h | 126 ++ components/ena/include/ena-exposure.h | 249 +++ components/ena/include/ena-storage.h | 302 +++ components/ena/include/ena.h | 49 + components/interface/interface-data.c | 2 +- components/interface/interface-wifi.c | 1 + components/nanopb/CMakeLists.txt | 8 + components/nanopb/LICENSE.txt | 20 + .../nanopb/TemporaryExposureKeyExport.pb.c | 19 + .../nanopb/TemporaryExposureKeyExport.pb.h | 147 ++ .../nanopb/TemporaryExposureKeyExport.proto | 65 + components/nanopb/pb.h | 868 ++++++++ components/nanopb/pb_common.c | 345 ++++ components/nanopb/pb_common.h | 45 + components/nanopb/pb_decode.c | 1745 +++++++++++++++++ components/nanopb/pb_decode.h | 196 ++ components/nanopb/pb_encode.c | 978 +++++++++ components/nanopb/pb_encode.h | 185 ++ 40 files changed, 7214 insertions(+), 10 deletions(-) create mode 100644 components/ena-binary-export/CMakeLists.txt create mode 100644 components/ena-binary-export/ena-binary-export.c create mode 100644 components/ena-binary-export/ena-binary-export.h create mode 100644 components/ena-binary-export/test/export.bin create mode 100644 components/ena-binary-export/test/export_g.bin create mode 100644 components/ena-binary-export/test/export_g.sig create mode 100644 components/ena-binary-export/test/export_tel.bin create mode 100644 components/ena-binary-export/test/export_tel.sig create mode 100644 components/ena/CMakeLists.txt create mode 100644 components/ena/Kconfig.projbuild create mode 100644 components/ena/ena-beacons.c create mode 100644 components/ena/ena-bluetooth-advertise.c create mode 100644 components/ena/ena-bluetooth-scan.c create mode 100644 components/ena/ena-crypto.c create mode 100644 components/ena/ena-exposure.c create mode 100644 components/ena/ena-storage.c create mode 100644 components/ena/ena.c create mode 100644 components/ena/include/ena-beacons.h create mode 100644 components/ena/include/ena-bluetooth-advertise.h create mode 100644 components/ena/include/ena-bluetooth-scan.h create mode 100644 components/ena/include/ena-crypto.h create mode 100644 components/ena/include/ena-exposure.h create mode 100644 components/ena/include/ena-storage.h create mode 100644 components/ena/include/ena.h create mode 100644 components/nanopb/CMakeLists.txt create mode 100644 components/nanopb/LICENSE.txt create mode 100644 components/nanopb/TemporaryExposureKeyExport.pb.c create mode 100644 components/nanopb/TemporaryExposureKeyExport.pb.h create mode 100644 components/nanopb/TemporaryExposureKeyExport.proto create mode 100644 components/nanopb/pb.h create mode 100644 components/nanopb/pb_common.c create mode 100644 components/nanopb/pb_common.h create mode 100644 components/nanopb/pb_decode.c create mode 100644 components/nanopb/pb_decode.h create mode 100644 components/nanopb/pb_encode.c create mode 100644 components/nanopb/pb_encode.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 54331c7..173376a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,5 @@ # The following lines of boilerplate have to be in your project's # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) -include(FetchContent) -FetchContent_Declare( - esp-ena - GIT_REPOSITORY https://github.com/Lurkars/esp-ena.git - GIT_TAG origin/component -) -FetchContent_MakeAvailable(esp-ena) -set(EXTRA_COMPONENT_DIRS ${esp-ena_SOURCE_DIR}/components) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(esp-ena-device) \ No newline at end of file diff --git a/README.md b/README.md index 0997df7..eb73021 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 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 fully compatible with the official API and is meant for people without smartphone or without access to Apples/Googles implementation. -The main source (the Exposure Notification API) is a separate branch in [**component**](https://github.com/Lurkars/esp-ena/tree/component). +The main source (the Exposure Notification API) is also available in a separate branch in [**component**](https://github.com/Lurkars/esp-ena/tree/component). This implementation fully covers the BLE part including the cryptography specifications needed and the exposure check. diff --git a/components/ena-binary-export/CMakeLists.txt b/components/ena-binary-export/CMakeLists.txt new file mode 100644 index 0000000..63ce621 --- /dev/null +++ b/components/ena-binary-export/CMakeLists.txt @@ -0,0 +1,10 @@ +idf_component_register( + SRCS + "ena-binary-export.c" + INCLUDE_DIRS "." + PRIV_REQUIRES + ena + nanopb + EMBED_FILES + "test/export.bin" +) \ No newline at end of file diff --git a/components/ena-binary-export/ena-binary-export.c b/components/ena-binary-export/ena-binary-export.c new file mode 100644 index 0000000..e70c267 --- /dev/null +++ b/components/ena-binary-export/ena-binary-export.c @@ -0,0 +1,90 @@ +#include +#include +#include + +#include "esp_err.h" +#include "esp_log.h" + +#include "ena-exposure.h" + +#include "pb_decode.h" +#include "TemporaryExposureKeyExport.pb.h" + +static const char kFileHeader[] = "EK Export v1 "; +static size_t kFileHeaderSize = sizeof(kFileHeader) - 1; + +bool ena_binary_export_decode_key_data(pb_istream_t *stream, const pb_field_t *field, void **arg) +{ + uint8_t *key_data = (uint8_t *)*arg; + + if (!pb_read(stream, key_data, stream->bytes_left)) + { + ESP_LOGW(ENA_EXPOSURE_LOG, "Decoding failed: %s\n", PB_GET_ERROR(stream)); + return false; + } + + return true; +} + +bool ena_binary_export_decode_key(pb_istream_t *stream, const pb_field_t *field, void **arg) +{ + uint8_t key_data[ENA_KEY_LENGTH] = {0}; + TemporaryExposureKey tek = TemporaryExposureKey_init_zero; + + tek.key_data = (pb_callback_t){ + .funcs.decode = ena_binary_export_decode_key_data, + .arg = &key_data, + }; + + if (!pb_decode(stream, TemporaryExposureKey_fields, &tek)) + { + ESP_LOGW(ENA_EXPOSURE_LOG, "Decoding failed: %s\n", PB_GET_ERROR(stream)); + return false; + } + + ESP_LOGD(ENA_EXPOSURE_LOG, + "check reported tek: rolling_start_interval_number %d, rolling_period %d, days_since_last_exposure %d, report_type %d, transmission_risk_values %d", + tek.rolling_start_interval_number, tek.rolling_period, tek.days_since_onset_of_symptoms, tek.report_type, tek.transmission_risk_level); + + ESP_LOG_BUFFER_HEXDUMP(ENA_EXPOSURE_LOG, &key_data, ENA_KEY_LENGTH, ESP_LOG_DEBUG); + + ena_temporary_exposure_key_t temporary_exposure_key = { + .transmission_risk_level = tek.transmission_risk_level, + .rolling_start_interval_number = tek.rolling_start_interval_number, + .rolling_period = tek.rolling_period, + .report_type = tek.report_type, + .days_since_onset_of_symptoms = tek.days_since_onset_of_symptoms, + }; + + memcpy(temporary_exposure_key.key_data, key_data, ENA_KEY_LENGTH); + + ena_exposure_check_temporary_exposure_key(temporary_exposure_key); + + return true; +} + +esp_err_t ena_binary_export_check_export(uint8_t *buf, size_t size) +{ + // validate header + if (memcmp(kFileHeader, buf, kFileHeaderSize) != 0) + { + ESP_LOGW(ENA_EXPOSURE_LOG, "Wrong or missing header!"); + return ESP_FAIL; + } + + TemporaryExposureKeyExport tek_export = TemporaryExposureKeyExport_init_zero; + + tek_export.keys = (pb_callback_t){ + .funcs.decode = ena_binary_export_decode_key, + }; + + pb_istream_t stream = pb_istream_from_buffer(&buf[kFileHeaderSize], (size - kFileHeaderSize)); + + if (!pb_decode(&stream, TemporaryExposureKeyExport_fields, &tek_export)) + { + ESP_LOGW(ENA_EXPOSURE_LOG, "Decoding failed: %s\n", PB_GET_ERROR(&stream)); + return ESP_FAIL; + } + + return ESP_OK; +} \ No newline at end of file diff --git a/components/ena-binary-export/ena-binary-export.h b/components/ena-binary-export/ena-binary-export.h new file mode 100644 index 0000000..a448229 --- /dev/null +++ b/components/ena-binary-export/ena-binary-export.h @@ -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. +/** + * @file + * + * @brief decode Exposure Key export and run exposure check + * + */ +#ifndef _ena_BINARY_H_ +#define _ena_BINARY_H_ + +#include +#include "esp_err.h" + +/** + * @brief reads a Temporary Exposue Key Export binary and check for exposures + * + * @param[in] buf the buffer containing the binary data + * @param[in] size the size of the buffer + * + * @return + * esp_err_t status of reading binary + */ +esp_err_t ena_binary_export_check_export(uint8_t *buf, size_t size); + +#endif \ No newline at end of file diff --git a/components/ena-binary-export/test/export.bin b/components/ena-binary-export/test/export.bin new file mode 100644 index 0000000000000000000000000000000000000000..f9a14f3a208fd48f76a46dfeb2dc2c64e18054d2 GIT binary patch literal 5913 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 literal 0 HcmV?d00001 diff --git a/components/ena-binary-export/test/export_g.bin b/components/ena-binary-export/test/export_g.bin new file mode 100644 index 0000000000000000000000000000000000000000..0a10445be73fde72b2fce52fa34a32f98b8ccb35 GIT binary patch literal 135 zcmZ?uR&cE-$S*2UC^J+50?rRFj>jKH#I&p zMN8OF&q&Y0#6Zu`z`(>*&qU8y&&W!eOF&%zNW;?^o;)VvWh}vo6$f&>w dlGILSwK~$Q-21RhY+jqZ?O6dvi7t?25daH7BrpH~ literal 0 HcmV?d00001 diff --git a/components/ena-binary-export/test/export_g.sig b/components/ena-binary-export/test/export_g.sig new file mode 100644 index 0000000000000000000000000000000000000000..05a7d194507a4e391452ad6e5c8de0112d0b18bb GIT binary patch literal 115 zcmV-(0F3_%aS9jDA0cdD5IqR_dn!l za`LpOEU3Kv*u>_~IbYhm;C$xr(!t*JmFY`+Q%6T~G&df)YrKNItVBGVyqv=P+?+hz z-24KZ{G5E8yrPuY_$bGPkv|LK%wOabrE)56E8(M(W_-v(rHw{Kps1X#RZWj9S<>V* zTemQYL?CqN{r}K6o__T=vi&};R;jBJm{;LrlD4J)56wAQQi`6XWR34aA>ci`0ig?- z|Dk9^ym-w*5#QCGqhvL(DgA*wN2mP{4Oa@*NIjq8KE8XP|70^BLKl+$hsFgN>Ga$i z^l9@<#>J@Zg3!I>|Dhk4#;G_kWSZ9`TtaAD;(zEh!u5n#Xf64-|&`uw!`|PYYIsIiKAr#2>np?`XKlO8I+u!j`8aD6T!AA%4y*9+m6dj=V zXgF}^ra{9OU&t27==$)#Xtf`=ztyDsXVh?{F2Pasr`B1Yo7;t|7)iZC(j3?z6v!9- z6#K5G-IADpI$Jh=LGc%OB{4`QM1IpZLOnk$~P-<|$2&F%PGk61a;<+hF%EMpUsxlpWCr9w8ZrowOzaLgt zq|u{tONLlB7@pCM#B0~li4!S>EG-S<7hh+WuU7O?ipo)kf4}^vHV%1^C6p^2$DHCiKGlprlv5rSlX* z0R}O-98rQii8NEMow#r_mmNbWP-o1vRkl|9>#YknG*k)(6 zn|l1ZAzOf7EUFKm&zSR9maK}&S?3poAZ?U={Er1J#_+1u!&@c2>S$Z<&v!mUwt(tx z8dI2)asR1q67*8j-65TYd%oGnry6y|62Bd_c8``n9&V(TCd<$@=uf$yecgsD7N?|oC32%q`%-(2&WKxa9%6ql>%W+Bgz?PpyT9wkR5Rtl;Cz@Eho%KtlWU$bJ6F?}SoZGa8&UPcY>x;C-vZH~N zNVD#?2gaQ@l~?N}aXP`Io3Xa=d~XK~U4OkULj0<&W{32^QWME@ZGg;=Y0(*qE{skY z-KKl+I^Pl9W2Qb_^ogun8m}HnLCi^@_wP6pyEp%95qijM|BsxL&j&&Qeo11BI1@ey zCqCBr(q&-ikPe}MHb~ZVUUuc&Op9a>9MZpGrU>^;dR?lRR5ntFqc36XUqa=Gl2D5L$&}$MxGfp0$l@=R5bo^#=P0Hh)(0RIm?~sCn~`$6 z`tmu-JEcjHj9w55)QZf&qww~Gaw5vg4-f6Bxk5NcWN)vTl*{uPmyyVM{NT>6hgbmi zU$ReKOc|T8YJ@4fI%$6yQXtPj|B_9z2@9LMjJOO?TZT;&kHe!P4_`aw-ZvYH`m=oQ z@h^H1zAky;L~>#M+ecr=oc7UT7xLh?6fby9Dth?S4pt=lr>&Z>;2BZad->8{DKkZf z`(%doKSqa8ppuk~7uCkTa!#g%6D}7i#%u8Irc`{_*)1WDi%zP0JM}a`4YCFJrF^`< z^yP7wy!h>x`qeTv{cufEx>^)ej7EFDY<^NF{TJ&CWDD?1>Em)m@cJ?40G_MZtesOD zgaVqR%*?QRCz9WO{e|U>@mmSRQ~~NtnKSVDrbiO$r<-O^g~pr^JqYkic{!((XoMcx zCPuGXS>BfhWduxMD%TJEBx=rv^67cGRUQuHaL-h!EW%|P?HP(O__{Z8E{BoOvE@+Q zFKJ~0@8y2zmDA!u>jL~z%~SZFT&M5x8NzkgM5XM3=S#INhMH=DOK2`v+2b!g!2m~T zJ{{jrQV)3gP)5Z*{I;(QgaXx|*_DtcFORFCxy?SxYWyP<9u@7+#Nms|x8#k^&9!wU zK4kEG@A3}rCRZnZ3(FNg#QpoT5b_MvicT-d$Y!@UAddIOo@k2s2e>U=yIj6D5qU-u ztCTwPu^aIm7vxgN0*z+hd7G@)nH5=v+NljOm+RhDld*k zF$tl7o|(wZP8xNEL`yR9x!T-}vY|c#JTr0aHr1DYyI}3+9sc7?ybo0p*sYjs-jeHC zs9U?oyngC*?|LG<&P)!Mt=L9EWOe3_vm$z#*PwL)_L-S#NQnSJOg^Z$5BK+z*pfamv|BNQ-Ay={ET#OEnvBWRCgenTg1(UR=^A%73($fyB@<& z(-*xZ**C5$)<7sw4OW8pe!^H^P5!Vxae5kP*M?+!e>O?ll0W&*n=$^!0<3ay7FZiO zWbe|X6t+2DAG4h7*?`-!;oshqVDTP^DI0itr5y1Fekx#>txs?+*bBQ{rAHmezZ8c@ z#UA-2vNXnBrhIK%B~gyU6+S1~M>Z#`7Vl&hDiJL-j3^*D0+hpXPbB=6W-kg!Y3Nx_ z6B94omXlD6d&l*toYU>g(`KF*d(gT-ow*$I!+ZkykE6TsaAo)Kf5SQA+83iDd$vt{ zx{<1|bc?PE?wMQEIQaH0?#2tl>KBt@Gd;8X{6EebT+|^H&=ogy z@?H{g9V-6RAfWZm4zVt1+pNGnyZ&J8ZG7KMk&O)oyv{s~jkP-$90tEUQ8yoy^8uf9mRL}d{R&m1iLWgm*LXI$)8 z>~O&q%V*fLruO3cU_H)hTFs3PMcr_tcr;8%!TwWbp4wd!xL zj{eXj1la-^@tgFS-I-eZEkdifk#tq*1yvI0Q2v~izop#w{Ui4F8xkh@GOnURNCn zgaVor2+hM+u@Cj{U@MH3-f@wFGbnI~^{1}|gVTC$Iz(6ES2R3kfvZ35G5KS7;bWhU zWHDveA-UTgr*!s94Go?y@suNh98ZfVm-J()^I4;hoecoXk9ua3{pQ8$`>UE{X8Q zW)5z%8EuPvtbkB}BayzWaLTV{iVOFmd|C|%#GsggIVmzxD)&cM#`3u3aC-1{i8-7> z(d%4uvtQOGALhKlx#L})0B1q;j;}=i(|tDVy|CPgH$%qIx`67$G}mYSa||dVQB_l4 zPiVEmZN=#xf9|v5G#SiZu=|#lhuDvRn8jZUY)_K;6KqO~r`~Gty#=8_r-=Le@O7H7 zc6%xgph`FRqYb#*Y3X2aj6GDM%NRMTnmfj7!=WU5@d;L}@qPqc( zWF%{AE-wa^c|SxiM#V^Z!);|YM*r{}-bu}*e6trM>4UT{+#mTSaON1d!Gk5h9?M4h z5wZm;DO=dGDZ=@BS_rk+4EJhX5JCaf$+qE4ZU|<+EL+6AxJ-%}f>%R!tg5j{x2u%( z*zJK5raR)s2TW1f-503Zb8<%*nf(W*-vz(IZRN1%_M$Xn{S`^?JC$6qBhG|CHRMyx zk7Csw#ircH8470z5W5vn4f!805})mJpOcb&-Z(bkMobleeFZfkn|oe8N6l7Es!B}C znb5j`Vioi{oQY$W-$n(zGZ;V~;U33uD}J8<;9 zMQd3NgG&>X=!LC1ieD;Gl%;1$OIEaRqx~A2^%1@=sP(t&WrQD>P+}f>X`*qV z!7HgwxTN>{JV=ahX7aC^R`4$f1v*rnkwG*;W_S(Hl`>IoN+}8nHK$_luw0HVe^<>k z`MNIyiV8S0sPE?~{vtPWA80FFr(;^*fb;w4`gqX-wKz7#ZPrKM)sKqcF>9dR-8;V{ zlP*W8`0tNW1Jc`wh8AVWd)iOr+X7kDLg zVlNkROLAF01^YQZ{@u9%MFp_0t3AB<#h^-s{To$4*TT;-c+9%mfB!}wYpCg6Ql2!% ziJrmxS2yXWq5ocQyIu~Sn64LQ4m>K|Kg02T6aB7SS+d5Gwh|BEo}V0F*Gm3+l0O+- zvc}mcvl0;fpWmdMYC9V z%zEoIDiJD>H(@xcZ-#6CJ61)mgMuCZ ziO$Y_2nE!xU(OgQ==)rq0DZD}Br^>0#S@6ifaX_DxI6pPm&pu5(Gh-gNGPx2%TiDD zrljGH30L`$bvQ=`4{G0d4JUQP*cxK-V0?;0vW;gbyBJWO!s5m3ojut@yv74j8BBh) zw;#v;P#Z4C_2!If144m18|==jh0v$dZdJsXjkA3vv5jUv0FhN_aJ zX9=NzHjE6OS6V&@&-?x4X=v=NX~fwK=s{zNSQlExp8Hq+tZdCY`mp(=2||Gm zHHkB9y6!h0#?!P^gD#@t4)<(!jqhiI@Vi^Gk8J7w_S}39kJ(JN>raTtsZQC-9*=!Q zb~Stgn|TFyX}#(H7# z)kRSdl^+xppgQx|C$7ZLe_}XHc-94lyVXJ{a8@zzD^RL>JWzH+v^v1aornuQ+n-xZ z#*)0V8GZGX#`nQlWU zz>!6VZ&E8S21?7!m7b-^ek*(eTfWmC_iRah@ib)fT+f}(7FrjWb(T@s8^Q^mMFnK6 zoBFO>J4omK+?$6`pjK9TeWTBM z)8}kx5-3!=jc4IKXcf?Q<1t_FvycjjhY|+;qL3|6Nvo{5;fd46`-+DKO0A}?OYn@W zYCO~ioB5>7i}2nje`T+Qd$zt6-lu4_V{lfyTNCki*b|+YxVuWK0) zeFUs)?d>sm9)l($b}(hF!k>;b32j(9u()`GICKYc1KR`XufuI^+G@57S|o6L@b7ik zN8QVYuWO6?*56fy+Pq4nz|kTd^)b9wwjq~iG3gC_B5D<9zs02gLAHQmZBqv`@B9qO z*Au?=`Frwc1w3Ed&+AMYBpb%2Nsd1-pVlLK0r=H{?Yy!&Bc5|kXokX7-Z{}jxMw>` z;)OyV78%Rz$G#bQI7lx=cFY*|I5$0=+RO{Ej8Z66pqK&G*|S#g5-o?D4i;g&pDnWu zgHT}R*vm?G;kZ0dBwB0i*?T=Oi)7nEmO^cA-7@x|^bGsE9X%8kFp=yR-nQ(RZxy*) zxmq!d2B#u~Qt^A$BT#_Y>3~joDJwR3gVk8jr;x~qF{>RD$`|NCXXS^h+V2P(wmJ7T z$Cv#PXMUg>&ic$bM?Z(8s3m4Z&G0gW;Ojd3+H@8P%QIn;o}{V0pF!OC06jauqq`io z#g_PYS3^sH!X0s*1fp`D@OXMo5qC7~RK8@~ki?23iD<|cb8shN*yavQHX z1Mb;XG@{v*M{`6cIQ{O}a%m|NY9@>_L@q;9c-G;M7o&)HDFQmhb(AAw6+8MRB|B~L zM>?56cvNoJGdr#otUuvexmtZ>{0(tm0BYq%ZVkQgDZOw%Z&&Gd^>%~S1$K5f&+O>l z*B_L~wO@`tCx06WkIHRG@?s;knJvJXosq_$5%H1$^unvO0h9;d;*!)$F;q2nl*EzH z+@!H6Z<5@%X+wDIy1~&93UKtQpvBGi@O`R=tD5^dnRWw&0)7*?PL>QTX{@#pUOAQ-yc3VJPsT2IE>!*eNJMkuX0qMgggV??Q#C+WUkx% zHw*DRG1rY;O}OXR_UFZa5B*Q9eZO-LhL#%XQfHo%eo{eFQw_Hy$xH z`bWdcjDe4%*Qj!lzD>NTc#)H_eM4kdpQ@zBbQ0;d=pDyt9vAZ?&Y_+{R!?+B3-S!q z$~)7}A(Mn*Tjx^0>sx2hDV!tk2K~(r^^BJN5=LeV`as018o-f{toUi!z{eN`oVObK zbgvPw0f1*8FET@(^3Y!X#E7r=r6v)-bpdMS^ZsbJTqozAIuFg&yl50TlIK)3F*hwm zfy&*ITjr#m^zi=m$;T>q_Jl>(&exf}idpXlWD980=ah)XSYC7>=DOIkO4lu0v%=@B4;(4ErVdqzn+OoI?~@IU-q{p%jv!AH`n@Cgx_*YiidEikKhOlU zp)v0f#Uk07BpKhPR8K~A{E9p8lZu06Yd3ye%(L~LJ7+=vi2z$49QDUnenGgQ8m)S4 zO-5xE<^->mztk_2qx5-EPU&l^l3`Usdv{?Z}Q>UZy<4ZGKW_q*$i*vo+K_V>0D zEyy`}RWq2gzZ@Wd_-z2dzJD~eyJvB0g5oKMXmTPYQojCOsJg3{rBOzuNwMT@1?_Nt z{ZHPp*DXjm7`2{D9)yG>wV< znX4#uPe1HOq}~rB$TOh2;3eD?t)wjd!VsIMqcU~*@D2?LicWd1r4U^zr%chKusRKA zAtX9fS@56tI#w`k8S%5GK?nspC1geT-G{LC`>g(Ex3Weo_n>@%Jv5a1M!%pGGxxM@ zW(#V9;}v8JupdS;aW4L@IY6)_P?t7$svPb)%<0uicX&kkY_xnx=gV4B2nA{t*1>+K zDXu`9-f*gU6~ba#Z+8)u0o&6)3iyw=MtBnaT^M$|1u#B1!i z85_FbjT+v)LP8IoXD3aj_LX+ZN^5yu4}t4B(r*7j`Nc@0)UBnCS2{Nl_iliNC=^A- zrks>Nzx4!ssAgjj$^p>myDbrw@55qi%_A*+c%<#&%853{y;@AeHo%P{i1sCH(1Ax4 z9T;Ar->0kHsUCxh(relec?Ogd{Z4({z&4Q8;;{P}LG}KiXyw{nL;{b3$PG#{;6rwRGyYBNpT^tHQpWWIhJBs;?pPXC3f6015dY=FYs!_ zq1qA?u?{Y*a-3FkX0IZC&j4sL4(-dY-UkhXsW^@7X}vtqqfN zJZYAnSVI{Bze9e1d+|w!#en;pGT|yaaZAK26R_jFH~lfbBoe_wkZTnzr@_+zp@6R5 zPdKK=FS_@Ss1S{$M?$w$*sM$P?hRN)NT0%#l0x3?}q>&%kF_s>F2IC?YI7L9|XYF{(BkZnx5kdY_Gkgszqv zTv$C0-cVuFSbL$93ZEMvFO=7KtedYd$k2bzVQ3VFDhYIGdgAmfp@4z;8`NNVv}p>g(T zzmA*I@O3lemuk$9tNyIieV0k;s3e3}BeOdx{M9vMe7FARM((CPW)KRfE=&8WN3hU? zFIVEp>-%Cwh_AGOuCiQ|`UbDRyo37eSgLJ-8^Iu;_N=SFyaOgu^yGn;o%np|=x|+S zTT`Dr5N=x>#hYTibtZEI&S3U@CjBmx$3Ls2h9SKR>tV<IZDK4ABi^UN3=D6Dia?}2G$&n$AyfK$dF!FISzxLv%+$2Tbex*8)XF#!^ z3_sI3Gqm4ToKZg=>lm&_Lfr>fUy$8M_-9-aOTe;>K!JJnX?S52YcpH9OK>f(TIEI% z61v6pWv$0}oVKlWLaWOOF;#$S>XXHe!}dH6%X=9fVy;#2@cRe%xyNmRP>)T?zm z|NB1&q*sUnfxBYHEB5=(nPu_fE|(185;7Vo81k!+eEVM zc*K9aTJEAXHO-$uG1vv6KzA3ir_;?kO+@z6pHBDq#l42>s!*7S3D;vxmcR7Vr4$yw zH=M!30IkR$xB+u#&kF5SRw@u(26_$>Fn_zcdN`S-MW-Yt)c~(^;p)CC3-vQ=&hr$K zy})i1_^kWfHNk}|k)1Q6^WsI8Qau(ts?S%K1ThsIxd%O?+4RwGa^PsuqYquGTLD^6 zFTGoLQc4gn34rQ~`uw-*xoO#UmFuBgx8H>6e!u7i zfjnGSB`T6_I{iV{ns7$GNWB(cGm@kWluTXa}@*)80j= zgKdwVYw$Xk>CY)vHht4|&u2Oaxq&SS�^g(~e4g-^hY1=tzDsWFBr?&W6>W>6327 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 literal 0 HcmV?d00001 diff --git a/components/ena-binary-export/test/export_tel.sig b/components/ena-binary-export/test/export_tel.sig new file mode 100644 index 0000000000000000000000000000000000000000..658d0e697a795f37ed5c6805622229be5b49e55f GIT binary patch literal 134 zcmV;10D1okg8>RO3KV2zE^=#WE@N+UZ*F0CVRCL^aBvy|b}=FYGBz?Q6EQ9_E;uwW zE-^4LG&L?XE;BAN5CIqgB1bSq0w6J`|8{R+)0Z8^w@$&0nk|{#3|M+DFaQ7m literal 0 HcmV?d00001 diff --git a/components/ena/CMakeLists.txt b/components/ena/CMakeLists.txt new file mode 100644 index 0000000..e15ec0d --- /dev/null +++ b/components/ena/CMakeLists.txt @@ -0,0 +1,14 @@ +idf_component_register( + SRCS + "ena.c" + "ena-beacons.c" + "ena-bluetooth-advertise.c" + "ena-bluetooth-scan.c" + "ena-crypto.c" + "ena-exposure.c" + "ena-storage.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES + spi_flash + mbedtls + bt) \ No newline at end of file diff --git a/components/ena/Kconfig.projbuild b/components/ena/Kconfig.projbuild new file mode 100644 index 0000000..3d8f642 --- /dev/null +++ b/components/ena/Kconfig.projbuild @@ -0,0 +1,94 @@ +menu "Exposure Notification API" + + menu "Storage" + config ENA_STORAGE_DUMP + bool "Dump storage" + default false + help + Dump storage (stored TEKs, temp. beacons and perm. beacons) to serial output after scan. + + config ENA_STORAGE_TEK_MAX + int "Max. TEKs" + default 14 + 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 + help + Defines the maximum number of temporary beacons to be stored. (Default 1000) + + config ENA_STORAGE_START_ADDRESS + int "Storage start address" + default 0 + help + Defines the start address on partition. (Default 0) + + config ENA_STORAGE_PARTITION_NAME + string "Partition name" + default "ena" + help + Name of the partition used for storage. (Default "ena", see partitions.csv) + + config ENA_STORAGE_ERASE + bool "Erase storage (!)" + default false + help + Erases the complete(!) partition on startup and reset counters. + + endmenu + + menu "Scanning" + config ENA_BEACON_TRESHOLD + int "Contact threshold" + default 300 + help + Threshold in seconds after a received beacon is stored permanently. (Default 5 minutes) + + config ENA_BEACON_CLEANUP_TRESHOLD + int "Clean-Up threshold" + default 14 + help + Threshold in days after stored beacons to be removed. + + config ENA_SCANNING_TIME + int "Scanning time" + default 30 + help + Time in seconds how long a scan should run. (Default 30 seconds) + + config ENA_SCANNING_INTERVAL + int "Scanning interval" + default 300 + help + Interval in seconds for the next scan to happen. (Default 5 minutes) + endmenu + + menu "Advertising" + config ENA_BT_ROTATION_TIMEOUT_INTERVAL + int "Rotation timeout interval" + default 900 + help + Base rotation timeout interval in seconds for BT address change & therefore the advertised beacon.(Default 5 minutes) + + config ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL + int "Randomize rotation timeout interval" + default 150 + help + Range in seconds for randomize the rotation timeout interval. (Default +/- ~2.5 minutes) + + config ENA_TEK_ROLLING_PERIOD + int "TEK rolling period" + default 144 + help + Defines the TEK rolling period in 10 minute steps. (Default 144 => 24 hours) + endmenu + +endmenu \ No newline at end of file diff --git a/components/ena/ena-beacons.c b/components/ena/ena-beacons.c new file mode 100644 index 0000000..6f28e58 --- /dev/null +++ b/components/ena/ena-beacons.c @@ -0,0 +1,123 @@ +// 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 "esp_log.h" + +#include "ena-crypto.h" +#include "ena-storage.h" + +#include "ena-beacons.h" + +static uint32_t temp_beacons_count = 0; +static ena_beacon_t temp_beacons[ENA_STORAGE_TEMP_BEACONS_MAX]; + +int ena_get_temp_beacon_index(uint8_t *rpi, uint8_t *aem) +{ + for (int i = 0; i < temp_beacons_count; i++) + { + if (memcmp(temp_beacons[i].rpi, rpi, sizeof(ENA_KEY_LENGTH)) == 0 && + memcmp(temp_beacons[i].aem, aem, sizeof(ENA_AEM_METADATA_LENGTH)) == 0) + { + return i; + } + } + return -1; +} + +void ena_beacons_temp_refresh(uint32_t unix_timestamp) +{ + for (int i = temp_beacons_count - 1; i >= 0; i--) + { + // check for treshold and add permanent beacon + if (temp_beacons[i].timestamp_last - temp_beacons[i].timestamp_first >= ENA_BEACON_TRESHOLD) + { + 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_storage_add_beacon(&temp_beacons[i]); + ena_storage_remove_temp_beacon(i); + } + else + // delete temp beacons older than two times time window (two times to be safe, one times time window enough?!) + if (unix_timestamp - temp_beacons[i].timestamp_last > (ENA_TIME_WINDOW * 2)) + { + ESP_LOGD(ENA_BEACON_LOG, "remove old temporary beacon %u", i); + ena_storage_remove_temp_beacon(i); + } + } + + // update beacons + temp_beacons_count = ena_storage_temp_beacons_count(); + for (int i = 0; i < temp_beacons_count; i++) + { + ena_storage_get_temp_beacon(i, &temp_beacons[i]); + } + +#if (CONFIG_ENA_STORAGE_DUMP) + // DEBUG dump + ena_storage_dump_teks(); + ena_storage_dump_exposure_information(); + ena_storage_dump_temp_beacons(); + ena_storage_dump_beacons(); +#endif +} + +void ena_beacons_cleanup(uint32_t unix_timestamp) +{ + uint32_t count = ena_storage_beacons_count(); + ena_beacon_t beacon; + for (int i = count - 1; i >= 0; i--) + { + ena_storage_get_beacon(i, &beacon); + if (((unix_timestamp - beacon.timestamp_last) / (60 * 60 * 24)) > ENA_BEACON_CLEANUP_TRESHOLD) + { + ena_storage_remove_beacon(i); + } + } +} + +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; + memcpy(temp_beacons[temp_beacons_count].rpi, rpi, ENA_KEY_LENGTH); + memcpy(temp_beacons[temp_beacons_count].aem, aem, ENA_AEM_METADATA_LENGTH); + 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 %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); + + if (beacon_index != temp_beacons_count) + { + ESP_LOGW(ENA_BEACON_LOG, "last temporary beacon index does not match array index!"); + } + temp_beacons_count++; + } + else + { + 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, "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(beacon_index, &temp_beacons[beacon_index]); + } +} \ No newline at end of file diff --git a/components/ena/ena-bluetooth-advertise.c b/components/ena/ena-bluetooth-advertise.c new file mode 100644 index 0000000..def2e1e --- /dev/null +++ b/components/ena/ena-bluetooth-advertise.c @@ -0,0 +1,88 @@ +// 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 "esp_log.h" +#include "esp_bt.h" +#include "esp_gap_ble_api.h" + +#include "ena-crypto.h" + +#include "ena-bluetooth-advertise.h" + +static esp_ble_adv_params_t ena_adv_params = { + .adv_int_min = 0x140, // 200 ms + .adv_int_max = 0x190, // 250 ms + .adv_type = ADV_TYPE_NONCONN_IND, + .own_addr_type = BLE_ADDR_TYPE_RANDOM, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +void ena_bluetooth_advertise_start(void) +{ + ESP_ERROR_CHECK(esp_ble_gap_start_advertising(&ena_adv_params)); +} + +void ena_bluetooth_advertise_set_payload(uint32_t enin, uint8_t *tek) +{ + uint8_t rpik[ENA_KEY_LENGTH] = {0}; + uint8_t rpi[ENA_KEY_LENGTH] = {0}; + uint8_t aemk[ENA_KEY_LENGTH] = {0}; + uint8_t aem[ENA_AEM_METADATA_LENGTH] = {0}; + + ena_crypto_rpik(rpik, tek); + + ena_crypto_rpi(rpi, rpik, enin); + + ena_crypto_aemk(aemk, tek); + + ena_crypto_aem(aem, aemk, rpi, esp_ble_tx_power_get(ESP_BLE_PWR_TYPE_ADV)); + + uint8_t adv_raw_data[31]; + // FLAG??? skipped on sniffed android packages!? + adv_raw_data[0] = 0x02; + adv_raw_data[1] = 0x01; + adv_raw_data[2] = ENA_BLUETOOTH_TAG_DATA; + + // SERVICE UUID + adv_raw_data[3] = 0x03; + adv_raw_data[4] = 0x03; + adv_raw_data[5] = 0x6F; + adv_raw_data[6] = 0xFD; + + // SERVICE DATA + adv_raw_data[7] = 0x17; + adv_raw_data[8] = 0x16; + + adv_raw_data[9] = 0x6F; + adv_raw_data[10] = 0xFD; + + for (int i = 0; i < ENA_KEY_LENGTH; i++) + { + adv_raw_data[i + 11] = rpi[i]; + } + for (int i = 0; i < ENA_AEM_METADATA_LENGTH; i++) + { + adv_raw_data[i + ENA_KEY_LENGTH + 11] = aem[i]; + } + + esp_ble_gap_config_adv_data_raw(adv_raw_data, sizeof(adv_raw_data)); + + ESP_LOGD(ENA_ADVERTISE_LOG, "payload for ENIN %u", enin); + ESP_LOG_BUFFER_HEXDUMP(ENA_ADVERTISE_LOG, adv_raw_data, sizeof(adv_raw_data), ESP_LOG_DEBUG); +} + +void ena_bluetooth_advertise_stop(void) +{ + ESP_ERROR_CHECK(esp_ble_gap_stop_advertising()); +} \ No newline at end of file diff --git a/components/ena/ena-bluetooth-scan.c b/components/ena/ena-bluetooth-scan.c new file mode 100644 index 0000000..c5745a1 --- /dev/null +++ b/components/ena/ena-bluetooth-scan.c @@ -0,0 +1,113 @@ +// 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 "esp_log.h" +#include "esp_gap_ble_api.h" + +#include "ena-crypto.h" +#include "ena-beacons.h" + +#include "ena-bluetooth-scan.h" + +static int scan_status = ENA_SCAN_STATUS_NOT_SCANNING; + +static const uint16_t ENA_SERVICE_UUID = 0xFD6F; + +static esp_ble_scan_params_t ena_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_RANDOM, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, // don't know good parameters, just copied + .scan_window = 0x30, // don't know good parameters, just copied + .scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE, +}; + +void ena_bluetooth_scan_event_callback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + + uint32_t unix_timestamp = (uint32_t)time(NULL); + esp_ble_gap_cb_param_t *p = (esp_ble_gap_cb_param_t *)param; + switch (event) + { + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + ESP_LOGD(ENA_SCAN_LOG, "start scanning..."); + break; + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + ESP_LOGD(ENA_SCAN_LOG, "stopped scanning..."); + ena_beacons_temp_refresh(unix_timestamp); + break; + case ESP_GAP_BLE_SCAN_RESULT_EVT: + if (p->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) + { + uint8_t service_uuid_length = 0; + uint8_t *service_uuid_data = esp_ble_resolve_adv_data(p->scan_rst.ble_adv, 0x03, &service_uuid_length); + // check for ENA Service UUID + if (service_uuid_length == sizeof(ENA_SERVICE_UUID) && memcmp(service_uuid_data, &ENA_SERVICE_UUID, service_uuid_length) == 0) + { + uint8_t service_data_length = 0; + uint8_t *service_data = esp_ble_resolve_adv_data(p->scan_rst.ble_adv, 0x16, &service_data_length); + if (service_data_length != (sizeof(ENA_SERVICE_UUID) + ENA_KEY_LENGTH + ENA_AEM_METADATA_LENGTH)) + { + ESP_LOGW(ENA_SCAN_LOG, "received ENA Service with invalid payload"); + break; + } + + uint8_t *rpi = malloc(ENA_KEY_LENGTH); + memcpy(rpi, &service_data[sizeof(ENA_SERVICE_UUID)], ENA_KEY_LENGTH); + uint8_t *aem = malloc(ENA_AEM_METADATA_LENGTH); + memcpy(aem, &service_data[sizeof(ENA_SERVICE_UUID) + ENA_KEY_LENGTH], ENA_AEM_METADATA_LENGTH); + ena_beacon(unix_timestamp, rpi, aem, p->scan_rst.rssi); + free(rpi); + free(aem); + } + } + else if (p->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) + { + scan_status = ENA_SCAN_STATUS_NOT_SCANNING; + ena_beacons_temp_refresh(unix_timestamp); + ESP_LOGD(ENA_SCAN_LOG, "finished scanning..."); + } + break; + default: + // nothing + break; + } +} + +void ena_bluetooth_scan_init(void) +{ + ESP_ERROR_CHECK(esp_ble_gap_set_scan_params(&ena_scan_params)); + ESP_ERROR_CHECK(esp_ble_gap_register_callback(ena_bluetooth_scan_event_callback)); + // init temporary beacons + ena_beacons_temp_refresh((uint32_t)time(NULL)); +} + +void ena_bluetooth_scan_start(uint32_t duration) +{ + scan_status = ENA_SCAN_STATUS_SCANNING; + ESP_ERROR_CHECK(esp_ble_gap_start_scanning(duration)); +} + +void ena_bluetooth_scan_stop(void) +{ + scan_status = ENA_SCAN_STATUS_WAITING; + ESP_ERROR_CHECK(esp_ble_gap_stop_scanning()); +} + +int ena_bluetooth_scan_get_status(void) +{ + return scan_status; +} diff --git a/components/ena/ena-crypto.c b/components/ena/ena-crypto.c new file mode 100644 index 0000000..0b02ee0 --- /dev/null +++ b/components/ena/ena-crypto.c @@ -0,0 +1,96 @@ +// 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 "mbedtls/md.h" +#include "mbedtls/aes.h" +#include "mbedtls/hkdf.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" + +#include "esp_log.h" + +#include "ena-crypto.h" + +#define ESP_CRYPTO_LOG "ESP-CRYPTO" + +static mbedtls_ctr_drbg_context ctr_drbg; + +void ena_crypto_init(void) +{ + mbedtls_entropy_context entropy; + uint8_t pers[] = "Exposure Notifcation API esp32"; + int ret; + + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); + + if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, pers, sizeof(pers))) != 0) + { + ESP_LOGE(ESP_CRYPTO_LOG, " failed\n ! mbedtls_ctr_drbg_init returned -0x%04x\n", -ret); + } +} + +uint32_t ena_crypto_enin(uint32_t unix_epoch_time) +{ + return unix_epoch_time / ENA_TIME_WINDOW; +} + +void ena_crypto_tek(uint8_t *tek) +{ + int ret; + if ((ret = mbedtls_ctr_drbg_random(&ctr_drbg, tek, ENA_KEY_LENGTH)) != 0) + { + ESP_LOGE(ESP_CRYPTO_LOG, " failed\n ! mbedtls_ctr_drbg_random returned -0x%04x\n", -ret); + } +} + +void ena_crypto_rpik(uint8_t *rpik, uint8_t *tek) +{ + const uint8_t rpik_info[] = "EN-RPIK"; + mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, tek, ENA_KEY_LENGTH, rpik_info, sizeof(rpik_info), rpik, ENA_KEY_LENGTH); +} + +void ena_crypto_rpi(uint8_t *rpi, uint8_t *rpik, uint32_t enin) +{ + uint8_t padded_data[] = "EN-RPI"; + padded_data[12] = (enin & 0x000000ff); + padded_data[13] = (enin & 0x0000ff00) >> 8; + padded_data[14] = (enin & 0x00ff0000) >> 16; + padded_data[15] = (enin & 0xff000000) >> 24; + + mbedtls_aes_context aes; + mbedtls_aes_init(&aes); + mbedtls_aes_setkey_enc(&aes, rpik, ENA_KEY_LENGTH * 8); + mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, padded_data, rpi); + mbedtls_aes_free(&aes); +} + +void ena_crypto_aemk(uint8_t *aemk, uint8_t *tek) +{ + uint8_t aemkInfo[] = "EN-AEMK"; + mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, tek, ENA_KEY_LENGTH, aemkInfo, sizeof(aemkInfo), aemk, ENA_KEY_LENGTH); +} + +void ena_crypto_aem(uint8_t *aem, uint8_t *aemk, uint8_t *rpi, uint8_t power_level) +{ + uint8_t metadata[ENA_AEM_METADATA_LENGTH]; + metadata[0] = 0b01000000; + metadata[1] = power_level; + size_t count = 0; + uint8_t sb[16] = {0}; + mbedtls_aes_context aes; + mbedtls_aes_init(&aes); + mbedtls_aes_setkey_enc(&aes, aemk, ENA_KEY_LENGTH * 8); + mbedtls_aes_crypt_ctr(&aes, ENA_AEM_METADATA_LENGTH, &count, rpi, sb, metadata, aem); + mbedtls_aes_free(&aes); +} \ 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..df5c584 --- /dev/null +++ b/components/ena/ena-exposure.c @@ -0,0 +1,327 @@ +// 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_err.h" +#include "esp_log.h" + +#include "ena-crypto.h" +#include "ena-storage.h" +#include "ena-beacons.h" + +#include "ena-exposure.h" + +static ena_exposure_summary_t *current_summary; + +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 + }, +}; + +int ena_exposure_transmission_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params) +{ + return config->transmission_risk_values[params.report_type]; +} + +int ena_exposure_duration_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params) +{ + // 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; + } + } + + return config->duration_risk_values[duration_level]; +} + +int ena_exposure_days_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params) +{ + // 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; + } + + return config->days_risk_values[days_level]; +} + +int ena_exposure_attenuation_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params) +{ + // 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; + } + + return config->attenuation_risk_values[attenuation_level]; +} + +int ena_exposure_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params) +{ + int score = 1; + score *= ena_exposure_transmission_risk_score(config, params); + + score *= ena_exposure_duration_risk_score(config, params); + + score *= ena_exposure_days_risk_score(config, params); + + score *= ena_exposure_attenuation_risk_score(config, params); + + if (score > 255) + { + score = 255; + } + + return score; +} + +void ena_exposure_summary(ena_exposure_config_t *config) +{ + uint32_t count = ena_storage_exposure_information_count(); + uint32_t current_time = (uint32_t)time(NULL); + + if (current_summary == NULL) + { + current_summary = malloc(sizeof(ena_exposure_summary_t)); + } + + current_summary->last_update = ena_storage_read_last_exposure_date(); + current_summary->days_since_last_exposure = INT_MAX; + current_summary->max_risk_score = 0; + current_summary->risk_score_sum = 0; + current_summary->num_exposures = count; + + if (count == 0) + { + current_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 < current_summary->days_since_last_exposure) + { + current_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 > current_summary->max_risk_score) + { + current_summary->max_risk_score = score; + } + current_summary->risk_score_sum += score; + } +} + +ena_exposure_summary_t *ena_exposure_current_summary(void) +{ + if (current_summary == NULL) + { + ena_exposure_summary(ena_exposure_default_config()); + } + + return current_summary; +} + +ena_exposure_config_t *ena_exposure_default_config(void) +{ + return &DEFAULT_ENA_EXPOSURE_CONFIG; +} + +void ena_exposure_check(ena_beacon_t beacon, ena_temporary_exposure_key_t temporary_exposure_key) +{ + uint32_t timestamp_day_start = temporary_exposure_key.rolling_start_interval_number * ENA_TIME_WINDOW; + uint32_t timestamp_day_end = temporary_exposure_key.rolling_start_interval_number * ENA_TIME_WINDOW + temporary_exposure_key.rolling_period * ENA_TIME_WINDOW; + + if (beacon.timestamp_first > timestamp_day_start && beacon.timestamp_last < timestamp_day_end) + { + ESP_LOGD(ENA_EXPOSURE_LOG, "matched timestamps!"); + + ESP_LOGD(ENA_EXPOSURE_LOG, "Beacon: %u,%u,%d", beacon.timestamp_first, beacon.timestamp_last, beacon.rssi); + ESP_LOG_BUFFER_HEXDUMP(ENA_EXPOSURE_LOG, beacon.rpi, ENA_KEY_LENGTH, ESP_LOG_DEBUG); + ESP_LOG_BUFFER_HEXDUMP(ENA_EXPOSURE_LOG, beacon.aem, ENA_AEM_METADATA_LENGTH, ESP_LOG_DEBUG); + + ESP_LOGD(ENA_EXPOSURE_LOG, "Key: %u,%u,%d", timestamp_day_start, timestamp_day_end, temporary_exposure_key.rolling_period); + ESP_LOG_BUFFER_HEXDUMP(ENA_EXPOSURE_LOG, temporary_exposure_key.key_data, ENA_KEY_LENGTH, ESP_LOG_DEBUG); + bool match = false; + 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 = temporary_exposure_key.report_type; + uint8_t rpi[ENA_KEY_LENGTH]; + uint8_t rpik[ENA_KEY_LENGTH]; + ena_crypto_rpik(rpik, temporary_exposure_key.key_data); + + for (int i = 0; i < temporary_exposure_key.rolling_period; i++) + { + ena_crypto_rpi(rpi, rpik, temporary_exposure_key.rolling_start_interval_number + i); + if (memcmp(beacon.rpi, rpi, sizeof(ENA_KEY_LENGTH)) == 0) + { + match = true; + exposure_info.day = timestamp_day_start; + 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); + } + } +} + +void ena_exposure_check_temporary_exposure_key(ena_temporary_exposure_key_t temporary_exposure_key) +{ + ena_beacon_t beacon; + uint32_t beacons_count = ena_storage_beacons_count(); + for (int y = 0; y < beacons_count; y++) + { + ena_storage_get_beacon(y, &beacon); + ena_exposure_check(beacon, temporary_exposure_key); + } +} \ No newline at end of file diff --git a/components/ena/ena-storage.c b/components/ena/ena-storage.c new file mode 100644 index 0000000..e62a1cc --- /dev/null +++ b/components/ena/ena-storage.c @@ -0,0 +1,504 @@ +// 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_partition.h" + +#include "ena-storage.h" +#include "ena-crypto.h" + +#define BLOCK_SIZE (4096) + +const int ENA_STORAGE_LAST_EXPOSURE_DATE_ADDRESS = (ENA_STORAGE_START_ADDRESS); +const int ENA_STORAGE_TEK_COUNT_ADDRESS = (ENA_STORAGE_LAST_EXPOSURE_DATE_ADDRESS + sizeof(uint32_t)); +const int ENA_STORAGE_TEK_START_ADDRESS = (ENA_STORAGE_TEK_COUNT_ADDRESS + sizeof(uint32_t)); +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_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) +{ + const esp_partition_t *partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, ENA_STORAGE_PARTITION_NAME); + assert(partition); + ESP_ERROR_CHECK(esp_partition_read(partition, address, data, 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); +} + +void ena_storage_write(size_t address, void *data, size_t size) +{ + const int block_num = address / BLOCK_SIZE; + // check for overflow + if (address + size <= (block_num + 1) * BLOCK_SIZE) + { + const esp_partition_t *partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, ENA_STORAGE_PARTITION_NAME); + assert(partition); + const int block_start = block_num * BLOCK_SIZE; + const int block_address = address - block_start; + void *buffer = malloc(BLOCK_SIZE); + if (buffer == NULL) + { + ESP_LOGE(ENA_STORAGE_LOG, "Warning %s malloc low memory", "buffer"); + return; + } + ESP_LOGD(ENA_STORAGE_LOG, "read block %d buffer: start %d size %u", block_num, block_start, BLOCK_SIZE); + ESP_ERROR_CHECK(esp_partition_read(partition, block_start, buffer, BLOCK_SIZE)); + vTaskDelay(1); + ESP_ERROR_CHECK(esp_partition_erase_range(partition, block_start, BLOCK_SIZE)); + + memcpy((buffer + block_address), data, size); + + ESP_ERROR_CHECK(esp_partition_write(partition, block_start, buffer, BLOCK_SIZE)); + free(buffer); + ESP_LOGD(ENA_STORAGE_LOG, "write data at %u", address); + ESP_LOG_BUFFER_HEXDUMP(ENA_STORAGE_LOG, data, size, ESP_LOG_DEBUG); + } + else + { + ESP_LOGD(ENA_STORAGE_LOG, "overflow block at address %u with size %d (block %d)", address, size, block_num); + const size_t block2_address = (block_num + 1) * BLOCK_SIZE; + const size_t data2_size = address + size - block2_address; + const size_t data1_size = size - data2_size; + ESP_LOGD(ENA_STORAGE_LOG, "block1_address %d, block1_size %d (block %d)", address, data1_size, block_num); + ESP_LOGD(ENA_STORAGE_LOG, "block2_address %d, block2_size %d (block %d)", block2_address, data2_size, block_num + 1); + void *data1 = malloc(data1_size); + memcpy(data1, data, data1_size); + ena_storage_write(address, data1, data1_size); + free(data1); + + void *data2 = malloc(data2_size); + memcpy(data2, (data + data1_size), data2_size); + ena_storage_write(block2_address, data2, data2_size); + free(data2); + } +} + +void ena_storage_erase(size_t address, size_t size) +{ + const int block_num = address / BLOCK_SIZE; + // check for overflow + if (address + size <= (block_num + 1) * BLOCK_SIZE) + { + uint8_t *zeros = calloc(size, sizeof(uint8_t)); + ena_storage_write(address, zeros, size); + free(zeros); + } + else + { + const size_t block2_address = (block_num + 1) * BLOCK_SIZE; + const size_t data2_size = address + size - block2_address; + const size_t data1_size = size - data2_size; + uint8_t *zeros = calloc(data1_size, sizeof(uint8_t)); + ena_storage_write(address, zeros, data1_size); + free(zeros); + ena_storage_erase(block2_address, data2_size); + } +} + +void ena_storage_shift_delete(size_t address, size_t end_address, size_t size) +{ + int block_num_start = address / BLOCK_SIZE; + // check for overflow + if (address + size <= (block_num_start + 1) * BLOCK_SIZE) + { + const esp_partition_t *partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, ENA_STORAGE_PARTITION_NAME); + assert(partition); + + int block_num_end = end_address / BLOCK_SIZE; + size_t block_start = address - block_num_start * BLOCK_SIZE; + while (block_num_end >= block_num_start) + { + + void *buffer = malloc(BLOCK_SIZE); + ESP_ERROR_CHECK(esp_partition_read(partition, block_num_start * BLOCK_SIZE, buffer, BLOCK_SIZE)); + vTaskDelay(1); + // shift inside buffer + ESP_LOGD(ENA_STORAGE_LOG, "shift block %d from %u to %u with size %u", block_num_start, (block_start + size), block_start, (BLOCK_SIZE - block_start - size)); + memcpy((buffer + block_start), (buffer + block_start + size), BLOCK_SIZE - block_start - size); + if (block_num_end > block_num_start) + { + void *buffer_next_block = malloc(BLOCK_SIZE); + + ESP_ERROR_CHECK(esp_partition_read(partition, (block_num_start + 1) * BLOCK_SIZE, buffer_next_block, BLOCK_SIZE)); + vTaskDelay(1); + // shift from next block + ESP_LOGD(ENA_STORAGE_LOG, "shift next block size %u", size); + memcpy((buffer + BLOCK_SIZE - size), buffer_next_block, size); + free(buffer_next_block); + } + + ESP_ERROR_CHECK(esp_partition_erase_range(partition, block_num_start * BLOCK_SIZE, BLOCK_SIZE)); + ESP_ERROR_CHECK(esp_partition_write(partition, block_num_start * BLOCK_SIZE, buffer, BLOCK_SIZE)); + free(buffer); + + block_num_start++; + block_start = 0; + } + } + else + { + ESP_LOGD(ENA_STORAGE_LOG, "overflow block at address %u with size %d (block %d)", address, size, block_num_start); + const size_t block1_address = address; + const size_t block2_address = (block_num_start + 1) * BLOCK_SIZE; + const size_t data2_size = address + size - block2_address; + const size_t data1_size = size - data2_size; + ena_storage_shift_delete(block1_address, block2_address, data1_size); + ena_storage_shift_delete(block2_address, end_address - data1_size, data2_size); + } +} + +uint32_t ena_storage_read_last_exposure_date(void) +{ + uint32_t timestamp = 0; + ena_storage_read(ENA_STORAGE_LAST_EXPOSURE_DATE_ADDRESS, ×tamp, sizeof(uint32_t)); + return timestamp; +} + +void ena_storage_write_last_exposure_date(uint32_t timestamp) +{ + ena_storage_write(ENA_STORAGE_LAST_EXPOSURE_DATE_ADDRESS, ×tamp, sizeof(uint32_t)); +} + +uint32_t ena_storage_read_last_tek(ena_tek_t *tek) +{ + uint32_t tek_count = 0; + ena_storage_read(ENA_STORAGE_TEK_COUNT_ADDRESS, &tek_count, sizeof(uint32_t)); + if (tek_count < 1) + { + return 0; + } + uint8_t index = (tek_count % ENA_STORAGE_TEK_MAX) - 1; + ena_storage_read(ENA_STORAGE_TEK_START_ADDRESS + index * sizeof(ena_tek_t), tek, sizeof(ena_tek_t)); + + 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); + return tek_count; +} + +void ena_storage_write_tek(ena_tek_t *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); + ena_storage_write(ENA_STORAGE_TEK_START_ADDRESS + index * sizeof(ena_tek_t), tek, sizeof(ena_tek_t)); + + tek_count++; + ena_storage_write(ENA_STORAGE_TEK_COUNT_ADDRESS, &tek_count, sizeof(uint32_t)); + + 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); +} + +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) +{ + uint32_t count = 0; + ena_storage_read(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); + ESP_LOGD(ENA_STORAGE_LOG, "read temp beacons count: %u", count); + return count; +} + +void ena_storage_get_temp_beacon(uint32_t index, ena_beacon_t *beacon) +{ + 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); +} + +uint32_t ena_storage_add_temp_beacon(ena_beacon_t *beacon) +{ + uint32_t count = ena_storage_temp_beacons_count(); + // overwrite older temporary beacons?! + uint8_t index = count % ENA_STORAGE_TEMP_BEACONS_MAX; + ena_storage_set_temp_beacon(index, beacon); + ESP_LOGD(ENA_STORAGE_LOG, "add 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); + count++; + ena_storage_write(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); + return count - 1; +} + +void ena_storage_set_temp_beacon(uint32_t index, ena_beacon_t *beacon) +{ + 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); +} + +void ena_storage_remove_temp_beacon(uint32_t index) +{ + uint32_t count = ena_storage_temp_beacons_count(); + 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_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); +} + +uint32_t ena_storage_beacons_count(void) +{ + 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); + return count; +} + +void ena_storage_get_beacon(uint32_t index, ena_beacon_t *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: 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); +} + +void ena_storage_add_beacon(ena_beacon_t *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: 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); +} + +void ena_storage_remove_beacon(uint32_t index) +{ + uint32_t count = ena_storage_beacons_count(); + size_t address_from = ENA_STORAGE_BEACONS_START_ADDRESS + index * sizeof(ena_beacon_t); + size_t address_to = ENA_STORAGE_BEACONS_START_ADDRESS + count * sizeof(ena_beacon_t); + + ena_storage_shift_delete(address_from, address_to, sizeof(ena_beacon_t)); + + count--; + ena_storage_write(ENA_STORAGE_BEACONS_COUNT_ADDRESS, &count, sizeof(uint32_t)); + ESP_LOGD(ENA_STORAGE_LOG, "remove beacon: %u", index); +} + +void ena_storage_erase_all(void) +{ + const esp_partition_t *partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, ENA_STORAGE_PARTITION_NAME); + assert(partition); + ESP_ERROR_CHECK(esp_partition_erase_range(partition, 0, partition->size)); + ESP_LOGI(ENA_STORAGE_LOG, "erased partition %s!", ENA_STORAGE_PARTITION_NAME); + + uint32_t count = 0; + ena_storage_write(ENA_STORAGE_LAST_EXPOSURE_DATE_ADDRESS, &count, sizeof(uint32_t)); + 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)); +} + +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; + + if (count < ENA_STORAGE_TEK_MAX) + { + stored = count; + } + + size_t size = sizeof(uint32_t) + stored * sizeof(ena_tek_t); + ena_storage_erase(ENA_STORAGE_TEK_COUNT_ADDRESS, size); + ESP_LOGI(ENA_STORAGE_LOG, "erased %d teks (size %u at %u)", stored, size, ENA_STORAGE_TEK_COUNT_ADDRESS); +} + +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); + ena_storage_erase(ENA_STORAGE_EXPOSURE_INFORMATION_COUNT_ADDRESS, size); + 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) +{ + 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; + + if (beacon_count < ENA_STORAGE_TEMP_BEACONS_MAX) + { + stored = beacon_count; + } + + size_t size = sizeof(uint32_t) + stored * sizeof(ena_beacon_t); + ena_storage_erase(ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS, size); + + ESP_LOGI(ENA_STORAGE_LOG, "erased %d temporary beacons (size %u at %u)", stored, size, ENA_STORAGE_TEMP_BEACONS_COUNT_ADDRESS); +} + +void ena_storage_erase_beacon(void) +{ + uint32_t beacon_count = 0; + ena_storage_read(ENA_STORAGE_BEACONS_COUNT_ADDRESS, &beacon_count, sizeof(uint32_t)); + + size_t size = sizeof(uint32_t) + beacon_count * sizeof(ena_beacon_t); + ena_storage_erase(ENA_STORAGE_BEACONS_COUNT_ADDRESS, size); + ESP_LOGI(ENA_STORAGE_LOG, "erased %d beacons (size %u at %u)", beacon_count, size, ENA_STORAGE_BEACONS_COUNT_ADDRESS); +} + +void ena_storage_dump_hash_array(uint8_t *data, size_t size) +{ + for (int i = 0; i < size; i++) + { + if (i == 0) + { + printf("%02x", data[i]); + } + else + { + printf(" %02x", data[i]); + } + } +} + +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)); + uint32_t stored = ENA_STORAGE_TEK_MAX; + + if (tek_count < ENA_STORAGE_TEK_MAX) + { + stored = tek_count; + } + + ESP_LOGD(ENA_STORAGE_LOG, "%u TEKs (%u stored)\n", tek_count, stored); + printf("#,enin,tek,rolling_period\n"); + for (int i = 0; i < stored; i++) + { + + size_t address = ENA_STORAGE_TEK_START_ADDRESS + i * sizeof(ena_tek_t); + ena_storage_read(address, &tek, sizeof(ena_tek_t)); + printf("%d,%u,", i, tek.enin); + ena_storage_dump_hash_array(tek.key_data, ENA_KEY_LENGTH); + printf(",%u\n", tek.rolling_period); + } +} + +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_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; + + if (beacon_count < ENA_STORAGE_TEMP_BEACONS_MAX) + { + stored = beacon_count; + } + ESP_LOGD(ENA_STORAGE_LOG, "%u temporary beacons (%u stored)\n", beacon_count, stored); + printf("#,timestamp_first,timestamp_last,rpi,aem,rssi\n"); + for (int i = 0; i < stored; i++) + { + ena_storage_get_temp_beacon(i, &beacon); + 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); + printf(",%d\n", beacon.rssi); + } +} + +void ena_storage_dump_beacons(void) +{ + + ena_beacon_t beacon; + 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_first,timestamp_last,rpi,aem,rssi\n"); + for (int i = 0; i < beacon_count; i++) + { + ena_storage_get_beacon(i, &beacon); + 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); + printf(",%d\n", beacon.rssi); + } +} \ No newline at end of file diff --git a/components/ena/ena.c b/components/ena/ena.c new file mode 100644 index 0000000..c0806dc --- /dev/null +++ b/components/ena/ena.c @@ -0,0 +1,185 @@ +// 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 "esp_system.h" +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_gap_ble_api.h" + +#include "nvs_flash.h" + +#include "ena-crypto.h" +#include "ena-storage.h" +#include "ena-bluetooth-scan.h" +#include "ena-bluetooth-advertise.h" +#include "ena-beacons.h" + +#include "ena.h" + +static ena_tek_t last_tek; // last ENIN +static uint32_t next_rpi_timestamp; // next rpi + +void ena_next_rpi_timestamp(uint32_t timestamp) +{ + int random_interval = esp_random() % (2 * ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL); + if (random_interval > ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL) + { + random_interval = ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL - random_interval; + } + next_rpi_timestamp = timestamp + ENA_BT_ROTATION_TIMEOUT_INTERVAL + random_interval; + ESP_LOGD(ENA_LOG, "next rpi at %u (%u from %u)", next_rpi_timestamp, (ENA_BT_ROTATION_TIMEOUT_INTERVAL + random_interval), timestamp); +} + +void ena_run(void) +{ + static uint32_t unix_timestamp = 0; + static uint32_t current_enin = 0; + unix_timestamp = (uint32_t)time(NULL); + current_enin = ena_crypto_enin(unix_timestamp); + if (current_enin - last_tek.enin >= last_tek.rolling_period) + { + ena_crypto_tek(last_tek.key_data); + last_tek.enin = current_enin; + // validity only to next day 00:00 + last_tek.rolling_period = ENA_TEK_ROLLING_PERIOD - (last_tek.enin % ENA_TEK_ROLLING_PERIOD); + ena_storage_write_tek(&last_tek); + // clean up old beacons + ena_beacons_cleanup(unix_timestamp); + } + + // change RPI + if (unix_timestamp >= next_rpi_timestamp) + { + if (ena_bluetooth_scan_get_status() == ENA_SCAN_STATUS_SCANNING) + { + ena_bluetooth_scan_stop(); + } + ena_bluetooth_advertise_stop(); + ena_bluetooth_advertise_set_payload(current_enin, last_tek.key_data); + ena_bluetooth_advertise_start(); + if (ena_bluetooth_scan_get_status() == ENA_SCAN_STATUS_WAITING) + { + ena_bluetooth_scan_start(ENA_SCANNING_TIME); + } + ena_next_rpi_timestamp(unix_timestamp); + } + + // scan + if (unix_timestamp % ENA_SCANNING_INTERVAL == 0 && ena_bluetooth_scan_get_status() == ENA_SCAN_STATUS_NOT_SCANNING) + { + ena_bluetooth_scan_start(ENA_SCANNING_TIME); + } +} + +void ena_start(void) +{ +#if (CONFIG_ENA_STORAGE_ERASE) + ena_storage_erase_all(); +#endif + + if (ena_storage_read_last_exposure_date() == 0xFFFFFFFF) + { + ena_storage_erase_all(); + } + + // init NVS for BLE + esp_err_t ret; + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + ESP_ERROR_CHECK(nvs_flash_erase()); + ESP_ERROR_CHECK(nvs_flash_init()); + } + + // init BLE + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) + { + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg)); + while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) + { + } + } + + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) + { + ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE)); + } + + if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) + { + ESP_ERROR_CHECK(esp_bluedroid_init()); + } + if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_INITIALIZED) + { + ESP_ERROR_CHECK(esp_bluedroid_enable()); + } + + // new bluetooth address nesseccary? + uint8_t bt_address[ESP_BD_ADDR_LEN]; + esp_fill_random(bt_address, ESP_BD_ADDR_LEN); + bt_address[0] |= 0xC0; + + ESP_ERROR_CHECK(esp_ble_gap_set_rand_addr(bt_address)); + + ESP_ERROR_CHECK(esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9)); + ESP_ERROR_CHECK(esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9)); + ESP_ERROR_CHECK(esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN, ESP_PWR_LVL_P9)); + ESP_ERROR_CHECK(esp_ble_gap_config_local_privacy(true)); + + // init ENA + 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); + + ena_next_rpi_timestamp(unix_timestamp); + + // read last TEK or create new + if (tek_count == 0 || (current_enin - last_tek.enin) >= last_tek.rolling_period) + { + ena_crypto_tek(last_tek.key_data); + last_tek.enin = ena_crypto_enin(unix_timestamp); + // validity only to next day 00:00 + last_tek.rolling_period = ENA_TEK_ROLLING_PERIOD - (last_tek.enin % ENA_TEK_ROLLING_PERIOD); + ena_storage_write_tek(&last_tek); + } + + // init scan + ena_bluetooth_scan_init(); + + // init and start advertising + ena_bluetooth_advertise_set_payload(current_enin, last_tek.key_data); + ena_bluetooth_advertise_start(); + // initial scan on every start + ena_bluetooth_scan_start(ENA_SCANNING_TIME); + + // what is a good stack size here? + // xTaskCreate(&ena_run, "ena_run", ENA_RAM, NULL, 5, NULL); +} + +void ena_stop(void) +{ + ena_bluetooth_advertise_stop(); + ena_bluetooth_scan_stop(); + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); +} \ No newline at end of file diff --git a/components/ena/include/ena-beacons.h b/components/ena/include/ena-beacons.h new file mode 100644 index 0000000..d0449cd --- /dev/null +++ b/components/ena/include/ena-beacons.h @@ -0,0 +1,62 @@ +// 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. +/** + * @file + * + * @brief handles scanned data by storing temporary beacons, check for threshold and store beacons permanently + * + */ +#ifndef _ena_BEACON_H_ +#define _ena_BEACON_H_ + +#define ENA_BEACON_LOG "ESP-ENA-beacon" // TAG for Logging +#define ENA_BEACON_TRESHOLD (CONFIG_ENA_BEACON_TRESHOLD) // meet for longer than 5 minutes +#define ENA_BEACON_CLEANUP_TRESHOLD (CONFIG_ENA_BEACON_CLEANUP_TRESHOLD) // threshold (in days) for stored beacons to be removed + +/** + * @brief check temporary beacon for threshold or expiring + * + * This function checks all current temporary beacons if the contact threshold is + * reached or if the temporary contact can be discarded. + * + * @param[in] unix_timestamp current time as UNIX timestamp to compare + * + */ +void ena_beacons_temp_refresh(uint32_t unix_timestamp); + +/** + * @brief check stored beacons to expire + * + * This function checks for all stored beacons if the last timestamp is over a threshold to remove the beacon. + * + * @param[in] unix_timestamp current time as UNIX timestamp to compate + * + */ +void ena_beacons_cleanup(uint32_t unix_timestamp); + +/** + * @brief handle new beacon received from a BLE scan + * + * This function gets called when a running BLE scan received a new ENA payload. + * On already detected RPI this will update just the timestamp and RSSI. + * + * @param[in] unix_timestamp UNIX timestamp when beacon was made + * @param[in] rpi received RPI from scanned payload + * @param[in] aem received AEM from scanned payload + * @param[in] rssi measured RSSI on scan + * + */ +void ena_beacon(uint32_t unix_timestamp, uint8_t *rpi, uint8_t *aem, int rssi); + +#endif \ No newline at end of file diff --git a/components/ena/include/ena-bluetooth-advertise.h b/components/ena/include/ena-bluetooth-advertise.h new file mode 100644 index 0000000..d3f03ab --- /dev/null +++ b/components/ena/include/ena-bluetooth-advertise.h @@ -0,0 +1,52 @@ +// 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. +/** + * @file + * + * @brief BLE advertising to send own beacons + * + */ +#ifndef _ena_BLUETOOTH_ADVERTISE_H_ +#define _ena_BLUETOOTH_ADVERTISE_H_ + +#define ENA_ADVERTISE_LOG "ESP-ENA-advertise" // TAG for Logging +#define ENA_BLUETOOTH_TAG_DATA (0x1A) // Data for BLE payload TAG + +/** + * @brief Start BLE advertising + */ +void ena_bluetooth_advertise_start(void); + +/** + * @brief Set payload for BLE advertising + * + * This will set the payload for based on given ENIN and TEK. + * + * Source documents (Section: Advertising Payload) + * + * https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf + * + * https://covid19-static.cdn-apple.com/applications/covid19/current/static/detection-tracing/pdf/ExposureNotification-BluetoothSpecificationv1.2.pdf + * + * @param[in] enin ENIN defining the start of the tek vadility. This should be the ENIN for the current timestamp + * @param[in] tek pointer to the TEK used to encrypt the payload. + */ +void ena_bluetooth_advertise_set_payload(uint32_t enin, uint8_t *tek); + +/** + * @brief Stop BLE advertising + */ +void ena_bluetooth_advertise_stop(void); + +#endif \ No newline at end of file diff --git a/components/ena/include/ena-bluetooth-scan.h b/components/ena/include/ena-bluetooth-scan.h new file mode 100644 index 0000000..d1ca3fa --- /dev/null +++ b/components/ena/include/ena-bluetooth-scan.h @@ -0,0 +1,69 @@ +// 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. +/** + * @file + * + * @brief BLE scans for detecting other beacons + * + */ +#ifndef _ena_BLUETOOTH_SCAN_H_ +#define _ena_BLUETOOTH_SCAN_H_ + +#define ENA_SCAN_LOG "ESP-ENA-scan" // TAG for Logging +#define ENA_SCANNING_TIME (CONFIG_ENA_SCANNING_TIME) // time how long a scan should run +#define ENA_SCANNING_INTERVAL (CONFIG_ENA_SCANNING_INTERVAL) // interval for next scan to happen + +/** + * @brief status of BLE scan + */ +typedef enum +{ + ENA_SCAN_STATUS_SCANNING = 0, // scan is running + ENA_SCAN_STATUS_NOT_SCANNING, // scan is not running + ENA_SCAN_STATUS_WAITING, // scan is not running but stopped manually +} ena_bluetooth_scan_status; + +/** + * @brief initialize the BLE scanning + * + */ +void ena_bluetooth_scan_init(void); + +/** + * @brief start BLE scanning for a given duration + * + * Source documents (Section: Scanning Behavior) + * + * https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf + * + * https://covid19-static.cdn-apple.com/applications/covid19/current/static/detection-tracing/pdf/ExposureNotification-BluetoothSpecificationv1.2.pdf + * + * @param[in] duration duration of the scan in seconds + */ +void ena_bluetooth_scan_start(uint32_t duration); + +/** + * @brief stop a running BLE scanning + */ +void ena_bluetooth_scan_stop(void); + +/** + * @brief return the current scanning status + * + * @return + * current scan status + */ +int ena_bluetooth_scan_get_status(void); + +#endif \ No newline at end of file diff --git a/components/ena/include/ena-crypto.h b/components/ena/include/ena-crypto.h new file mode 100644 index 0000000..11e8dd0 --- /dev/null +++ b/components/ena/include/ena-crypto.h @@ -0,0 +1,126 @@ +// 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. +/** + * @file + * + * @brief covers cryptography part (key creation, encryption etc.) + * + */ +#ifndef _ena_CRYPTO_H_ +#define _ena_CRYPTO_H_ + +#define ENA_TIME_WINDOW (600) // time window every 10 minutes +#define ENA_KEY_LENGTH (16) // key length +#define ENA_AEM_METADATA_LENGTH (4) // size of metadata +#define ENA_TEK_ROLLING_PERIOD (CONFIG_ENA_TEK_ROLLING_PERIOD) // TEKRollingPeriod + +#include + +/** + * @brief initialize cryptography + * + * This initialize the cryptography by setting up entropy. + */ +void ena_crypto_init(void); + +/** + * @brief calculate ENIntervalNumber (ENIN) for given UNIX timestamp + * + * Source documents (Section: ENIntervalNumber) + * + * https://blog.google/documents/69/Exposure_Notification_-_Cryptography_Specification_v1.2.1.pdf + * + * https://covid19-static.cdn-apple.com/applications/covid19/current/static/detection-tracing/pdf/ExposureNotification-CryptographySpecificationv1.2.pdf + * + * + * @param[in] unix_timestamp UNIX Timestamp to calculate ENIN for + * + * @return + * ENIN for given timestamp + */ +uint32_t ena_crypto_enin(uint32_t unix_timestamp); + +/** + * @brief calculate a new random Temporary Exposure Key (TEK) + * + * Source documents (Section: Temporary Exposure Key) + * + * https://blog.google/documents/69/Exposure_Notification_-_Cryptography_Specification_v1.2.1.pdf + * + * https://covid19-static.cdn-apple.com/applications/covid19/current/static/detection-tracing/pdf/ExposureNotification-CryptographySpecificationv1.2.pdf + * + * @param[out] tek pointer to the new TEK + */ +void ena_crypto_tek(uint8_t *tek); + +/** + * @brief calculate a new Rolling Proximity Identifier Key (RPIK) with given TEK + * + * Source documents (Section: Rolling Proximity Identifier Key) + * + * https://blog.google/documents/69/Exposure_Notification_-_Cryptography_Specification_v1.2.1.pdf + * + * https://covid19-static.cdn-apple.com/applications/covid19/current/static/detection-tracing/pdf/ExposureNotification-CryptographySpecificationv1.2.pdf + * + * @param[out] rpik pointer to the new RPIK + * @param[in] tek TEK for calculating RPIK + */ +void ena_crypto_rpik(uint8_t *rpik, uint8_t *tek); + +/** + * @brief calculate a new Rolling Proximity Identifier with given RPIK and ENIN + * + * Source documents (Section: Rolling Proximity Identifier) + * + * https://blog.google/documents/69/Exposure_Notification_-_Cryptography_Specification_v1.2.1.pdf + * + * https://covid19-static.cdn-apple.com/applications/covid19/current/static/detection-tracing/pdf/ExposureNotification-CryptographySpecificationv1.2.pdf + * + * @param[out] rpi pointer to the new RPI + * @param[in] rpik RPIK for encrypting RPI + * @param[in] enin ENIN to encrypt in RPI + */ +void ena_crypto_rpi(uint8_t *rpi, uint8_t *rpik, uint32_t enin); + +/** + * @brief calculate a new Associated Encrypted Metadata Key (AEMK) with given TEK + * + * Source documents (Section: Associated Encrypted Metadata Key) + * + * https://blog.google/documents/69/Exposure_Notification_-_Cryptography_Specification_v1.2.1.pdf + * + * https://covid19-static.cdn-apple.com/applications/covid19/current/static/detection-tracing/pdf/ExposureNotification-CryptographySpecificationv1.2.pdf + * + * @param[out] aemk pointer to the new AEMK + * @param[in] tek TEK for calculating AEMK + */ +void ena_crypto_aemk(uint8_t *aemk, uint8_t *tek); + +/** + * @brief create Associated Encrypted Metadata (AEM) with given AEMK along the RPI + * + * Source documents (Section: Associated Encrypted Metadata) + * + * https://blog.google/documents/69/Exposure_Notification_-_Cryptography_Specification_v1.2.1.pdf + * + * https://covid19-static.cdn-apple.com/applications/covid19/current/static/detection-tracing/pdf/ExposureNotification-CryptographySpecificationv1.2.pdf + * + * @param[out] aem pointer to the new AEM + * @param[in] aemk AEMK for encrypting AEM + * @param[in] rpi RPI for encrypting AEM + * @param[in] power_level BLE power level to encrypt in AEM + */ +void ena_crypto_aem(uint8_t *aem, uint8_t *aemk, uint8_t *rpi, uint8_t power_level); + +#endif \ No newline at end of file diff --git a/components/ena/include/ena-exposure.h b/components/ena/include/ena-exposure.h new file mode 100644 index 0000000..297e338 --- /dev/null +++ b/components/ena/include/ena-exposure.h @@ -0,0 +1,249 @@ +// 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. +/** + * @file + * + * @brief compare temporary exposure keys with stored beacons, calculate score and risk + * + */ +#ifndef _ena_EXPOSURE_H_ +#define _ena_EXPOSURE_H_ + +#include +#include "esp_err.h" +#include "ena-storage.h" +#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__)) +{ + uint32_t last_update; // timestamp of last update of exposure data + 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 temporary exposure key + * + * The temporary exposure key is used to check for exposure. + */ +typedef struct __attribute__((__packed__)) +{ + uint8_t key_data[ENA_KEY_LENGTH]; + uint8_t transmission_risk_level; + uint32_t rolling_start_interval_number; + uint32_t rolling_period; + ena_report_type_t report_type; + uint32_t days_since_onset_of_symptoms; +} ena_temporary_exposure_key_t; + +/** + * @brief calculate transmission risk score + * + * @param[in] config the exposure configuration used for calculating score + * @param[in] params the exposure parameter to calculate with + * + * @return + */ +int ena_exposure_transmission_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params); + +/** + * @brief calculate duration risk score + * + * @param[in] config the exposure configuration used for calculating score + * @param[in] params the exposure parameter to calculate with + * + * @return + */ +int ena_exposure_duration_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params); + +/** + * @brief calculate days risk score + * + * @param[in] config the exposure configuration used for calculating score + * @param[in] params the exposure parameter to calculate with + * + * @return + */ +int ena_exposure_days_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params); + +/** + * @brief calculate attenuation risk score + * + * @param[in] config the exposure configuration used for calculating score + * @param[in] params the exposure parameter to calculate with + * + * @return + */ +int ena_exposure_attenuation_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params); + +/** + * @brief calculate overall risk score + * + * @param[in] config the exposure configuration used for calculating score + * @param[in] params the exposure parameter to calculate with + * + * @return + */ +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 + */ +void ena_exposure_summary(ena_exposure_config_t *config); + +/** + * @brief return the current exposure summary + * + * @return + * ena_exposure_summary_t pointer to the current exposure summary + */ +ena_exposure_summary_t *ena_exposure_current_summary(void); + +/** + * @brief return a default exposure configuration + * + * @return + * ena_exposure_config_t default exposure configuration + */ +ena_exposure_config_t *ena_exposure_default_config(void); + +/** + * @brief reads Temporary Exposue Key check for exposures with certain beacon + * + * @param[in] ena_beacon_t the beacon to check against + * @param[in] temporary_exposure_key the temporary exposure keys to check + */ +void ena_exposure_check(ena_beacon_t beacon, ena_temporary_exposure_key_t temporary_exposure_key); + +/** + * @brief reads Temporary Exposue Key and check for exposures with all beacons + * + * @param[in] temporary_exposure_key the temporary exposure keys to check + */ +void ena_exposure_check_temporary_exposure_key(ena_temporary_exposure_key_t temporary_exposure_key); + +#endif \ No newline at end of file diff --git a/components/ena/include/ena-storage.h b/components/ena/include/ena-storage.h new file mode 100644 index 0000000..f5f98e1 --- /dev/null +++ b/components/ena/include/ena-storage.h @@ -0,0 +1,302 @@ +// 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. +/** + * @file + * + * @brief storage part to store own TEKs and beacons + * + */ +#ifndef _ena_STORAGE_H_ +#define _ena_STORAGE_H_ + +#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 // 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 + */ +typedef struct __attribute__((__packed__)) +{ + uint8_t key_data[ENA_KEY_LENGTH]; // key data for encryption + uint32_t enin; // ENIN marking start of validity + uint8_t rolling_period; // period after validity start to mark key as expired +} ena_tek_t; + +/** + * @brief sturcture for storing a beacons + */ +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_first; // timestamp of first recognition + uint32_t timestamp_last; // timestamp of last recognition + int rssi; // average measured RSSI +} ena_beacon_t; + +/** + * @brief structure for storing a Exposure Information (combined ExposureInformation, ExposureWindow and ScanInstance from Google API >= 1.5) + */ +typedef struct __attribute__((__packed__)) +{ + 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 + * + * @param[in] address the address to read bytes from + * @param[out] data pointer to write the read data + * @param[in] size how many bytes to read + */ +void ena_storage_read(size_t address, void *data, size_t size); + +/** + * @brief store bytes at given address + * + * @param[in] address the address to write bytes to + * @param[in] data pointer to the data to write + * @param[in] size how many bytes to write + */ +void ena_storage_write(size_t address, void *data, size_t size); + +/** + * @brief erase storage at given address + * + * @param[in] address the address to erase from + * @param[in] size how many bytes to erase + */ +void ena_storage_erase(size_t address, size_t size); + +/** + * @brief deletes bytes at given address and shift other data back + * + * @param[in] address the address to delete from + * @param[in] end_address the address to mark end of shift + * @param[in] size how many bytes to delete + */ +void ena_storage_shift_delete(size_t address, size_t end_address, size_t size); + +/** + * @brief get timestamp of most recent exposure data + * + * @return + * unix timestamp + */ +uint32_t ena_storage_read_last_exposure_date(void); + +/** + * @brief set timestamp of most recent exposure data + * + * @param[in] timestamp unix timestamp + */ +void ena_storage_write_last_exposure_date(uint32_t timestamp); + +/** + * @brief get last stored TEK + * + * @param[out] tek pointer to write last TEK to + * + * @return + * total number of TEKs stored + */ +uint32_t ena_storage_read_last_tek(ena_tek_t *tek); + +/** + * @brief store given TEK + * + * This will store the given TEK as new TEK. + * + * @param[in] tek the tek to store + */ +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 + * + * @return + * total number of temporary beacons stored + */ +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 beacon to write to + */ +void ena_storage_get_temp_beacon(uint32_t index, ena_beacon_t *beacon); + +/** + * @brief store temporary beacon + * + * @param[in] beacon new temporary beacon to store + * + * @return + * index of new stored beacon + */ +uint32_t ena_storage_add_temp_beacon(ena_beacon_t *beacon); + +/** + * @brief store temporary beacon at given index + * + * @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_beacon_t *beacon); + +/** + * @brief remove temporary beacon at given index + * + * @param[in] index the index of the temporary beacon to remove + */ +void ena_storage_remove_temp_beacon(uint32_t index); + +/** + * @brief get number of permanently stored beacons + * + * @return + * total number of beacons stored + */ +uint32_t ena_storage_beacons_count(void); + +/** + * @brief get permanently stored beacon at given index + * + * @param[in] index the index of the beacon to read + * @param[out] beacon pointer to to write to + */ +void ena_storage_get_beacon(uint32_t index, ena_beacon_t *beacon); + +/** + * @brief permanently store beacon + * + * @param[in] beacon new beacon to permanently store + */ +void ena_storage_add_beacon(ena_beacon_t *beacon); + +/** + * @brief remove beacon at given index + * + * @param[in] index the index of the beacon to remove + */ +void ena_storage_remove_beacon(uint32_t index); + +/** + * @brief erase the storage + * + * This function completely deletes all stored data and resets the counters + * of TEKs, temporary beacon and beacon to zero. + */ +void ena_storage_erase_all(void); + +/** + * @brief erase all stored TEKs + * + * This function deletes all stored TEKs and resets counter to zero. + */ +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 + * + * This function deletes all stored temporary beacons and resets counter to zero. + */ +void ena_storage_erase_temporary_beacon(void); + +/** + * @brief erase all permanently stored beacons + * + * This function deletes all stored beacons and resets counter to zero. + */ +void ena_storage_erase_beacon(void); + +/** + * @brief helper to dump a byte array as hash + */ +void ena_storage_dump_hash_array(uint8_t *data, size_t size); + +/** + * @brief dump all stored TEKs to serial output + * + * This function prints all stored TEKs to serial output in + * the following CSV format: #,enin,tek + */ +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 + * + * This function prints all stored temporary beacons to serial output in + * the following CSV format: #,timestamp_first,timestamp_last,rpi,aem,rssi + */ +void ena_storage_dump_temp_beacons(void); + +/** + * @brief dump all stored beacons to serial output + * + * This function prints all stored beacons to serial output in + * the following CSV format: #,timestamp,rpi,aem,rssi + */ +void ena_storage_dump_beacons(void); + +#endif \ No newline at end of file diff --git a/components/ena/include/ena.h b/components/ena/include/ena.h new file mode 100644 index 0000000..bcc2f85 --- /dev/null +++ b/components/ena/include/ena.h @@ -0,0 +1,49 @@ +// 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. +/** + * @file + * + * @brief run all other ena parts together to time scanning, advertising and exposure checks + * + */ +#ifndef _ena_H_ +#define _ena_H_ + +#define ENA_LOG "ESP-ENA" // TAG for Logging +#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 + +/** + * @brief Run Exposure Notification API + * + * This runs the complete BLE logic + * + */ +void ena_run(void); + +/** + * @brief Start Exposure Notification API + * + * This initializes the complete stack of ESP_ENA. It will initialize BLE module and + * starting a task for managing advertising and scanning processes. + * + */ +void ena_start(void); + +/** + * @brief stop ena + */ +void ena_stop(void); + +#endif \ No newline at end of file diff --git a/components/interface/interface-data.c b/components/interface/interface-data.c index e4c4ede..aa7bf0d 100644 --- a/components/interface/interface-data.c +++ b/components/interface/interface-data.c @@ -78,7 +78,7 @@ void interface_data_rst(void) ena_storage_write_last_exposure_date(0); break; case INTERFACE_DATA_DEL_ALL: - ena_storage_erase(); + ena_storage_erase_all(); break; } diff --git a/components/interface/interface-wifi.c b/components/interface/interface-wifi.c index 2a16a7a..40af8a2 100644 --- a/components/interface/interface-wifi.c +++ b/components/interface/interface-wifi.c @@ -75,6 +75,7 @@ void interface_wifi_mid(void) memset(¤t_wifi_config, 0, sizeof(wifi_config_t)); memcpy(current_wifi_config.sta.ssid, ap_info[ap_selected].ssid, strlen((char *)ap_info[ap_selected].ssid)); interface_input(&interface_wifi_input_rst, &interface_wifi_input_set, 64); + interface_input_set_text("muffimuffi"); } void interface_wifi_up(void) { 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/LICENSE.txt b/components/nanopb/LICENSE.txt new file mode 100644 index 0000000..d11c9af --- /dev/null +++ b/components/nanopb/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2011 Petteri Aimonen + +This software is provided 'as-is', without any express or +implied warranty. In no event will the authors be held liable +for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. 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