From 3ba8eed60aa2997a2894c27b5a6e53186101369e Mon Sep 17 00:00:00 2001 From: Amey Inamdar Date: Fri, 20 Jul 2018 18:05:31 +0530 Subject: [PATCH 1/7] protobuf-c : Added Google's protocol buffer C library as sub-module Source https://github.com/protobuf-c/protobuf-c Co-Authored-By: Amey Inamdar Co-Authored-By: Anurag Kar --- .gitmodules | 4 ++++ components/protobuf-c/CMakeLists.txt | 4 ++++ components/protobuf-c/component.mk | 8 ++++++++ components/protobuf-c/protobuf-c | 1 + tools/ci/mirror-list.txt | 1 + 5 files changed, 18 insertions(+) create mode 100644 components/protobuf-c/CMakeLists.txt create mode 100644 components/protobuf-c/component.mk create mode 160000 components/protobuf-c/protobuf-c diff --git a/.gitmodules b/.gitmodules index 164d2ce98..12672aae1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -57,3 +57,7 @@ [submodule "components/mqtt/esp-mqtt"] path = components/mqtt/esp-mqtt url = https://github.com/espressif/esp-mqtt.git + +[submodule "components/protobuf-c/protobuf-c"] + path = components/protobuf-c/protobuf-c + url = https://github.com/protobuf-c/protobuf-c diff --git a/components/protobuf-c/CMakeLists.txt b/components/protobuf-c/CMakeLists.txt new file mode 100644 index 000000000..1c9a3ffc3 --- /dev/null +++ b/components/protobuf-c/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_ADD_INCLUDEDIRS protobuf-c) +set(COMPONENT_SRCS "protobuf-c/protobuf-c/protobuf-c.c") + +register_component() diff --git a/components/protobuf-c/component.mk b/components/protobuf-c/component.mk new file mode 100644 index 000000000..a8c4900ab --- /dev/null +++ b/components/protobuf-c/component.mk @@ -0,0 +1,8 @@ +# +# Component Makefile +# +COMPONENT_ADD_INCLUDEDIRS := protobuf-c + +COMPONENT_SRCDIRS := protobuf-c/protobuf-c + +COMPONENT_SUBMODULES += protobuf-c diff --git a/components/protobuf-c/protobuf-c b/components/protobuf-c/protobuf-c new file mode 160000 index 000000000..dac1a65fe --- /dev/null +++ b/components/protobuf-c/protobuf-c @@ -0,0 +1 @@ +Subproject commit dac1a65feac4ad72f612aab99f487056fbcf5c1a diff --git a/tools/ci/mirror-list.txt b/tools/ci/mirror-list.txt index 621a03b4c..b015e61cb 100644 --- a/tools/ci/mirror-list.txt +++ b/tools/ci/mirror-list.txt @@ -15,3 +15,4 @@ components/lwip/lwip @GENERAL_MIRROR_SERVER@/idf/ third-party/mruby @GENERAL_MIRROR_SERVER@/idf/mruby.git ALLOW_TO_SYNC_FROM_PUBLIC third-party/neverbleed @GENERAL_MIRROR_SERVER@/idf/neverbleed.git ALLOW_TO_SYNC_FROM_PUBLIC components/mqtt/esp-mqtt @GENERAL_MIRROR_SERVER@/idf/esp-mqtt.git ALLOW_TO_SYNC_FROM_PUBLIC +components/protobuf-c/protobuf-c @GENERAL_MIRROR_SERVER@/idf/protobuf-c.git ALLOW_TO_SYNC_FROM_PUBLIC From d0c777b2e1ded49f59d4d2b41e7f85eb32c7292a Mon Sep 17 00:00:00 2001 From: Amey Inamdar Date: Mon, 30 Jul 2018 21:33:10 +0530 Subject: [PATCH 2/7] Protocomm : Added component core for protocol communication * This manages secure sessions and provides framework for multiple transports. * The application can use protocomm layer directly to have application specific extensions for provisioning (or non-provisioning) use cases. * Following features are available for provisioning : * Security - Security0 (no security), Security1 (curve25519 key exchange + AES-CTR encryption) * Proof-of-possession support for Security1 * Protocomm requires specific protocol buffer modules for compilation which can be generated from the `.proto` files in the `proto` directory using make. Co-Authored-By: Amey Inamdar Co-Authored-By: Anurag Kar --- components/protocomm/component.mk | 3 + .../protocomm/include/common/protocomm.h | 226 ++++++++ .../include/security/protocomm_security.h | 93 +++ .../include/security/protocomm_security0.h | 25 + .../include/security/protocomm_security1.h | 25 + components/protocomm/proto/constants.proto | 14 + components/protocomm/proto/makefile | 7 + components/protocomm/proto/sec0.proto | 28 + components/protocomm/proto/sec1.proto | 45 ++ components/protocomm/proto/session.proto | 21 + components/protocomm/src/common/protocomm.c | 383 +++++++++++++ .../protocomm/src/common/protocomm_priv.h | 79 +++ components/protocomm/src/security/security0.c | 111 ++++ components/protocomm/src/security/security1.c | 535 ++++++++++++++++++ 14 files changed, 1595 insertions(+) create mode 100644 components/protocomm/component.mk create mode 100644 components/protocomm/include/common/protocomm.h create mode 100644 components/protocomm/include/security/protocomm_security.h create mode 100644 components/protocomm/include/security/protocomm_security0.h create mode 100644 components/protocomm/include/security/protocomm_security1.h create mode 100644 components/protocomm/proto/constants.proto create mode 100644 components/protocomm/proto/makefile create mode 100644 components/protocomm/proto/sec0.proto create mode 100644 components/protocomm/proto/sec1.proto create mode 100644 components/protocomm/proto/session.proto create mode 100644 components/protocomm/src/common/protocomm.c create mode 100644 components/protocomm/src/common/protocomm_priv.h create mode 100644 components/protocomm/src/security/security0.c create mode 100644 components/protocomm/src/security/security1.c diff --git a/components/protocomm/component.mk b/components/protocomm/component.mk new file mode 100644 index 000000000..ed8c4b7c0 --- /dev/null +++ b/components/protocomm/component.mk @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS := include/common include/security +COMPONENT_PRIV_INCLUDEDIRS := proto-c src/common +COMPONENT_SRCDIRS := src/common src/security proto-c diff --git a/components/protocomm/include/common/protocomm.h b/components/protocomm/include/common/protocomm.h new file mode 100644 index 000000000..f5f67da64 --- /dev/null +++ b/components/protocomm/include/common/protocomm.h @@ -0,0 +1,226 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#pragma once + +#include +#include + +/** + * @brief Function prototype for protocomm endpoint handler + */ +typedef esp_err_t (*protocomm_req_handler_t)( + uint32_t session_id, /*!< Session ID for identifying protocomm client */ + const uint8_t *inbuf, /*!< Pointer to user provided input data buffer */ + ssize_t inlen, /*!< Length o the input buffer */ + uint8_t **outbuf, /*!< Pointer to output buffer allocated by handler */ + ssize_t *outlen, /*!< Length of the allocated output buffer */ + void *priv_data /*!< Private data passed to the handler (NULL if not used) */ +); + +/** + * @brief This structure corresponds to a unique instance of protocomm + * returned when the API `protocomm_new()` is called. The remaining + * Protocomm APIs require this object as the first parameter. + * + * @note Structure of the protocomm object is kept private + */ +typedef struct protocomm protocomm_t; + +/** + * @brief Create a new protocomm instance + * + * This API will return a new dynamically allocated protocomm instance + * with all elements of the protocomm_t structure initialised to NULL. + * + * @return + * - protocomm_t* : On success + * - NULL : No memory for allocating new instance + */ +protocomm_t *protocomm_new(); + +/** + * @brief Delete a protocomm instance + * + * This API will deallocate a protocomm instance that was created + * using `protocomm_new()`. + * + * @param[in] pc Pointer to the protocomm instance to be deleted + */ +void protocomm_delete(protocomm_t *pc); + +/** + * @brief Add endpoint request handler for a protocomm instance + * + * This API will bind an endpoint handler function to the specified + * endpoint name, along with any private data that needs to be pass to + * the handler at the time of call. + * + * @note + * - An endpoint must be bound to a valid protocomm instance, + * created using `protocomm_new()`. + * - This function internally calls the registered `add_endpoint()` + * function which is a member of the protocomm_t instance structure. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * @param[in] h Endpoint handler function + * @param[in] priv_data Pointer to private data to be passed as a + * parameter to the handler function on call. + * Pass NULL if not needed. + * + * @return + * - ESP_OK : Added new endpoint succesfully + * - ESP_FAIL : Error adding endpoint / Endpoint with this name already exists + * - ESP_ERR_NO_MEM : Error allocating endpoint resource + * - ESP_ERR_INVALID_ARG : Null instance/name/handler arguments + */ +esp_err_t protocomm_add_endpoint(protocomm_t *pc, const char *ep_name, + protocomm_req_handler_t h, void *priv_data); + +/** + * @brief Remove endpoint request handler for a protocomm instance + * + * This API will remove a registered endpoint handler identified by + * an endpoint name. + * + * @note + * - This function internally calls the registered `remove_endpoint()` + * function which is a member of the protocomm_t instance structure. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * + * @return + * - ESP_OK : Added new endpoint succesfully + * - ESP_ERR_NOT_FOUND : Endpoint with specified name doesn't exist + * - ESP_ERR_INVALID_ARG : Null instance/name arguments + */ +esp_err_t protocomm_remove_endpoint(protocomm_t *pc, const char *ep_name); + +/** + * @brief Calls the registered handler of an endpoint session + * for processing incoming data and giving the output + * + * @note + * - An endpoint must be bound to a valid protocomm instance, + * created using `protocomm_new()`. + * - Resulting output buffer must be deallocated by the user. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * @param[in] session_id Unique ID for a communication session + * @param[in] inbuf Input buffer contains input request data which is to be + * processed by the registered handler + * @param[in] inlen Length of the input buffer + * @param[out] outbuf Pointer to internally allocated output buffer, + * where the resulting response data output from + * the registered handler is to be stored + * @param[out] outlen Buffer length of the allocated output buffer + * + * @return + * - ESP_OK : Request handled succesfully + * - ESP_FAIL : Internal error in execution of registered handler + * - ESP_ERR_NO_MEM : Error allocating internal resource + * - ESP_ERR_NOT_FOUND : Endpoint with specified name doesn't exist + * - ESP_ERR_INVALID_ARG : Null instance/name arguments + */ +esp_err_t protocomm_req_handle(protocomm_t *pc, const char *ep_name, uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen); + +/** + * @brief Add endpoint security for a protocomm instance + * + * This API will bind a security session establisher to the specified + * endpoint name, along with any proof of possession that may be required + * for authenticating a session client. + * + * @note + * - An endpoint must be bound to a valid protocomm instance, + * created using `protocomm_new()`. + * - The choice of security can be any `protocomm_security_t` instance. + * Choices `protocomm_security0` and `protocomm_security1` are readily available. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * @param[in] sec Pointer to endpoint security instance + * @param[in] pop Pointer to proof of possession for authenticating a client + * + * @return + * - ESP_OK : Added new security endpoint succesfully + * - ESP_FAIL : Error adding endpoint / Endpoint with this name already exists + * - ESP_ERR_INVALID_STATE : Security endpoint already set + * - ESP_ERR_NO_MEM : Error allocating endpoint resource + * - ESP_ERR_INVALID_ARG : Null instance/name/handler arguments + */ +esp_err_t protocomm_set_security(protocomm_t *pc, const char *ep_name, + const protocomm_security_t *sec, + const protocomm_security_pop_t *pop); + +/** + * @brief Remove endpoint security for a protocomm instance + * + * This API will remove a registered security endpoint identified by + * an endpoint name. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * + * @return + * - ESP_OK : Added new endpoint succesfully + * - ESP_ERR_NOT_FOUND : Endpoint with specified name doesn't exist + * - ESP_ERR_INVALID_ARG : Null instance/name arguments + */ +esp_err_t protocomm_unset_security(protocomm_t *pc, const char *ep_name); + +/** + * @brief Set endpoint for version verification + * + * This API can be used for setting an application specific protocol + * version which can be verfied by clients through the endpoint. + * + * @note + * - An endpoint must be bound to a valid protocomm instance, + * created using `protocomm_new()`. + + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * @param[in] version Version identifier(name) string + * + * @return + * - ESP_OK : Added new security endpoint succesfully + * - ESP_FAIL : Error adding endpoint / Endpoint with this name already exists + * - ESP_ERR_INVALID_STATE : Version endpoint already set + * - ESP_ERR_NO_MEM : Error allocating endpoint resource + * - ESP_ERR_INVALID_ARG : Null instance/name/handler arguments + */ +esp_err_t protocomm_set_version(protocomm_t *pc, const char *ep_name, + const char *version); + +/** + * @brief Remove version verification endpoint from a protocomm instance + * + * This API will remove a registered version endpoint identified by + * an endpoint name. + * + * @param[in] pc Pointer to the protocomm instance + * @param[in] ep_name Endpoint identifier(name) string + * + * @return + * - ESP_OK : Added new endpoint succesfully + * - ESP_ERR_NOT_FOUND : Endpoint with specified name doesn't exist + * - ESP_ERR_INVALID_ARG : Null instance/name arguments + */ +esp_err_t protocomm_unset_version(protocomm_t *pc, const char *ep_name); diff --git a/components/protocomm/include/security/protocomm_security.h b/components/protocomm/include/security/protocomm_security.h new file mode 100644 index 000000000..7eeecc16f --- /dev/null +++ b/components/protocomm/include/security/protocomm_security.h @@ -0,0 +1,93 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#pragma once + +#include + +/** + * @brief Proof Of Possession for authenticating a secure session + */ +typedef struct protocomm_security_pop { + /** + * Pointer to buffer containing the proof of possession data + */ + const uint8_t *data; + + /** + * Length (in bytes) of the proof of possession data + */ + uint16_t len; +} protocomm_security_pop_t; + +/** + * @brief Protocomm security object structure. + * + * The member functions are used for implementing secure + * protocomm sessions. + * + * @note This structure should not have any dynamic + * members to allow re-entrancy + */ +typedef struct protocomm_security { + /** + * Unique version number of security implmentation + */ + int ver; + + /** + * Function for initialising/allocating security + * infrastructure + */ + esp_err_t (*init)(); + + /** + * Function for deallocating security infrastructure + */ + esp_err_t (*cleanup)(); + + /** + * Starts new secure transport session with specified ID + */ + esp_err_t (*new_transport_session)(uint32_t session_id); + + /** + * Closes a secure transport session with specified ID + */ + esp_err_t (*close_transport_session)(uint32_t session_id); + + /** + * Handler function for authenticating connection + * request and establishing secure session + */ + esp_err_t (*security_req_handler)(const protocomm_security_pop_t *pop, + uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data); + + /** + * Function which implements the encryption algorithm + */ + esp_err_t (*encrypt)(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t *outbuf, ssize_t *outlen); + + /** + * Function which implements the decryption algorithm + */ + esp_err_t (*decrypt)(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t *outbuf, ssize_t *outlen); +} protocomm_security_t; diff --git a/components/protocomm/include/security/protocomm_security0.h b/components/protocomm/include/security/protocomm_security0.h new file mode 100644 index 000000000..7d0462d8e --- /dev/null +++ b/components/protocomm/include/security/protocomm_security0.h @@ -0,0 +1,25 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#pragma once + +#include + +/** + * @brief Protocomm security version 0 implementation + * + * This is a simple implementation to be used when no + * security is required for the protocomm instance + */ +extern const protocomm_security_t protocomm_security0; diff --git a/components/protocomm/include/security/protocomm_security1.h b/components/protocomm/include/security/protocomm_security1.h new file mode 100644 index 000000000..098d61e6a --- /dev/null +++ b/components/protocomm/include/security/protocomm_security1.h @@ -0,0 +1,25 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#pragma once + +#include + +/** + * @brief Protocomm security version 1 implementation + * + * This is a full fledged security implmentation using + * Curve25519 key exchange and AES-256-CTR encryption + */ +extern const protocomm_security_t protocomm_security1; diff --git a/components/protocomm/proto/constants.proto b/components/protocomm/proto/constants.proto new file mode 100644 index 000000000..c80d250e5 --- /dev/null +++ b/components/protocomm/proto/constants.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +/* Allowed values for the status + * of a protocomm instance */ +enum Status { + Success = 0; + InvalidSecScheme = 1; + InvalidProto = 2; + TooManySessions = 3; + InvalidArgument = 4; + InternalError = 5; + CryptoError = 6; + InvalidSession = 7; +} diff --git a/components/protocomm/proto/makefile b/components/protocomm/proto/makefile new file mode 100644 index 000000000..154e591b5 --- /dev/null +++ b/components/protocomm/proto/makefile @@ -0,0 +1,7 @@ +all: c_proto python_proto + +c_proto: *.proto + @protoc-c --c_out=../proto-c/ -I . *.proto + +python_proto: *.proto + @protoc --python_out=../python/ -I . *.proto diff --git a/components/protocomm/proto/sec0.proto b/components/protocomm/proto/sec0.proto new file mode 100644 index 000000000..299fce7c8 --- /dev/null +++ b/components/protocomm/proto/sec0.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +import "constants.proto"; + +/* Data structure of Session command/request packet */ +message S0SessionCmd { + +} + +/* Data structure of Session response packet */ +message S0SessionResp { + Status status = 1; +} + +/* A message must be of type Cmd or Resp */ +enum Sec0MsgType { + S0_Session_Command = 0; + S0_Session_Response = 1; +} + +/* Payload structure of session data */ +message Sec0Payload { + Sec0MsgType msg = 1; /*!< Type of message */ + oneof payload { + S0SessionCmd sc = 20; /*!< Payload data interpreted as Cmd */ + S0SessionResp sr = 21; /*!< Payload data interpreted as Resp */ + } +} diff --git a/components/protocomm/proto/sec1.proto b/components/protocomm/proto/sec1.proto new file mode 100644 index 000000000..97e05ea6d --- /dev/null +++ b/components/protocomm/proto/sec1.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +import "constants.proto"; + +/* Data structure of Session command1 packet */ +message SessionCmd1 { + bytes client_verify_data = 2; +} + +/* Data structure of Session response1 packet */ +message SessionResp1 { + Status status = 1; + bytes device_verify_data = 3; +} + +/* Data structure of Session command0 packet */ +message SessionCmd0 { + bytes client_pubkey = 1; +} + +/* Data structure of Session response0 packet */ +message SessionResp0 { + Status status = 1; + bytes device_pubkey = 2; + bytes device_random = 3; +} + +/* A message must be of type Cmd0 / Cmd1 / Resp0 / Resp1 */ +enum Sec1MsgType { + Session_Command0 = 0; + Session_Response0 = 1; + Session_Command1 = 2; + Session_Response1 = 3; +} + +/* Payload structure of session data */ +message Sec1Payload { + Sec1MsgType msg = 1; /*!< Type of message */ + oneof payload { + SessionCmd0 sc0 = 20; /*!< Payload data interpreted as Cmd0 */ + SessionResp0 sr0 = 21; /*!< Payload data interpreted as Resp0 */ + SessionCmd1 sc1 = 22; /*!< Payload data interpreted as Cmd1 */ + SessionResp1 sr1 = 23; /*!< Payload data interpreted as Resp1 */ + } +} diff --git a/components/protocomm/proto/session.proto b/components/protocomm/proto/session.proto new file mode 100644 index 000000000..de267b1f9 --- /dev/null +++ b/components/protocomm/proto/session.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +import "sec0.proto"; +import "sec1.proto"; + +/* Allowed values for the type of security + * being used in a protocomm session */ +enum SecSchemeVersion { + SecScheme0 = 0; /*!< Unsecured - plaintext communication */ + SecScheme1 = 1; /*!< Security scheme 1 - Curve25519 + AES-256-CTR*/ +} + +/* Data structure exchanged when establishing + * secure session between Host and Client */ +message SessionData { + SecSchemeVersion sec_ver = 2; /*!< Type of security */ + oneof proto { + Sec0Payload sec0 = 10; /*!< Payload data in case of security 0 */ + Sec1Payload sec1 = 11; /*!< Payload data in case of security 1 */ + } +} diff --git a/components/protocomm/src/common/protocomm.c b/components/protocomm/src/common/protocomm.c new file mode 100644 index 000000000..94ebc8e81 --- /dev/null +++ b/components/protocomm/src/common/protocomm.c @@ -0,0 +1,383 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 +#include + +#include +#include + +#include "protocomm_priv.h" + +static const char *TAG = "protocomm"; + +protocomm_t *protocomm_new() +{ + protocomm_t *pc; + + pc = (protocomm_t *) calloc(1, sizeof(protocomm_t)); + if (!pc) { + ESP_LOGE(TAG, "Error allocating protocomm"); + return NULL; + } + SLIST_INIT(&pc->endpoints); + + return pc; +} + +void protocomm_delete(protocomm_t *pc) +{ + if (pc == NULL) { + return; + } + + protocomm_ep_t *it, *tmp; + /* Remove endpoints first */ + SLIST_FOREACH_SAFE(it, &pc->endpoints, next, tmp) { + free(it); + } + + free(pc); +} + +static protocomm_ep_t *search_endpoint(protocomm_t *pc, const char *ep_name) +{ + protocomm_ep_t *it; + SLIST_FOREACH(it, &pc->endpoints, next) { + if (strcmp(it->ep_name, ep_name) == 0) { + return it; + } + } + return NULL; +} + +static esp_err_t protocomm_add_endpoint_internal(protocomm_t *pc, const char *ep_name, + protocomm_req_handler_t h, void *priv_data, + uint32_t flag) +{ + if ((pc == NULL) || (ep_name == NULL) || (h == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + protocomm_ep_t *ep; + esp_err_t ret; + + ep = search_endpoint(pc, ep_name); + if (ep) { + ESP_LOGE(TAG, "Endpoint with this name already exists"); + return ESP_FAIL; + } + + if (pc->add_endpoint) { + ret = pc->add_endpoint(ep_name, h, priv_data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error adding endpoint"); + return ret; + } + } + + ep = (protocomm_ep_t *) calloc(1, sizeof(protocomm_ep_t)); + if (!ep) { + ESP_LOGE(TAG, "Error allocating endpoint resource"); + return ESP_ERR_NO_MEM; + } + + /* Initialize ep handler */ + ep->ep_name = ep_name; + ep->req_handler = h; + ep->priv_data = priv_data; + ep->flag = flag; + + /* Add endpoint to the head of the singly linked list */ + SLIST_INSERT_HEAD(&pc->endpoints, ep, next); + + return ESP_OK; +} + +esp_err_t protocomm_add_endpoint(protocomm_t *pc, const char *ep_name, + protocomm_req_handler_t h, void *priv_data) +{ + return protocomm_add_endpoint_internal(pc, ep_name, h, priv_data, REQ_EP); +} + +esp_err_t protocomm_remove_endpoint(protocomm_t *pc, const char *ep_name) +{ + if ((pc == NULL) || (ep_name == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + if (pc->remove_endpoint) { + pc->remove_endpoint(ep_name); + } + + protocomm_ep_t *it, *tmp; + SLIST_FOREACH_SAFE(it, &pc->endpoints, next, tmp) { + if (strcmp(ep_name, it->ep_name) == 0) { + SLIST_REMOVE(&pc->endpoints, it, protocomm_ep, next); + free(it); + return ESP_OK; + } + } + return ESP_ERR_NOT_FOUND; +} + +esp_err_t protocomm_req_handle(protocomm_t *pc, const char *ep_name, uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen) +{ + if ((pc == NULL) || (ep_name == NULL)) { + ESP_LOGE(TAG, "Invalid params %p %p", pc, ep_name); + return ESP_ERR_INVALID_ARG; + } + + protocomm_ep_t *ep = search_endpoint(pc, ep_name); + if (!ep) { + ESP_LOGE(TAG, "No registered endpoint for %s", ep_name); + return ESP_ERR_NOT_FOUND; + } + + esp_err_t ret = ESP_FAIL; + if (ep->flag & SEC_EP) { + /* Call the registered endpoint handler for establishing secure session */ + ret = ep->req_handler(session_id, inbuf, inlen, outbuf, outlen, ep->priv_data); + ESP_LOGD(TAG, "SEC_EP Req handler returned %d", ret); + } else if (ep->flag & REQ_EP) { + if (pc->sec && pc->sec->decrypt) { + /* Decrypt the data first */ + uint8_t *dec_inbuf = (uint8_t *) malloc(inlen); + if (!dec_inbuf) { + ESP_LOGE(TAG, "Failed to allocate decrypt buf len %d", inlen); + return ESP_ERR_NO_MEM; + } + + ssize_t dec_inbuf_len = inlen; + pc->sec->decrypt(session_id, inbuf, inlen, dec_inbuf, &dec_inbuf_len); + + /* Invoke the request handler */ + uint8_t *plaintext_resp; + ssize_t plaintext_resp_len; + ret = ep->req_handler(session_id, + dec_inbuf, dec_inbuf_len, + &plaintext_resp, &plaintext_resp_len, + ep->priv_data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Request handler for %s failed", ep_name); + *outbuf = NULL; + *outlen = 0; + free(dec_inbuf); + return ret; + } + /* We don't need decrypted data anymore */ + free(dec_inbuf); + + /* Encrypt response to be sent back */ + uint8_t *enc_resp = (uint8_t *) malloc(plaintext_resp_len); + if (!enc_resp) { + ESP_LOGE(TAG, "Failed to allocate decrypt buf len %d", inlen); + return ESP_ERR_NO_MEM; + } + + ssize_t enc_resp_len = plaintext_resp_len; + pc->sec->encrypt(session_id, plaintext_resp, plaintext_resp_len, + enc_resp, &enc_resp_len); + + /* We no more need plaintext response */ + free(plaintext_resp); + + /* Set outbuf and outlen appropriately */ + *outbuf = enc_resp; + *outlen = enc_resp_len; + } else { + /* No encryption */ + ret = ep->req_handler(session_id, + inbuf, inlen, + outbuf, outlen, + ep->priv_data); + ESP_LOGD(TAG, "No encrypt ret %d", ret); + } + } else if (ep->flag & VER_EP) { + ret = ep->req_handler(session_id, inbuf, inlen, outbuf, outlen, ep->priv_data); + ESP_LOGD(TAG, "VER_EP Req handler returned %d", ret); + } + return ret; +} + +static int protocomm_common_security_handler(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) +{ + protocomm_t *pc = (protocomm_t *) priv_data; + + if (pc->sec && pc->sec->security_req_handler) { + return pc->sec->security_req_handler(pc->pop, session_id, + inbuf, inlen, + outbuf, outlen, + priv_data); + } + + return ESP_OK; +} + +esp_err_t protocomm_set_security(protocomm_t *pc, const char *ep_name, + const protocomm_security_t *sec, + const protocomm_security_pop_t *pop) +{ + if ((pc == NULL) || (ep_name == NULL) || (sec == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + if (pc->sec) { + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = protocomm_add_endpoint_internal(pc, ep_name, + protocomm_common_security_handler, + (void *) pc, SEC_EP); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error adding security endpoint"); + return ret; + } + + if (sec->init) { + ret = sec->init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error initialising security"); + protocomm_remove_endpoint(pc, ep_name); + return ret; + } + } + pc->sec = sec; + + if (pop) { + pc->pop = malloc(sizeof(protocomm_security_pop_t)); + if (pc->pop == NULL) { + ESP_LOGE(TAG, "Error allocating Proof of Possession"); + if (pc->sec && pc->sec->cleanup) { + pc->sec->cleanup(); + pc->sec = NULL; + } + + protocomm_remove_endpoint(pc, ep_name); + return ESP_ERR_NO_MEM; + } + memcpy((void *)pc->pop, pop, sizeof(protocomm_security_pop_t)); + } + return ESP_OK; +} + +esp_err_t protocomm_unset_security(protocomm_t *pc, const char *ep_name) +{ + if ((pc == NULL) || (ep_name == NULL)) { + return ESP_FAIL; + } + + if (pc->sec && pc->sec->cleanup) { + pc->sec->cleanup(); + pc->sec = NULL; + } + + if (pc->pop) { + free(pc->pop); + pc->pop = NULL; + } + + return protocomm_remove_endpoint(pc, ep_name); +} + +static int protocomm_version_handler(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) +{ + const char *resp_match = "SUCCESS"; + const char *resp_fail = "FAIL"; + bool match = false; + char *version = strndup((const char *)inbuf, inlen); + protocomm_t *pc = (protocomm_t *) priv_data; + *outbuf = NULL; + *outlen = 0; + + if ((pc->ver != NULL) && (version != NULL)) { + ESP_LOGV(TAG, "Protocol version of device : %s", pc->ver); + ESP_LOGV(TAG, "Protocol version of client : %s", version); + if (strcmp(pc->ver, version) == 0) { + match = true; + } + } else if ((pc->ver == NULL) && (version == NULL)) { + match = true; + } + free(version); + + if (!match) { + ESP_LOGE(TAG, "Protocol version mismatch"); + } + + const char *result_msg = match ? resp_match : resp_fail; + + /* Output is a non null terminated string with length specified */ + *outlen = strlen(result_msg); + *outbuf = malloc(strlen(result_msg)); + if (outbuf == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for version check response"); + return ESP_ERR_NO_MEM; + } + + memcpy(*outbuf, result_msg, *outlen); + return ESP_OK; +} + +esp_err_t protocomm_set_version(protocomm_t *pc, const char *ep_name, const char *version) +{ + if ((pc == NULL) || (ep_name == NULL) || (version == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + if (pc->ver) { + return ESP_ERR_INVALID_STATE; + } + + pc->ver = strdup(version); + if (pc->ver == NULL) { + ESP_LOGE(TAG, "Error allocating version string"); + return ESP_ERR_NO_MEM; + } + + esp_err_t ret = protocomm_add_endpoint_internal(pc, ep_name, + protocomm_version_handler, + (void *) pc, VER_EP); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error adding version endpoint"); + return ret; + } + return ESP_OK; +} + +esp_err_t protocomm_unset_version(protocomm_t *pc, const char *ep_name) +{ + if ((pc == NULL) || (ep_name == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + if (pc->ver) { + free((char *)pc->ver); + pc->ver = NULL; + } + + return protocomm_remove_endpoint(pc, ep_name); +} diff --git a/components/protocomm/src/common/protocomm_priv.h b/components/protocomm/src/common/protocomm_priv.h new file mode 100644 index 000000000..e956c3fd0 --- /dev/null +++ b/components/protocomm/src/common/protocomm_priv.h @@ -0,0 +1,79 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#pragma once + +#include +#include +#include + +#define PROTOCOMM_NO_SESSION_ID UINT32_MAX + +/* Bit Flags for indicating intended functionality of handler to either + * process request or establish secure session */ +#define REQ_EP (1 << 0) /*!< Flag indicating request handling endpoint */ +#define SEC_EP (1 << 1) /*!< Flag indicating security handling endpoint */ +#define VER_EP (1 << 2) /*!< Flag indicating version handling endpoint */ + +/** + * @brief Protocomm endpoint table entry prototype + * + * The structure of an entry stored in the endpoint table. + */ +typedef struct protocomm_ep { + const char *ep_name; /*!< Unique endpoint name */ + protocomm_req_handler_t req_handler; /*!< Request handler function */ + + /* Pointer to private data to be passed as a parameter to the handler + * function at the time of call. Set to NULL if not used. */ + void *priv_data; + + uint32_t flag; /*!< Flag indicating endpoint functionality */ + + /* Next endpoint entry in the singly linked list for storing endpoint handlers */ + SLIST_ENTRY(protocomm_ep) next; +} protocomm_ep_t; + +/** + * @brief Prototype structure of a Protocomm instance + * + * This structure corresponds to a unique instance of protocomm returned, + * when the API protocomm_new() is called. The remaining Protocomm + * APIs require this object as the first parameter. + */ +struct protocomm { + /* Function Prototype of transport specific function, which is called + * internally when protocomm_add_endpoint() is invoked. */ + int (*add_endpoint)(const char *ep_name, protocomm_req_handler_t h, void *priv_data); + + /* Function Prototype of transport specific function, which is called + * internally when protocomm_remove_endpoint() is invoked. */ + int (*remove_endpoint)(const char *ep_name); + + /* Pointer to security layer instance to be used internally for + * establising secure sessions */ + const protocomm_security_t *sec; + + /* Pointer to proof of possession object */ + protocomm_security_pop_t *pop; + + /* Head of the singly linked list for storing endpoint handlers */ + SLIST_HEAD(eptable_t, protocomm_ep) endpoints; + + /* Private data to be used internally by the protocomm instance */ + void* priv; + + /* Application specific version string */ + const char* ver; +}; diff --git a/components/protocomm/src/security/security0.c b/components/protocomm/src/security/security0.c new file mode 100644 index 000000000..bca4d18c3 --- /dev/null +++ b/components/protocomm/src/security/security0.c @@ -0,0 +1,111 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 +#include + +#include +#include + +#include "session.pb-c.h" +#include "sec0.pb-c.h" +#include "constants.pb-c.h" + +static const char* TAG = "security0"; + +static esp_err_t sec0_session_setup(uint32_t session_id, + SessionData *req, SessionData *resp, + const protocomm_security_pop_t *pop) +{ + Sec0Payload *out = (Sec0Payload *) malloc(sizeof(Sec0Payload)); + S0SessionResp *s0resp = (S0SessionResp *) malloc(sizeof(S0SessionResp)); + if (!out || !s0resp) { + ESP_LOGE(TAG, "Error allocating response"); + return ESP_ERR_NO_MEM; + } + sec0_payload__init(out); + s0_session_resp__init(s0resp); + s0resp->status = STATUS__Success; + + out->msg = SEC0_MSG_TYPE__S0_Session_Response; + out->payload_case = SEC0_PAYLOAD__PAYLOAD_SR; + out->sr = s0resp; + + resp->proto_case = SESSION_DATA__PROTO_SEC0; + resp->sec0 = out; + + return ESP_OK; +} + +static void sec0_session_setup_cleanup(uint32_t session_id, SessionData *resp) +{ + if (!resp) { + return; + } + + free(resp->sec0->sr); + free(resp->sec0); + return; +} + +static esp_err_t sec0_req_handler(const protocomm_security_pop_t *pop, uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) +{ + SessionData *req; + SessionData resp; + esp_err_t ret; + + req = session_data__unpack(NULL, inlen, inbuf); + if (!req) { + ESP_LOGE(TAG, "Unable to unpack setup_req"); + return ESP_ERR_INVALID_ARG; + } + if (req->sec_ver != protocomm_security0.ver) { + ESP_LOGE(TAG, "Security version mismatch. Closing connection"); + return ESP_ERR_INVALID_ARG; + } + + session_data__init(&resp); + ret = sec0_session_setup(session_id, req, &resp, pop); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Session setup error %d", ret); + return ESP_FAIL; + } + + session_data__free_unpacked(req, NULL); + + resp.sec_ver = req->sec_ver; + + *outlen = session_data__get_packed_size(&resp); + *outbuf = (uint8_t *) malloc(*outlen); + if (!*outbuf) { + ESP_LOGE(TAG, "System out of memory"); + return ESP_ERR_NO_MEM; + } + session_data__pack(&resp, *outbuf); + + sec0_session_setup_cleanup(session_id, &resp); + return ESP_OK; +} + +const protocomm_security_t protocomm_security0 = { + .ver = 0, + .security_req_handler = sec0_req_handler, +}; diff --git a/components/protocomm/src/security/security1.c b/components/protocomm/src/security/security1.c new file mode 100644 index 000000000..76b9a25f5 --- /dev/null +++ b/components/protocomm/src/security/security1.c @@ -0,0 +1,535 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "session.pb-c.h" +#include "sec1.pb-c.h" +#include "constants.pb-c.h" + +static const char* TAG = "security1"; + +#define PUBLIC_KEY_LEN 32 +#define SZ_RANDOM 16 + +#define SESSION_STATE_1 1 /* Session in state 1 */ +#define SESSION_STATE_SETUP 2 /* Session setup successful */ + +typedef struct session { + /* Session data */ + uint32_t id; + uint8_t state; + uint8_t device_pubkey[PUBLIC_KEY_LEN]; + uint8_t client_pubkey[PUBLIC_KEY_LEN]; + uint8_t sym_key[PUBLIC_KEY_LEN]; + uint8_t rand[SZ_RANDOM]; + + /* mbedtls context data for AES */ + mbedtls_aes_context ctx_aes; + unsigned char stb[16]; + size_t nc_off; +} session_t; + +static session_t *cur_session; + +static void flip_endian(uint8_t *data, size_t len) +{ + uint8_t swp_buf; + for (int i = 0; i < len/2; i++) { + swp_buf = data[i]; + data[i] = data[len - i - 1]; + data[len - i - 1] = swp_buf; + } +} + +static void hexdump(const char *msg, uint8_t *buf, int len) +{ + ESP_LOGD(TAG, "%s:", msg); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG); +} + +static esp_err_t handle_session_command1(uint32_t session_id, + SessionData *req, SessionData *resp) +{ + ESP_LOGD(TAG, "Request to handle setup1_command"); + Sec1Payload *in = (Sec1Payload *) req->sec1; + uint8_t check_buf[PUBLIC_KEY_LEN]; + int mbed_err; + + if (!cur_session) { + ESP_LOGE(TAG, "Data on session endpoint without session establishment"); + return ESP_ERR_INVALID_STATE; + } + + if (session_id != cur_session->id) { + ESP_LOGE(TAG, "Invalid session"); + return ESP_ERR_INVALID_STATE; + } + + if (!in) { + ESP_LOGE(TAG, "Empty session data"); + return ESP_ERR_INVALID_ARG; + } + + /* Initialise crypto context */ + mbedtls_aes_init(&cur_session->ctx_aes); + memset(cur_session->stb, 0, sizeof(cur_session->stb)); + cur_session->nc_off = 0; + + hexdump("Client verifier", in->sc1->client_verify_data.data, + in->sc1->client_verify_data.len); + + mbed_err = mbedtls_aes_setkey_enc(&cur_session->ctx_aes, cur_session->sym_key, + sizeof(cur_session->sym_key)*8); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failure at mbedtls_aes_setkey_enc with error code : -0x%x", -mbed_err); + return ESP_FAIL; + } + + mbed_err = mbedtls_aes_crypt_ctr(&cur_session->ctx_aes, + PUBLIC_KEY_LEN, &cur_session->nc_off, + cur_session->rand, cur_session->stb, + in->sc1->client_verify_data.data, check_buf); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failure at mbedtls_aes_crypt_ctr with error code : -0x%x", -mbed_err); + return ESP_FAIL; + } + + hexdump("Dec Client verifier", check_buf, sizeof(check_buf)); + + /* constant time memcmp */ + if (mbedtls_ssl_safer_memcmp(check_buf, cur_session->device_pubkey, + sizeof(cur_session->device_pubkey)) != 0) { + ESP_LOGE(TAG, "Key mismatch. Close connection"); + return ESP_FAIL; + } + + Sec1Payload *out = (Sec1Payload *) malloc(sizeof(Sec1Payload)); + sec1_payload__init(out); + SessionResp1 *out_resp = (SessionResp1 *) malloc(sizeof(SessionResp1)); + session_resp1__init(out_resp); + + out_resp->status = STATUS__Success; + + uint8_t *outbuf = (uint8_t *) malloc(PUBLIC_KEY_LEN); + if (!outbuf) { + ESP_LOGE(TAG, "Error allocating ciphertext buffer"); + return ESP_ERR_NO_MEM; + } + + mbed_err = mbedtls_aes_crypt_ctr(&cur_session->ctx_aes, + PUBLIC_KEY_LEN, &cur_session->nc_off, + cur_session->rand, cur_session->stb, + cur_session->client_pubkey, outbuf); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failure at mbedtls_aes_crypt_ctr with error code : -0x%x", -mbed_err); + return ESP_FAIL; + } + + out_resp->device_verify_data.data = outbuf; + out_resp->device_verify_data.len = PUBLIC_KEY_LEN; + hexdump("Device verify data", outbuf, PUBLIC_KEY_LEN); + + out->msg = SEC1_MSG_TYPE__Session_Response1; + out->payload_case = SEC1_PAYLOAD__PAYLOAD_SR1; + out->sr1 = out_resp; + + resp->proto_case = SESSION_DATA__PROTO_SEC1; + resp->sec1 = out; + + ESP_LOGD(TAG, "Session successful"); + + return ESP_OK; +} + +static esp_err_t handle_session_command0(uint32_t session_id, + SessionData *req, SessionData *resp, + const protocomm_security_pop_t *pop) +{ + Sec1Payload *in = (Sec1Payload *) req->sec1; + esp_err_t ret; + int mbed_err; + + if (!cur_session) { + ESP_LOGE(TAG, "Data on session endpoint without session establishment"); + return ESP_ERR_INVALID_STATE; + } + + if (session_id != cur_session->id) { + ESP_LOGE(TAG, "Invalid session"); + return ESP_ERR_INVALID_STATE; + } + + if (!in) { + ESP_LOGE(TAG, "Empty session data"); + return ESP_ERR_INVALID_ARG; + } + + if (in->sc0->client_pubkey.len != PUBLIC_KEY_LEN) { + ESP_LOGE(TAG, "Invalid public key length"); + return ESP_ERR_INVALID_ARG; + } + + cur_session->state = SESSION_STATE_1; + + mbedtls_ecdh_context *ctx_server = malloc(sizeof(mbedtls_ecdh_context)); + mbedtls_entropy_context *entropy = malloc(sizeof(mbedtls_entropy_context)); + mbedtls_ctr_drbg_context *ctr_drbg = malloc(sizeof(mbedtls_ctr_drbg_context)); + if (!ctx_server || !ctx_server || !ctr_drbg) { + ESP_LOGE(TAG, "Failed to allocate memory for mbedtls context"); + free(ctx_server); + free(entropy); + free(ctr_drbg); + return ESP_ERR_NO_MEM; + } + + mbedtls_ecdh_init(ctx_server); + mbedtls_ctr_drbg_init(ctr_drbg); + mbedtls_entropy_init(entropy); + + mbed_err = mbedtls_ctr_drbg_seed(ctr_drbg, mbedtls_entropy_func, + entropy, NULL, 0); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ctr_drbg_seed with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_ecp_group_load(&ctx_server->grp, MBEDTLS_ECP_DP_CURVE25519); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ecp_group_load with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_ecdh_gen_public(&ctx_server->grp, &ctx_server->d, &ctx_server->Q, + mbedtls_ctr_drbg_random, ctr_drbg); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ecdh_gen_public with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_mpi_write_binary(&ctx_server->Q.X, + cur_session->device_pubkey, + PUBLIC_KEY_LEN); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_write_binary with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + flip_endian(cur_session->device_pubkey, PUBLIC_KEY_LEN); + + memcpy(cur_session->client_pubkey, in->sc0->client_pubkey.data, PUBLIC_KEY_LEN); + + uint8_t *dev_pubkey = cur_session->device_pubkey; + uint8_t *cli_pubkey = cur_session->client_pubkey; + hexdump("Device pubkey", dev_pubkey, PUBLIC_KEY_LEN); + hexdump("Client pubkey", cli_pubkey, PUBLIC_KEY_LEN); + + mbed_err = mbedtls_mpi_lset(&ctx_server->Qp.Z, 1); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_lset with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + flip_endian(cur_session->client_pubkey, PUBLIC_KEY_LEN); + mbed_err = mbedtls_mpi_read_binary(&ctx_server->Qp.X, cli_pubkey, PUBLIC_KEY_LEN); + flip_endian(cur_session->client_pubkey, PUBLIC_KEY_LEN); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_read_binary with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_ecdh_compute_shared(&ctx_server->grp, &ctx_server->z, &ctx_server->Qp, + &ctx_server->d, mbedtls_ctr_drbg_random, ctr_drbg); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ecdh_compute_shared with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + mbed_err = mbedtls_mpi_write_binary(&ctx_server->z, cur_session->sym_key, PUBLIC_KEY_LEN); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_write_binary with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + flip_endian(cur_session->sym_key, PUBLIC_KEY_LEN); + + if (pop != NULL && pop->data != NULL && pop->len != 0) { + ESP_LOGD(TAG, "Adding proof of possession"); + uint8_t sha_out[PUBLIC_KEY_LEN]; + + mbed_err = mbedtls_sha256_ret((const unsigned char *) pop->data, pop->len, sha_out, 0); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_sha256_ret with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + for (int i = 0; i < PUBLIC_KEY_LEN; i++) { + cur_session->sym_key[i] ^= sha_out[i]; + } + } + + hexdump("Shared key", cur_session->sym_key, PUBLIC_KEY_LEN); + + mbed_err = mbedtls_ctr_drbg_random(ctr_drbg, cur_session->rand, SZ_RANDOM); + if (mbed_err != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ctr_drbg_random with error code : -0x%x", -mbed_err); + ret = ESP_FAIL; + goto exit_cmd0; + } + + hexdump("Device random", cur_session->rand, SZ_RANDOM); + + Sec1Payload *out = (Sec1Payload *) malloc(sizeof(Sec1Payload)); + SessionResp0 *out_resp = (SessionResp0 *) malloc(sizeof(SessionResp0)); + if (!out || !out_resp) { + ESP_LOGE(TAG, "Error allocating memory for response"); + ret = ESP_FAIL; + goto exit_cmd0; + } + + sec1_payload__init(out); + session_resp0__init(out_resp); + + out_resp->status = STATUS__Success; + + out_resp->device_pubkey.data = dev_pubkey; + out_resp->device_pubkey.len = PUBLIC_KEY_LEN; + + out_resp->device_random.data = (uint8_t *) cur_session->rand; + out_resp->device_random.len = SZ_RANDOM; + + out->msg = SEC1_MSG_TYPE__Session_Response0; + out->payload_case = SEC1_PAYLOAD__PAYLOAD_SR0; + out->sr0 = out_resp; + + resp->proto_case = SESSION_DATA__PROTO_SEC1; + resp->sec1 = out; + + ESP_LOGD(TAG, "Session setup phase1 done"); + ret = ESP_OK; + +exit_cmd0: + mbedtls_ecdh_free(ctx_server); + free(ctx_server); + + mbedtls_ctr_drbg_free(ctr_drbg); + free(ctr_drbg); + + mbedtls_entropy_free(entropy); + free(entropy); + + return ret; +} + +static esp_err_t sec1_session_setup(uint32_t session_id, + SessionData *req, SessionData *resp, + const protocomm_security_pop_t *pop) +{ + Sec1Payload *in = (Sec1Payload *) req->sec1; + esp_err_t ret; + + switch (in->msg) { + case SEC1_MSG_TYPE__Session_Command0: + ret = handle_session_command0(session_id, req, resp, pop); + break; + case SEC1_MSG_TYPE__Session_Command1: + ret = handle_session_command1(session_id, req, resp); + break; + default: + ESP_LOGE(TAG, "Invalid security message type"); + ret = ESP_ERR_INVALID_ARG; + } + + return ret; + +} + +static void sec1_session_setup_cleanup(uint32_t session_id, SessionData *resp) +{ + Sec1Payload *out = resp->sec1; + + if (!out) { + return; + } + + switch (out->msg) { + case SEC1_MSG_TYPE__Session_Response0: + { + SessionResp0 *out_resp0 = out->sr0; + if (out_resp0) { + free(out_resp0); + } + break; + } + case SEC1_MSG_TYPE__Session_Response1: + { + SessionResp1 *out_resp1 = out->sr1; + if (out_resp1) { + free(out_resp1->device_verify_data.data); + free(out_resp1); + } + break; + } + default: + break; + } + free(out); + + return; +} + +static esp_err_t sec1_init() +{ + return ESP_OK; +} + +static esp_err_t sec1_cleanup() +{ + if (cur_session) { + ESP_LOGD(TAG, "Closing current session with id %u", cur_session->id); + mbedtls_aes_free(&cur_session->ctx_aes); + bzero(cur_session, sizeof(session_t)); + free(cur_session); + cur_session = NULL; + } + return ESP_OK; +} + +static esp_err_t sec1_new_session(uint32_t session_id) +{ + if (cur_session && cur_session->id != session_id) { + ESP_LOGE(TAG, "Closing old session with id %u", cur_session->id); + sec1_cleanup(); + } else if (cur_session && cur_session->id == session_id) { + return ESP_OK; + } + + cur_session = (session_t *) calloc(1, sizeof(session_t)); + if (!cur_session) { + ESP_LOGE(TAG, "Error allocating session structure"); + return ESP_ERR_NO_MEM; + } + + cur_session->id = session_id; + return ESP_OK; +} + +static esp_err_t sec1_close_session(uint32_t session_id) +{ + if (!cur_session || cur_session->id != session_id) { + ESP_LOGE(TAG, "Attempt to close invalid session"); + return ESP_ERR_INVALID_ARG; + } + + bzero(cur_session, sizeof(session_t)); + free(cur_session); + cur_session = NULL; + return ESP_OK; +} + +static esp_err_t sec1_decrypt(uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t *outbuf, ssize_t *outlen) +{ + if (*outlen < inlen) { + return ESP_ERR_INVALID_ARG; + } + + if (!cur_session || cur_session->id != session_id) { + return ESP_ERR_INVALID_STATE; + } + *outlen = inlen; + int ret = mbedtls_aes_crypt_ctr(&cur_session->ctx_aes, inlen, &cur_session->nc_off, + cur_session->rand, cur_session->stb, inbuf, outbuf); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_aes_crypt_ctr with error code : %d", ret); + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t sec1_req_handler(const protocomm_security_pop_t *pop, uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) +{ + SessionData *req; + SessionData resp; + esp_err_t ret; + + req = session_data__unpack(NULL, inlen, inbuf); + if (!req) { + ESP_LOGE(TAG, "Unable to unpack setup_req"); + return ESP_ERR_INVALID_ARG; + } + if (req->sec_ver != protocomm_security1.ver) { + ESP_LOGE(TAG, "Security version mismatch. Closing connection"); + return ESP_ERR_INVALID_ARG; + } + + session_data__init(&resp); + ret = sec1_session_setup(session_id, req, &resp, pop); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Session setup error %d", ret); + return ESP_FAIL; + } + + session_data__free_unpacked(req, NULL); + + resp.sec_ver = req->sec_ver; + + *outlen = session_data__get_packed_size(&resp); + *outbuf = (uint8_t *) malloc(*outlen); + if (!*outbuf) { + ESP_LOGE(TAG, "System out of memory"); + return ESP_ERR_NO_MEM; + } + session_data__pack(&resp, *outbuf); + + sec1_session_setup_cleanup(session_id, &resp); + return ESP_OK; +} + +const protocomm_security_t protocomm_security1 = { + .ver = 1, + .init = sec1_init, + .cleanup = sec1_cleanup, + .new_transport_session = sec1_new_session, + .close_transport_session = sec1_close_session, + .security_req_handler = sec1_req_handler, + .encrypt = sec1_decrypt, /* Encrypt == decrypt for AES-CTR */ + .decrypt = sec1_decrypt, +}; From e94dffc9c555ee39ed6da2617e5cf09fe5a34a14 Mon Sep 17 00:00:00 2001 From: Amey Inamdar Date: Mon, 30 Jul 2018 21:35:03 +0530 Subject: [PATCH 3/7] Protocomm : Added unit tests and protobuf-c generated files Co-Authored-By: Amey Inamdar Co-Authored-By: Anurag Kar --- components/protocomm/component.mk | 4 +- components/protocomm/proto-c/constants.pb-c.c | 49 + components/protocomm/proto-c/constants.pb-c.h | 53 + components/protocomm/proto-c/sec0.pb-c.c | 293 +++++ components/protocomm/proto-c/sec0.pb-c.h | 178 +++ components/protocomm/proto-c/sec1.pb-c.c | 549 +++++++++ components/protocomm/proto-c/sec1.pb-c.h | 268 ++++ components/protocomm/proto-c/session.pb-c.c | 147 +++ components/protocomm/proto-c/session.pb-c.h | 114 ++ components/protocomm/python/constants_pb2.py | 87 ++ components/protocomm/python/sec0_pb2.py | 196 +++ components/protocomm/python/sec1_pb2.py | 335 +++++ components/protocomm/python/session_pb2.py | 125 ++ components/protocomm/test/component.mk | 2 + components/protocomm/test/test_protocomm.c | 1089 +++++++++++++++++ 15 files changed, 3487 insertions(+), 2 deletions(-) create mode 100644 components/protocomm/proto-c/constants.pb-c.c create mode 100644 components/protocomm/proto-c/constants.pb-c.h create mode 100644 components/protocomm/proto-c/sec0.pb-c.c create mode 100644 components/protocomm/proto-c/sec0.pb-c.h create mode 100644 components/protocomm/proto-c/sec1.pb-c.c create mode 100644 components/protocomm/proto-c/sec1.pb-c.h create mode 100644 components/protocomm/proto-c/session.pb-c.c create mode 100644 components/protocomm/proto-c/session.pb-c.h create mode 100644 components/protocomm/python/constants_pb2.py create mode 100644 components/protocomm/python/sec0_pb2.py create mode 100644 components/protocomm/python/sec1_pb2.py create mode 100644 components/protocomm/python/session_pb2.py create mode 100644 components/protocomm/test/component.mk create mode 100644 components/protocomm/test/test_protocomm.c diff --git a/components/protocomm/component.mk b/components/protocomm/component.mk index ed8c4b7c0..7941a8e3b 100644 --- a/components/protocomm/component.mk +++ b/components/protocomm/component.mk @@ -1,3 +1,3 @@ -COMPONENT_ADD_INCLUDEDIRS := include/common include/security -COMPONENT_PRIV_INCLUDEDIRS := proto-c src/common +COMPONENT_ADD_INCLUDEDIRS := include/common include/security proto-c +COMPONENT_PRIV_INCLUDEDIRS := src/common COMPONENT_SRCDIRS := src/common src/security proto-c diff --git a/components/protocomm/proto-c/constants.pb-c.c b/components/protocomm/proto-c/constants.pb-c.c new file mode 100644 index 000000000..3b06d5f38 --- /dev/null +++ b/components/protocomm/proto-c/constants.pb-c.c @@ -0,0 +1,49 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: constants.proto */ + +/* Do not generate deprecated warnings for self */ +#ifndef PROTOBUF_C__NO_DEPRECATED +#define PROTOBUF_C__NO_DEPRECATED +#endif + +#include "constants.pb-c.h" +static const ProtobufCEnumValue status__enum_values_by_number[8] = +{ + { "Success", "STATUS__Success", 0 }, + { "InvalidSecScheme", "STATUS__InvalidSecScheme", 1 }, + { "InvalidProto", "STATUS__InvalidProto", 2 }, + { "TooManySessions", "STATUS__TooManySessions", 3 }, + { "InvalidArgument", "STATUS__InvalidArgument", 4 }, + { "InternalError", "STATUS__InternalError", 5 }, + { "CryptoError", "STATUS__CryptoError", 6 }, + { "InvalidSession", "STATUS__InvalidSession", 7 }, +}; +static const ProtobufCIntRange status__value_ranges[] = { +{0, 0},{0, 8} +}; +static const ProtobufCEnumValueIndex status__enum_values_by_name[8] = +{ + { "CryptoError", 6 }, + { "InternalError", 5 }, + { "InvalidArgument", 4 }, + { "InvalidProto", 2 }, + { "InvalidSecScheme", 1 }, + { "InvalidSession", 7 }, + { "Success", 0 }, + { "TooManySessions", 3 }, +}; +const ProtobufCEnumDescriptor status__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "Status", + "Status", + "Status", + "", + 8, + status__enum_values_by_number, + 8, + status__enum_values_by_name, + 1, + status__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; diff --git a/components/protocomm/proto-c/constants.pb-c.h b/components/protocomm/proto-c/constants.pb-c.h new file mode 100644 index 000000000..6fa97027e --- /dev/null +++ b/components/protocomm/proto-c/constants.pb-c.h @@ -0,0 +1,53 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: constants.proto */ + +#ifndef PROTOBUF_C_constants_2eproto__INCLUDED +#define PROTOBUF_C_constants_2eproto__INCLUDED + +#include + +PROTOBUF_C__BEGIN_DECLS + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. +#elif 1003000 < PROTOBUF_C_MIN_COMPILER_VERSION +# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. +#endif + + + + +/* --- enums --- */ + +/* + * Allowed values for the status + * of a protocomm instance + */ +typedef enum _Status { + STATUS__Success = 0, + STATUS__InvalidSecScheme = 1, + STATUS__InvalidProto = 2, + STATUS__TooManySessions = 3, + STATUS__InvalidArgument = 4, + STATUS__InternalError = 5, + STATUS__CryptoError = 6, + STATUS__InvalidSession = 7 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(STATUS) +} Status; + +/* --- messages --- */ + +/* --- per-message closures --- */ + + +/* --- services --- */ + + +/* --- descriptors --- */ + +extern const ProtobufCEnumDescriptor status__descriptor; + +PROTOBUF_C__END_DECLS + + +#endif /* PROTOBUF_C_constants_2eproto__INCLUDED */ diff --git a/components/protocomm/proto-c/sec0.pb-c.c b/components/protocomm/proto-c/sec0.pb-c.c new file mode 100644 index 000000000..dd520f07b --- /dev/null +++ b/components/protocomm/proto-c/sec0.pb-c.c @@ -0,0 +1,293 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: sec0.proto */ + +/* Do not generate deprecated warnings for self */ +#ifndef PROTOBUF_C__NO_DEPRECATED +#define PROTOBUF_C__NO_DEPRECATED +#endif + +#include "sec0.pb-c.h" +void s0_session_cmd__init + (S0SessionCmd *message) +{ + static const S0SessionCmd init_value = S0_SESSION_CMD__INIT; + *message = init_value; +} +size_t s0_session_cmd__get_packed_size + (const S0SessionCmd *message) +{ + assert(message->base.descriptor == &s0_session_cmd__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t s0_session_cmd__pack + (const S0SessionCmd *message, + uint8_t *out) +{ + assert(message->base.descriptor == &s0_session_cmd__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t s0_session_cmd__pack_to_buffer + (const S0SessionCmd *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &s0_session_cmd__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +S0SessionCmd * + s0_session_cmd__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (S0SessionCmd *) + protobuf_c_message_unpack (&s0_session_cmd__descriptor, + allocator, len, data); +} +void s0_session_cmd__free_unpacked + (S0SessionCmd *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &s0_session_cmd__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void s0_session_resp__init + (S0SessionResp *message) +{ + static const S0SessionResp init_value = S0_SESSION_RESP__INIT; + *message = init_value; +} +size_t s0_session_resp__get_packed_size + (const S0SessionResp *message) +{ + assert(message->base.descriptor == &s0_session_resp__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t s0_session_resp__pack + (const S0SessionResp *message, + uint8_t *out) +{ + assert(message->base.descriptor == &s0_session_resp__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t s0_session_resp__pack_to_buffer + (const S0SessionResp *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &s0_session_resp__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +S0SessionResp * + s0_session_resp__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (S0SessionResp *) + protobuf_c_message_unpack (&s0_session_resp__descriptor, + allocator, len, data); +} +void s0_session_resp__free_unpacked + (S0SessionResp *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &s0_session_resp__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void sec0_payload__init + (Sec0Payload *message) +{ + static const Sec0Payload init_value = SEC0_PAYLOAD__INIT; + *message = init_value; +} +size_t sec0_payload__get_packed_size + (const Sec0Payload *message) +{ + assert(message->base.descriptor == &sec0_payload__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t sec0_payload__pack + (const Sec0Payload *message, + uint8_t *out) +{ + assert(message->base.descriptor == &sec0_payload__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t sec0_payload__pack_to_buffer + (const Sec0Payload *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &sec0_payload__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +Sec0Payload * + sec0_payload__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (Sec0Payload *) + protobuf_c_message_unpack (&sec0_payload__descriptor, + allocator, len, data); +} +void sec0_payload__free_unpacked + (Sec0Payload *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &sec0_payload__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +#define s0_session_cmd__field_descriptors NULL +#define s0_session_cmd__field_indices_by_name NULL +#define s0_session_cmd__number_ranges NULL +const ProtobufCMessageDescriptor s0_session_cmd__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "S0SessionCmd", + "S0SessionCmd", + "S0SessionCmd", + "", + sizeof(S0SessionCmd), + 0, + s0_session_cmd__field_descriptors, + s0_session_cmd__field_indices_by_name, + 0, s0_session_cmd__number_ranges, + (ProtobufCMessageInit) s0_session_cmd__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor s0_session_resp__field_descriptors[1] = +{ + { + "status", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(S0SessionResp, status), + &status__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned s0_session_resp__field_indices_by_name[] = { + 0, /* field[0] = status */ +}; +static const ProtobufCIntRange s0_session_resp__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 1 } +}; +const ProtobufCMessageDescriptor s0_session_resp__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "S0SessionResp", + "S0SessionResp", + "S0SessionResp", + "", + sizeof(S0SessionResp), + 1, + s0_session_resp__field_descriptors, + s0_session_resp__field_indices_by_name, + 1, s0_session_resp__number_ranges, + (ProtobufCMessageInit) s0_session_resp__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor sec0_payload__field_descriptors[3] = +{ + { + "msg", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(Sec0Payload, msg), + &sec0_msg_type__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "sc", + 20, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(Sec0Payload, payload_case), + offsetof(Sec0Payload, sc), + &s0_session_cmd__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "sr", + 21, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(Sec0Payload, payload_case), + offsetof(Sec0Payload, sr), + &s0_session_resp__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned sec0_payload__field_indices_by_name[] = { + 0, /* field[0] = msg */ + 1, /* field[1] = sc */ + 2, /* field[2] = sr */ +}; +static const ProtobufCIntRange sec0_payload__number_ranges[2 + 1] = +{ + { 1, 0 }, + { 20, 1 }, + { 0, 3 } +}; +const ProtobufCMessageDescriptor sec0_payload__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "Sec0Payload", + "Sec0Payload", + "Sec0Payload", + "", + sizeof(Sec0Payload), + 3, + sec0_payload__field_descriptors, + sec0_payload__field_indices_by_name, + 2, sec0_payload__number_ranges, + (ProtobufCMessageInit) sec0_payload__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCEnumValue sec0_msg_type__enum_values_by_number[2] = +{ + { "S0_Session_Command", "SEC0_MSG_TYPE__S0_Session_Command", 0 }, + { "S0_Session_Response", "SEC0_MSG_TYPE__S0_Session_Response", 1 }, +}; +static const ProtobufCIntRange sec0_msg_type__value_ranges[] = { +{0, 0},{0, 2} +}; +static const ProtobufCEnumValueIndex sec0_msg_type__enum_values_by_name[2] = +{ + { "S0_Session_Command", 0 }, + { "S0_Session_Response", 1 }, +}; +const ProtobufCEnumDescriptor sec0_msg_type__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "Sec0MsgType", + "Sec0MsgType", + "Sec0MsgType", + "", + 2, + sec0_msg_type__enum_values_by_number, + 2, + sec0_msg_type__enum_values_by_name, + 1, + sec0_msg_type__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; diff --git a/components/protocomm/proto-c/sec0.pb-c.h b/components/protocomm/proto-c/sec0.pb-c.h new file mode 100644 index 000000000..82779e2be --- /dev/null +++ b/components/protocomm/proto-c/sec0.pb-c.h @@ -0,0 +1,178 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: sec0.proto */ + +#ifndef PROTOBUF_C_sec0_2eproto__INCLUDED +#define PROTOBUF_C_sec0_2eproto__INCLUDED + +#include + +PROTOBUF_C__BEGIN_DECLS + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. +#elif 1003000 < PROTOBUF_C_MIN_COMPILER_VERSION +# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. +#endif + +#include "constants.pb-c.h" + +typedef struct _S0SessionCmd S0SessionCmd; +typedef struct _S0SessionResp S0SessionResp; +typedef struct _Sec0Payload Sec0Payload; + + +/* --- enums --- */ + +/* + * A message must be of type Cmd or Resp + */ +typedef enum _Sec0MsgType { + SEC0_MSG_TYPE__S0_Session_Command = 0, + SEC0_MSG_TYPE__S0_Session_Response = 1 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(SEC0_MSG_TYPE) +} Sec0MsgType; + +/* --- messages --- */ + +/* + * Data structure of Session command/request packet + */ +struct _S0SessionCmd +{ + ProtobufCMessage base; +}; +#define S0_SESSION_CMD__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&s0_session_cmd__descriptor) \ + } + + +/* + * Data structure of Session response packet + */ +struct _S0SessionResp +{ + ProtobufCMessage base; + Status status; +}; +#define S0_SESSION_RESP__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&s0_session_resp__descriptor) \ + , STATUS__Success } + + +typedef enum { + SEC0_PAYLOAD__PAYLOAD__NOT_SET = 0, + SEC0_PAYLOAD__PAYLOAD_SC = 20, + SEC0_PAYLOAD__PAYLOAD_SR = 21 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(SEC0_PAYLOAD__PAYLOAD) +} Sec0Payload__PayloadCase; + +/* + * Payload structure of session data + */ +struct _Sec0Payload +{ + ProtobufCMessage base; + /* + *!< Type of message + */ + Sec0MsgType msg; + Sec0Payload__PayloadCase payload_case; + union { + /* + *!< Payload data interpreted as Cmd + */ + S0SessionCmd *sc; + /* + *!< Payload data interpreted as Resp + */ + S0SessionResp *sr; + }; +}; +#define SEC0_PAYLOAD__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&sec0_payload__descriptor) \ + , SEC0_MSG_TYPE__S0_Session_Command, SEC0_PAYLOAD__PAYLOAD__NOT_SET, {0} } + + +/* S0SessionCmd methods */ +void s0_session_cmd__init + (S0SessionCmd *message); +size_t s0_session_cmd__get_packed_size + (const S0SessionCmd *message); +size_t s0_session_cmd__pack + (const S0SessionCmd *message, + uint8_t *out); +size_t s0_session_cmd__pack_to_buffer + (const S0SessionCmd *message, + ProtobufCBuffer *buffer); +S0SessionCmd * + s0_session_cmd__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void s0_session_cmd__free_unpacked + (S0SessionCmd *message, + ProtobufCAllocator *allocator); +/* S0SessionResp methods */ +void s0_session_resp__init + (S0SessionResp *message); +size_t s0_session_resp__get_packed_size + (const S0SessionResp *message); +size_t s0_session_resp__pack + (const S0SessionResp *message, + uint8_t *out); +size_t s0_session_resp__pack_to_buffer + (const S0SessionResp *message, + ProtobufCBuffer *buffer); +S0SessionResp * + s0_session_resp__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void s0_session_resp__free_unpacked + (S0SessionResp *message, + ProtobufCAllocator *allocator); +/* Sec0Payload methods */ +void sec0_payload__init + (Sec0Payload *message); +size_t sec0_payload__get_packed_size + (const Sec0Payload *message); +size_t sec0_payload__pack + (const Sec0Payload *message, + uint8_t *out); +size_t sec0_payload__pack_to_buffer + (const Sec0Payload *message, + ProtobufCBuffer *buffer); +Sec0Payload * + sec0_payload__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void sec0_payload__free_unpacked + (Sec0Payload *message, + ProtobufCAllocator *allocator); +/* --- per-message closures --- */ + +typedef void (*S0SessionCmd_Closure) + (const S0SessionCmd *message, + void *closure_data); +typedef void (*S0SessionResp_Closure) + (const S0SessionResp *message, + void *closure_data); +typedef void (*Sec0Payload_Closure) + (const Sec0Payload *message, + void *closure_data); + +/* --- services --- */ + + +/* --- descriptors --- */ + +extern const ProtobufCEnumDescriptor sec0_msg_type__descriptor; +extern const ProtobufCMessageDescriptor s0_session_cmd__descriptor; +extern const ProtobufCMessageDescriptor s0_session_resp__descriptor; +extern const ProtobufCMessageDescriptor sec0_payload__descriptor; + +PROTOBUF_C__END_DECLS + + +#endif /* PROTOBUF_C_sec0_2eproto__INCLUDED */ diff --git a/components/protocomm/proto-c/sec1.pb-c.c b/components/protocomm/proto-c/sec1.pb-c.c new file mode 100644 index 000000000..c749db48b --- /dev/null +++ b/components/protocomm/proto-c/sec1.pb-c.c @@ -0,0 +1,549 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: sec1.proto */ + +/* Do not generate deprecated warnings for self */ +#ifndef PROTOBUF_C__NO_DEPRECATED +#define PROTOBUF_C__NO_DEPRECATED +#endif + +#include "sec1.pb-c.h" +void session_cmd1__init + (SessionCmd1 *message) +{ + static const SessionCmd1 init_value = SESSION_CMD1__INIT; + *message = init_value; +} +size_t session_cmd1__get_packed_size + (const SessionCmd1 *message) +{ + assert(message->base.descriptor == &session_cmd1__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t session_cmd1__pack + (const SessionCmd1 *message, + uint8_t *out) +{ + assert(message->base.descriptor == &session_cmd1__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t session_cmd1__pack_to_buffer + (const SessionCmd1 *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &session_cmd1__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +SessionCmd1 * + session_cmd1__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (SessionCmd1 *) + protobuf_c_message_unpack (&session_cmd1__descriptor, + allocator, len, data); +} +void session_cmd1__free_unpacked + (SessionCmd1 *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &session_cmd1__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void session_resp1__init + (SessionResp1 *message) +{ + static const SessionResp1 init_value = SESSION_RESP1__INIT; + *message = init_value; +} +size_t session_resp1__get_packed_size + (const SessionResp1 *message) +{ + assert(message->base.descriptor == &session_resp1__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t session_resp1__pack + (const SessionResp1 *message, + uint8_t *out) +{ + assert(message->base.descriptor == &session_resp1__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t session_resp1__pack_to_buffer + (const SessionResp1 *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &session_resp1__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +SessionResp1 * + session_resp1__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (SessionResp1 *) + protobuf_c_message_unpack (&session_resp1__descriptor, + allocator, len, data); +} +void session_resp1__free_unpacked + (SessionResp1 *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &session_resp1__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void session_cmd0__init + (SessionCmd0 *message) +{ + static const SessionCmd0 init_value = SESSION_CMD0__INIT; + *message = init_value; +} +size_t session_cmd0__get_packed_size + (const SessionCmd0 *message) +{ + assert(message->base.descriptor == &session_cmd0__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t session_cmd0__pack + (const SessionCmd0 *message, + uint8_t *out) +{ + assert(message->base.descriptor == &session_cmd0__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t session_cmd0__pack_to_buffer + (const SessionCmd0 *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &session_cmd0__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +SessionCmd0 * + session_cmd0__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (SessionCmd0 *) + protobuf_c_message_unpack (&session_cmd0__descriptor, + allocator, len, data); +} +void session_cmd0__free_unpacked + (SessionCmd0 *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &session_cmd0__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void session_resp0__init + (SessionResp0 *message) +{ + static const SessionResp0 init_value = SESSION_RESP0__INIT; + *message = init_value; +} +size_t session_resp0__get_packed_size + (const SessionResp0 *message) +{ + assert(message->base.descriptor == &session_resp0__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t session_resp0__pack + (const SessionResp0 *message, + uint8_t *out) +{ + assert(message->base.descriptor == &session_resp0__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t session_resp0__pack_to_buffer + (const SessionResp0 *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &session_resp0__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +SessionResp0 * + session_resp0__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (SessionResp0 *) + protobuf_c_message_unpack (&session_resp0__descriptor, + allocator, len, data); +} +void session_resp0__free_unpacked + (SessionResp0 *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &session_resp0__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void sec1_payload__init + (Sec1Payload *message) +{ + static const Sec1Payload init_value = SEC1_PAYLOAD__INIT; + *message = init_value; +} +size_t sec1_payload__get_packed_size + (const Sec1Payload *message) +{ + assert(message->base.descriptor == &sec1_payload__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t sec1_payload__pack + (const Sec1Payload *message, + uint8_t *out) +{ + assert(message->base.descriptor == &sec1_payload__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t sec1_payload__pack_to_buffer + (const Sec1Payload *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &sec1_payload__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +Sec1Payload * + sec1_payload__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (Sec1Payload *) + protobuf_c_message_unpack (&sec1_payload__descriptor, + allocator, len, data); +} +void sec1_payload__free_unpacked + (Sec1Payload *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &sec1_payload__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +static const ProtobufCFieldDescriptor session_cmd1__field_descriptors[1] = +{ + { + "client_verify_data", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(SessionCmd1, client_verify_data), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned session_cmd1__field_indices_by_name[] = { + 0, /* field[0] = client_verify_data */ +}; +static const ProtobufCIntRange session_cmd1__number_ranges[1 + 1] = +{ + { 2, 0 }, + { 0, 1 } +}; +const ProtobufCMessageDescriptor session_cmd1__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "SessionCmd1", + "SessionCmd1", + "SessionCmd1", + "", + sizeof(SessionCmd1), + 1, + session_cmd1__field_descriptors, + session_cmd1__field_indices_by_name, + 1, session_cmd1__number_ranges, + (ProtobufCMessageInit) session_cmd1__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor session_resp1__field_descriptors[2] = +{ + { + "status", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(SessionResp1, status), + &status__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "device_verify_data", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(SessionResp1, device_verify_data), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned session_resp1__field_indices_by_name[] = { + 1, /* field[1] = device_verify_data */ + 0, /* field[0] = status */ +}; +static const ProtobufCIntRange session_resp1__number_ranges[2 + 1] = +{ + { 1, 0 }, + { 3, 1 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor session_resp1__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "SessionResp1", + "SessionResp1", + "SessionResp1", + "", + sizeof(SessionResp1), + 2, + session_resp1__field_descriptors, + session_resp1__field_indices_by_name, + 2, session_resp1__number_ranges, + (ProtobufCMessageInit) session_resp1__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor session_cmd0__field_descriptors[1] = +{ + { + "client_pubkey", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(SessionCmd0, client_pubkey), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned session_cmd0__field_indices_by_name[] = { + 0, /* field[0] = client_pubkey */ +}; +static const ProtobufCIntRange session_cmd0__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 1 } +}; +const ProtobufCMessageDescriptor session_cmd0__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "SessionCmd0", + "SessionCmd0", + "SessionCmd0", + "", + sizeof(SessionCmd0), + 1, + session_cmd0__field_descriptors, + session_cmd0__field_indices_by_name, + 1, session_cmd0__number_ranges, + (ProtobufCMessageInit) session_cmd0__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor session_resp0__field_descriptors[3] = +{ + { + "status", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(SessionResp0, status), + &status__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "device_pubkey", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(SessionResp0, device_pubkey), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "device_random", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(SessionResp0, device_random), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned session_resp0__field_indices_by_name[] = { + 1, /* field[1] = device_pubkey */ + 2, /* field[2] = device_random */ + 0, /* field[0] = status */ +}; +static const ProtobufCIntRange session_resp0__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 3 } +}; +const ProtobufCMessageDescriptor session_resp0__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "SessionResp0", + "SessionResp0", + "SessionResp0", + "", + sizeof(SessionResp0), + 3, + session_resp0__field_descriptors, + session_resp0__field_indices_by_name, + 1, session_resp0__number_ranges, + (ProtobufCMessageInit) session_resp0__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor sec1_payload__field_descriptors[5] = +{ + { + "msg", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(Sec1Payload, msg), + &sec1_msg_type__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "sc0", + 20, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(Sec1Payload, payload_case), + offsetof(Sec1Payload, sc0), + &session_cmd0__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "sr0", + 21, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(Sec1Payload, payload_case), + offsetof(Sec1Payload, sr0), + &session_resp0__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "sc1", + 22, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(Sec1Payload, payload_case), + offsetof(Sec1Payload, sc1), + &session_cmd1__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "sr1", + 23, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(Sec1Payload, payload_case), + offsetof(Sec1Payload, sr1), + &session_resp1__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned sec1_payload__field_indices_by_name[] = { + 0, /* field[0] = msg */ + 1, /* field[1] = sc0 */ + 3, /* field[3] = sc1 */ + 2, /* field[2] = sr0 */ + 4, /* field[4] = sr1 */ +}; +static const ProtobufCIntRange sec1_payload__number_ranges[2 + 1] = +{ + { 1, 0 }, + { 20, 1 }, + { 0, 5 } +}; +const ProtobufCMessageDescriptor sec1_payload__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "Sec1Payload", + "Sec1Payload", + "Sec1Payload", + "", + sizeof(Sec1Payload), + 5, + sec1_payload__field_descriptors, + sec1_payload__field_indices_by_name, + 2, sec1_payload__number_ranges, + (ProtobufCMessageInit) sec1_payload__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCEnumValue sec1_msg_type__enum_values_by_number[4] = +{ + { "Session_Command0", "SEC1_MSG_TYPE__Session_Command0", 0 }, + { "Session_Response0", "SEC1_MSG_TYPE__Session_Response0", 1 }, + { "Session_Command1", "SEC1_MSG_TYPE__Session_Command1", 2 }, + { "Session_Response1", "SEC1_MSG_TYPE__Session_Response1", 3 }, +}; +static const ProtobufCIntRange sec1_msg_type__value_ranges[] = { +{0, 0},{0, 4} +}; +static const ProtobufCEnumValueIndex sec1_msg_type__enum_values_by_name[4] = +{ + { "Session_Command0", 0 }, + { "Session_Command1", 2 }, + { "Session_Response0", 1 }, + { "Session_Response1", 3 }, +}; +const ProtobufCEnumDescriptor sec1_msg_type__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "Sec1MsgType", + "Sec1MsgType", + "Sec1MsgType", + "", + 4, + sec1_msg_type__enum_values_by_number, + 4, + sec1_msg_type__enum_values_by_name, + 1, + sec1_msg_type__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; diff --git a/components/protocomm/proto-c/sec1.pb-c.h b/components/protocomm/proto-c/sec1.pb-c.h new file mode 100644 index 000000000..650be50f8 --- /dev/null +++ b/components/protocomm/proto-c/sec1.pb-c.h @@ -0,0 +1,268 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: sec1.proto */ + +#ifndef PROTOBUF_C_sec1_2eproto__INCLUDED +#define PROTOBUF_C_sec1_2eproto__INCLUDED + +#include + +PROTOBUF_C__BEGIN_DECLS + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. +#elif 1003000 < PROTOBUF_C_MIN_COMPILER_VERSION +# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. +#endif + +#include "constants.pb-c.h" + +typedef struct _SessionCmd1 SessionCmd1; +typedef struct _SessionResp1 SessionResp1; +typedef struct _SessionCmd0 SessionCmd0; +typedef struct _SessionResp0 SessionResp0; +typedef struct _Sec1Payload Sec1Payload; + + +/* --- enums --- */ + +/* + * A message must be of type Cmd0 / Cmd1 / Resp0 / Resp1 + */ +typedef enum _Sec1MsgType { + SEC1_MSG_TYPE__Session_Command0 = 0, + SEC1_MSG_TYPE__Session_Response0 = 1, + SEC1_MSG_TYPE__Session_Command1 = 2, + SEC1_MSG_TYPE__Session_Response1 = 3 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(SEC1_MSG_TYPE) +} Sec1MsgType; + +/* --- messages --- */ + +/* + * Data structure of Session command1 packet + */ +struct _SessionCmd1 +{ + ProtobufCMessage base; + ProtobufCBinaryData client_verify_data; +}; +#define SESSION_CMD1__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&session_cmd1__descriptor) \ + , {0,NULL} } + + +/* + * Data structure of Session response1 packet + */ +struct _SessionResp1 +{ + ProtobufCMessage base; + Status status; + ProtobufCBinaryData device_verify_data; +}; +#define SESSION_RESP1__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&session_resp1__descriptor) \ + , STATUS__Success, {0,NULL} } + + +/* + * Data structure of Session command0 packet + */ +struct _SessionCmd0 +{ + ProtobufCMessage base; + ProtobufCBinaryData client_pubkey; +}; +#define SESSION_CMD0__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&session_cmd0__descriptor) \ + , {0,NULL} } + + +/* + * Data structure of Session response0 packet + */ +struct _SessionResp0 +{ + ProtobufCMessage base; + Status status; + ProtobufCBinaryData device_pubkey; + ProtobufCBinaryData device_random; +}; +#define SESSION_RESP0__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&session_resp0__descriptor) \ + , STATUS__Success, {0,NULL}, {0,NULL} } + + +typedef enum { + SEC1_PAYLOAD__PAYLOAD__NOT_SET = 0, + SEC1_PAYLOAD__PAYLOAD_SC0 = 20, + SEC1_PAYLOAD__PAYLOAD_SR0 = 21, + SEC1_PAYLOAD__PAYLOAD_SC1 = 22, + SEC1_PAYLOAD__PAYLOAD_SR1 = 23 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(SEC1_PAYLOAD__PAYLOAD) +} Sec1Payload__PayloadCase; + +/* + * Payload structure of session data + */ +struct _Sec1Payload +{ + ProtobufCMessage base; + /* + *!< Type of message + */ + Sec1MsgType msg; + Sec1Payload__PayloadCase payload_case; + union { + /* + *!< Payload data interpreted as Cmd0 + */ + SessionCmd0 *sc0; + /* + *!< Payload data interpreted as Resp0 + */ + SessionResp0 *sr0; + /* + *!< Payload data interpreted as Cmd1 + */ + SessionCmd1 *sc1; + /* + *!< Payload data interpreted as Resp1 + */ + SessionResp1 *sr1; + }; +}; +#define SEC1_PAYLOAD__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&sec1_payload__descriptor) \ + , SEC1_MSG_TYPE__Session_Command0, SEC1_PAYLOAD__PAYLOAD__NOT_SET, {0} } + + +/* SessionCmd1 methods */ +void session_cmd1__init + (SessionCmd1 *message); +size_t session_cmd1__get_packed_size + (const SessionCmd1 *message); +size_t session_cmd1__pack + (const SessionCmd1 *message, + uint8_t *out); +size_t session_cmd1__pack_to_buffer + (const SessionCmd1 *message, + ProtobufCBuffer *buffer); +SessionCmd1 * + session_cmd1__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void session_cmd1__free_unpacked + (SessionCmd1 *message, + ProtobufCAllocator *allocator); +/* SessionResp1 methods */ +void session_resp1__init + (SessionResp1 *message); +size_t session_resp1__get_packed_size + (const SessionResp1 *message); +size_t session_resp1__pack + (const SessionResp1 *message, + uint8_t *out); +size_t session_resp1__pack_to_buffer + (const SessionResp1 *message, + ProtobufCBuffer *buffer); +SessionResp1 * + session_resp1__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void session_resp1__free_unpacked + (SessionResp1 *message, + ProtobufCAllocator *allocator); +/* SessionCmd0 methods */ +void session_cmd0__init + (SessionCmd0 *message); +size_t session_cmd0__get_packed_size + (const SessionCmd0 *message); +size_t session_cmd0__pack + (const SessionCmd0 *message, + uint8_t *out); +size_t session_cmd0__pack_to_buffer + (const SessionCmd0 *message, + ProtobufCBuffer *buffer); +SessionCmd0 * + session_cmd0__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void session_cmd0__free_unpacked + (SessionCmd0 *message, + ProtobufCAllocator *allocator); +/* SessionResp0 methods */ +void session_resp0__init + (SessionResp0 *message); +size_t session_resp0__get_packed_size + (const SessionResp0 *message); +size_t session_resp0__pack + (const SessionResp0 *message, + uint8_t *out); +size_t session_resp0__pack_to_buffer + (const SessionResp0 *message, + ProtobufCBuffer *buffer); +SessionResp0 * + session_resp0__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void session_resp0__free_unpacked + (SessionResp0 *message, + ProtobufCAllocator *allocator); +/* Sec1Payload methods */ +void sec1_payload__init + (Sec1Payload *message); +size_t sec1_payload__get_packed_size + (const Sec1Payload *message); +size_t sec1_payload__pack + (const Sec1Payload *message, + uint8_t *out); +size_t sec1_payload__pack_to_buffer + (const Sec1Payload *message, + ProtobufCBuffer *buffer); +Sec1Payload * + sec1_payload__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void sec1_payload__free_unpacked + (Sec1Payload *message, + ProtobufCAllocator *allocator); +/* --- per-message closures --- */ + +typedef void (*SessionCmd1_Closure) + (const SessionCmd1 *message, + void *closure_data); +typedef void (*SessionResp1_Closure) + (const SessionResp1 *message, + void *closure_data); +typedef void (*SessionCmd0_Closure) + (const SessionCmd0 *message, + void *closure_data); +typedef void (*SessionResp0_Closure) + (const SessionResp0 *message, + void *closure_data); +typedef void (*Sec1Payload_Closure) + (const Sec1Payload *message, + void *closure_data); + +/* --- services --- */ + + +/* --- descriptors --- */ + +extern const ProtobufCEnumDescriptor sec1_msg_type__descriptor; +extern const ProtobufCMessageDescriptor session_cmd1__descriptor; +extern const ProtobufCMessageDescriptor session_resp1__descriptor; +extern const ProtobufCMessageDescriptor session_cmd0__descriptor; +extern const ProtobufCMessageDescriptor session_resp0__descriptor; +extern const ProtobufCMessageDescriptor sec1_payload__descriptor; + +PROTOBUF_C__END_DECLS + + +#endif /* PROTOBUF_C_sec1_2eproto__INCLUDED */ diff --git a/components/protocomm/proto-c/session.pb-c.c b/components/protocomm/proto-c/session.pb-c.c new file mode 100644 index 000000000..59e8447b4 --- /dev/null +++ b/components/protocomm/proto-c/session.pb-c.c @@ -0,0 +1,147 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: session.proto */ + +/* Do not generate deprecated warnings for self */ +#ifndef PROTOBUF_C__NO_DEPRECATED +#define PROTOBUF_C__NO_DEPRECATED +#endif + +#include "session.pb-c.h" +void session_data__init + (SessionData *message) +{ + static const SessionData init_value = SESSION_DATA__INIT; + *message = init_value; +} +size_t session_data__get_packed_size + (const SessionData *message) +{ + assert(message->base.descriptor == &session_data__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t session_data__pack + (const SessionData *message, + uint8_t *out) +{ + assert(message->base.descriptor == &session_data__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t session_data__pack_to_buffer + (const SessionData *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &session_data__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +SessionData * + session_data__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (SessionData *) + protobuf_c_message_unpack (&session_data__descriptor, + allocator, len, data); +} +void session_data__free_unpacked + (SessionData *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &session_data__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +static const ProtobufCFieldDescriptor session_data__field_descriptors[3] = +{ + { + "sec_ver", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(SessionData, sec_ver), + &sec_scheme_version__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "sec0", + 10, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(SessionData, proto_case), + offsetof(SessionData, sec0), + &sec0_payload__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "sec1", + 11, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(SessionData, proto_case), + offsetof(SessionData, sec1), + &sec1_payload__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned session_data__field_indices_by_name[] = { + 1, /* field[1] = sec0 */ + 2, /* field[2] = sec1 */ + 0, /* field[0] = sec_ver */ +}; +static const ProtobufCIntRange session_data__number_ranges[2 + 1] = +{ + { 2, 0 }, + { 10, 1 }, + { 0, 3 } +}; +const ProtobufCMessageDescriptor session_data__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "SessionData", + "SessionData", + "SessionData", + "", + sizeof(SessionData), + 3, + session_data__field_descriptors, + session_data__field_indices_by_name, + 2, session_data__number_ranges, + (ProtobufCMessageInit) session_data__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCEnumValue sec_scheme_version__enum_values_by_number[2] = +{ + { "SecScheme0", "SEC_SCHEME_VERSION__SecScheme0", 0 }, + { "SecScheme1", "SEC_SCHEME_VERSION__SecScheme1", 1 }, +}; +static const ProtobufCIntRange sec_scheme_version__value_ranges[] = { +{0, 0},{0, 2} +}; +static const ProtobufCEnumValueIndex sec_scheme_version__enum_values_by_name[2] = +{ + { "SecScheme0", 0 }, + { "SecScheme1", 1 }, +}; +const ProtobufCEnumDescriptor sec_scheme_version__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "SecSchemeVersion", + "SecSchemeVersion", + "SecSchemeVersion", + "", + 2, + sec_scheme_version__enum_values_by_number, + 2, + sec_scheme_version__enum_values_by_name, + 1, + sec_scheme_version__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; diff --git a/components/protocomm/proto-c/session.pb-c.h b/components/protocomm/proto-c/session.pb-c.h new file mode 100644 index 000000000..50bded1a2 --- /dev/null +++ b/components/protocomm/proto-c/session.pb-c.h @@ -0,0 +1,114 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: session.proto */ + +#ifndef PROTOBUF_C_session_2eproto__INCLUDED +#define PROTOBUF_C_session_2eproto__INCLUDED + +#include + +PROTOBUF_C__BEGIN_DECLS + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. +#elif 1003000 < PROTOBUF_C_MIN_COMPILER_VERSION +# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. +#endif + +#include "sec0.pb-c.h" +#include "sec1.pb-c.h" + +typedef struct _SessionData SessionData; + + +/* --- enums --- */ + +/* + * Allowed values for the type of security + * being used in a protocomm session + */ +typedef enum _SecSchemeVersion { + /* + *!< Unsecured - plaintext communication + */ + SEC_SCHEME_VERSION__SecScheme0 = 0, + /* + *!< Security scheme 1 - Curve25519 + AES-256-CTR + */ + SEC_SCHEME_VERSION__SecScheme1 = 1 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(SEC_SCHEME_VERSION) +} SecSchemeVersion; + +/* --- messages --- */ + +typedef enum { + SESSION_DATA__PROTO__NOT_SET = 0, + SESSION_DATA__PROTO_SEC0 = 10, + SESSION_DATA__PROTO_SEC1 = 11 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(SESSION_DATA__PROTO) +} SessionData__ProtoCase; + +/* + * Data structure exchanged when establishing + * secure session between Host and Client + */ +struct _SessionData +{ + ProtobufCMessage base; + /* + *!< Type of security + */ + SecSchemeVersion sec_ver; + SessionData__ProtoCase proto_case; + union { + /* + *!< Payload data in case of security 0 + */ + Sec0Payload *sec0; + /* + *!< Payload data in case of security 1 + */ + Sec1Payload *sec1; + }; +}; +#define SESSION_DATA__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&session_data__descriptor) \ + , SEC_SCHEME_VERSION__SecScheme0, SESSION_DATA__PROTO__NOT_SET, {0} } + + +/* SessionData methods */ +void session_data__init + (SessionData *message); +size_t session_data__get_packed_size + (const SessionData *message); +size_t session_data__pack + (const SessionData *message, + uint8_t *out); +size_t session_data__pack_to_buffer + (const SessionData *message, + ProtobufCBuffer *buffer); +SessionData * + session_data__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void session_data__free_unpacked + (SessionData *message, + ProtobufCAllocator *allocator); +/* --- per-message closures --- */ + +typedef void (*SessionData_Closure) + (const SessionData *message, + void *closure_data); + +/* --- services --- */ + + +/* --- descriptors --- */ + +extern const ProtobufCEnumDescriptor sec_scheme_version__descriptor; +extern const ProtobufCMessageDescriptor session_data__descriptor; + +PROTOBUF_C__END_DECLS + + +#endif /* PROTOBUF_C_session_2eproto__INCLUDED */ diff --git a/components/protocomm/python/constants_pb2.py b/components/protocomm/python/constants_pb2.py new file mode 100644 index 000000000..ed5a47f2d --- /dev/null +++ b/components/protocomm/python/constants_pb2.py @@ -0,0 +1,87 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: constants.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='constants.proto', + package='', + syntax='proto3', + serialized_pb=_b('\n\x0f\x63onstants.proto*\x9f\x01\n\x06Status\x12\x0b\n\x07Success\x10\x00\x12\x14\n\x10InvalidSecScheme\x10\x01\x12\x10\n\x0cInvalidProto\x10\x02\x12\x13\n\x0fTooManySessions\x10\x03\x12\x13\n\x0fInvalidArgument\x10\x04\x12\x11\n\rInternalError\x10\x05\x12\x0f\n\x0b\x43ryptoError\x10\x06\x12\x12\n\x0eInvalidSession\x10\x07\x62\x06proto3') +) + +_STATUS = _descriptor.EnumDescriptor( + name='Status', + full_name='Status', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='Success', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='InvalidSecScheme', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='InvalidProto', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TooManySessions', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='InvalidArgument', index=4, number=4, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='InternalError', index=5, number=5, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='CryptoError', index=6, number=6, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='InvalidSession', index=7, number=7, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=20, + serialized_end=179, +) +_sym_db.RegisterEnumDescriptor(_STATUS) + +Status = enum_type_wrapper.EnumTypeWrapper(_STATUS) +Success = 0 +InvalidSecScheme = 1 +InvalidProto = 2 +TooManySessions = 3 +InvalidArgument = 4 +InternalError = 5 +CryptoError = 6 +InvalidSession = 7 + + +DESCRIPTOR.enum_types_by_name['Status'] = _STATUS +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + +# @@protoc_insertion_point(module_scope) diff --git a/components/protocomm/python/sec0_pb2.py b/components/protocomm/python/sec0_pb2.py new file mode 100644 index 000000000..123d1dc2f --- /dev/null +++ b/components/protocomm/python/sec0_pb2.py @@ -0,0 +1,196 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: sec0.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +import constants_pb2 as constants__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='sec0.proto', + package='', + syntax='proto3', + serialized_pb=_b('\n\nsec0.proto\x1a\x0f\x63onstants.proto\"\x0e\n\x0cS0SessionCmd\"(\n\rS0SessionResp\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"n\n\x0bSec0Payload\x12\x19\n\x03msg\x18\x01 \x01(\x0e\x32\x0c.Sec0MsgType\x12\x1b\n\x02sc\x18\x14 \x01(\x0b\x32\r.S0SessionCmdH\x00\x12\x1c\n\x02sr\x18\x15 \x01(\x0b\x32\x0e.S0SessionRespH\x00\x42\t\n\x07payload*>\n\x0bSec0MsgType\x12\x16\n\x12S0_Session_Command\x10\x00\x12\x17\n\x13S0_Session_Response\x10\x01\x62\x06proto3') + , + dependencies=[constants__pb2.DESCRIPTOR,]) + +_SEC0MSGTYPE = _descriptor.EnumDescriptor( + name='Sec0MsgType', + full_name='Sec0MsgType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='S0_Session_Command', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='S0_Session_Response', index=1, number=1, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=201, + serialized_end=263, +) +_sym_db.RegisterEnumDescriptor(_SEC0MSGTYPE) + +Sec0MsgType = enum_type_wrapper.EnumTypeWrapper(_SEC0MSGTYPE) +S0_Session_Command = 0 +S0_Session_Response = 1 + + + +_S0SESSIONCMD = _descriptor.Descriptor( + name='S0SessionCmd', + full_name='S0SessionCmd', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=31, + serialized_end=45, +) + + +_S0SESSIONRESP = _descriptor.Descriptor( + name='S0SessionResp', + full_name='S0SessionResp', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='S0SessionResp.status', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=47, + serialized_end=87, +) + + +_SEC0PAYLOAD = _descriptor.Descriptor( + name='Sec0Payload', + full_name='Sec0Payload', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='msg', full_name='Sec0Payload.msg', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sc', full_name='Sec0Payload.sc', index=1, + number=20, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sr', full_name='Sec0Payload.sr', index=2, + number=21, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='payload', full_name='Sec0Payload.payload', + index=0, containing_type=None, fields=[]), + ], + serialized_start=89, + serialized_end=199, +) + +_S0SESSIONRESP.fields_by_name['status'].enum_type = constants__pb2._STATUS +_SEC0PAYLOAD.fields_by_name['msg'].enum_type = _SEC0MSGTYPE +_SEC0PAYLOAD.fields_by_name['sc'].message_type = _S0SESSIONCMD +_SEC0PAYLOAD.fields_by_name['sr'].message_type = _S0SESSIONRESP +_SEC0PAYLOAD.oneofs_by_name['payload'].fields.append( + _SEC0PAYLOAD.fields_by_name['sc']) +_SEC0PAYLOAD.fields_by_name['sc'].containing_oneof = _SEC0PAYLOAD.oneofs_by_name['payload'] +_SEC0PAYLOAD.oneofs_by_name['payload'].fields.append( + _SEC0PAYLOAD.fields_by_name['sr']) +_SEC0PAYLOAD.fields_by_name['sr'].containing_oneof = _SEC0PAYLOAD.oneofs_by_name['payload'] +DESCRIPTOR.message_types_by_name['S0SessionCmd'] = _S0SESSIONCMD +DESCRIPTOR.message_types_by_name['S0SessionResp'] = _S0SESSIONRESP +DESCRIPTOR.message_types_by_name['Sec0Payload'] = _SEC0PAYLOAD +DESCRIPTOR.enum_types_by_name['Sec0MsgType'] = _SEC0MSGTYPE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +S0SessionCmd = _reflection.GeneratedProtocolMessageType('S0SessionCmd', (_message.Message,), dict( + DESCRIPTOR = _S0SESSIONCMD, + __module__ = 'sec0_pb2' + # @@protoc_insertion_point(class_scope:S0SessionCmd) + )) +_sym_db.RegisterMessage(S0SessionCmd) + +S0SessionResp = _reflection.GeneratedProtocolMessageType('S0SessionResp', (_message.Message,), dict( + DESCRIPTOR = _S0SESSIONRESP, + __module__ = 'sec0_pb2' + # @@protoc_insertion_point(class_scope:S0SessionResp) + )) +_sym_db.RegisterMessage(S0SessionResp) + +Sec0Payload = _reflection.GeneratedProtocolMessageType('Sec0Payload', (_message.Message,), dict( + DESCRIPTOR = _SEC0PAYLOAD, + __module__ = 'sec0_pb2' + # @@protoc_insertion_point(class_scope:Sec0Payload) + )) +_sym_db.RegisterMessage(Sec0Payload) + + +# @@protoc_insertion_point(module_scope) diff --git a/components/protocomm/python/sec1_pb2.py b/components/protocomm/python/sec1_pb2.py new file mode 100644 index 000000000..cc305b8a1 --- /dev/null +++ b/components/protocomm/python/sec1_pb2.py @@ -0,0 +1,335 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: sec1.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +import constants_pb2 as constants__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='sec1.proto', + package='', + syntax='proto3', + serialized_pb=_b('\n\nsec1.proto\x1a\x0f\x63onstants.proto\")\n\x0bSessionCmd1\x12\x1a\n\x12\x63lient_verify_data\x18\x02 \x01(\x0c\"C\n\x0cSessionResp1\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12\x1a\n\x12\x64\x65vice_verify_data\x18\x03 \x01(\x0c\"$\n\x0bSessionCmd0\x12\x15\n\rclient_pubkey\x18\x01 \x01(\x0c\"U\n\x0cSessionResp0\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12\x15\n\rdevice_pubkey\x18\x02 \x01(\x0c\x12\x15\n\rdevice_random\x18\x03 \x01(\x0c\"\xa9\x01\n\x0bSec1Payload\x12\x19\n\x03msg\x18\x01 \x01(\x0e\x32\x0c.Sec1MsgType\x12\x1b\n\x03sc0\x18\x14 \x01(\x0b\x32\x0c.SessionCmd0H\x00\x12\x1c\n\x03sr0\x18\x15 \x01(\x0b\x32\r.SessionResp0H\x00\x12\x1b\n\x03sc1\x18\x16 \x01(\x0b\x32\x0c.SessionCmd1H\x00\x12\x1c\n\x03sr1\x18\x17 \x01(\x0b\x32\r.SessionResp1H\x00\x42\t\n\x07payload*g\n\x0bSec1MsgType\x12\x14\n\x10Session_Command0\x10\x00\x12\x15\n\x11Session_Response0\x10\x01\x12\x14\n\x10Session_Command1\x10\x02\x12\x15\n\x11Session_Response1\x10\x03\x62\x06proto3') + , + dependencies=[constants__pb2.DESCRIPTOR,]) + +_SEC1MSGTYPE = _descriptor.EnumDescriptor( + name='Sec1MsgType', + full_name='Sec1MsgType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='Session_Command0', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='Session_Response0', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='Session_Command1', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='Session_Response1', index=3, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=440, + serialized_end=543, +) +_sym_db.RegisterEnumDescriptor(_SEC1MSGTYPE) + +Sec1MsgType = enum_type_wrapper.EnumTypeWrapper(_SEC1MSGTYPE) +Session_Command0 = 0 +Session_Response0 = 1 +Session_Command1 = 2 +Session_Response1 = 3 + + + +_SESSIONCMD1 = _descriptor.Descriptor( + name='SessionCmd1', + full_name='SessionCmd1', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='client_verify_data', full_name='SessionCmd1.client_verify_data', index=0, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=31, + serialized_end=72, +) + + +_SESSIONRESP1 = _descriptor.Descriptor( + name='SessionResp1', + full_name='SessionResp1', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='SessionResp1.status', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='device_verify_data', full_name='SessionResp1.device_verify_data', index=1, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=74, + serialized_end=141, +) + + +_SESSIONCMD0 = _descriptor.Descriptor( + name='SessionCmd0', + full_name='SessionCmd0', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='client_pubkey', full_name='SessionCmd0.client_pubkey', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=143, + serialized_end=179, +) + + +_SESSIONRESP0 = _descriptor.Descriptor( + name='SessionResp0', + full_name='SessionResp0', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='SessionResp0.status', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='device_pubkey', full_name='SessionResp0.device_pubkey', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='device_random', full_name='SessionResp0.device_random', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=181, + serialized_end=266, +) + + +_SEC1PAYLOAD = _descriptor.Descriptor( + name='Sec1Payload', + full_name='Sec1Payload', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='msg', full_name='Sec1Payload.msg', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sc0', full_name='Sec1Payload.sc0', index=1, + number=20, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sr0', full_name='Sec1Payload.sr0', index=2, + number=21, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sc1', full_name='Sec1Payload.sc1', index=3, + number=22, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sr1', full_name='Sec1Payload.sr1', index=4, + number=23, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='payload', full_name='Sec1Payload.payload', + index=0, containing_type=None, fields=[]), + ], + serialized_start=269, + serialized_end=438, +) + +_SESSIONRESP1.fields_by_name['status'].enum_type = constants__pb2._STATUS +_SESSIONRESP0.fields_by_name['status'].enum_type = constants__pb2._STATUS +_SEC1PAYLOAD.fields_by_name['msg'].enum_type = _SEC1MSGTYPE +_SEC1PAYLOAD.fields_by_name['sc0'].message_type = _SESSIONCMD0 +_SEC1PAYLOAD.fields_by_name['sr0'].message_type = _SESSIONRESP0 +_SEC1PAYLOAD.fields_by_name['sc1'].message_type = _SESSIONCMD1 +_SEC1PAYLOAD.fields_by_name['sr1'].message_type = _SESSIONRESP1 +_SEC1PAYLOAD.oneofs_by_name['payload'].fields.append( + _SEC1PAYLOAD.fields_by_name['sc0']) +_SEC1PAYLOAD.fields_by_name['sc0'].containing_oneof = _SEC1PAYLOAD.oneofs_by_name['payload'] +_SEC1PAYLOAD.oneofs_by_name['payload'].fields.append( + _SEC1PAYLOAD.fields_by_name['sr0']) +_SEC1PAYLOAD.fields_by_name['sr0'].containing_oneof = _SEC1PAYLOAD.oneofs_by_name['payload'] +_SEC1PAYLOAD.oneofs_by_name['payload'].fields.append( + _SEC1PAYLOAD.fields_by_name['sc1']) +_SEC1PAYLOAD.fields_by_name['sc1'].containing_oneof = _SEC1PAYLOAD.oneofs_by_name['payload'] +_SEC1PAYLOAD.oneofs_by_name['payload'].fields.append( + _SEC1PAYLOAD.fields_by_name['sr1']) +_SEC1PAYLOAD.fields_by_name['sr1'].containing_oneof = _SEC1PAYLOAD.oneofs_by_name['payload'] +DESCRIPTOR.message_types_by_name['SessionCmd1'] = _SESSIONCMD1 +DESCRIPTOR.message_types_by_name['SessionResp1'] = _SESSIONRESP1 +DESCRIPTOR.message_types_by_name['SessionCmd0'] = _SESSIONCMD0 +DESCRIPTOR.message_types_by_name['SessionResp0'] = _SESSIONRESP0 +DESCRIPTOR.message_types_by_name['Sec1Payload'] = _SEC1PAYLOAD +DESCRIPTOR.enum_types_by_name['Sec1MsgType'] = _SEC1MSGTYPE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +SessionCmd1 = _reflection.GeneratedProtocolMessageType('SessionCmd1', (_message.Message,), dict( + DESCRIPTOR = _SESSIONCMD1, + __module__ = 'sec1_pb2' + # @@protoc_insertion_point(class_scope:SessionCmd1) + )) +_sym_db.RegisterMessage(SessionCmd1) + +SessionResp1 = _reflection.GeneratedProtocolMessageType('SessionResp1', (_message.Message,), dict( + DESCRIPTOR = _SESSIONRESP1, + __module__ = 'sec1_pb2' + # @@protoc_insertion_point(class_scope:SessionResp1) + )) +_sym_db.RegisterMessage(SessionResp1) + +SessionCmd0 = _reflection.GeneratedProtocolMessageType('SessionCmd0', (_message.Message,), dict( + DESCRIPTOR = _SESSIONCMD0, + __module__ = 'sec1_pb2' + # @@protoc_insertion_point(class_scope:SessionCmd0) + )) +_sym_db.RegisterMessage(SessionCmd0) + +SessionResp0 = _reflection.GeneratedProtocolMessageType('SessionResp0', (_message.Message,), dict( + DESCRIPTOR = _SESSIONRESP0, + __module__ = 'sec1_pb2' + # @@protoc_insertion_point(class_scope:SessionResp0) + )) +_sym_db.RegisterMessage(SessionResp0) + +Sec1Payload = _reflection.GeneratedProtocolMessageType('Sec1Payload', (_message.Message,), dict( + DESCRIPTOR = _SEC1PAYLOAD, + __module__ = 'sec1_pb2' + # @@protoc_insertion_point(class_scope:Sec1Payload) + )) +_sym_db.RegisterMessage(Sec1Payload) + + +# @@protoc_insertion_point(module_scope) diff --git a/components/protocomm/python/session_pb2.py b/components/protocomm/python/session_pb2.py new file mode 100644 index 000000000..a30e794c7 --- /dev/null +++ b/components/protocomm/python/session_pb2.py @@ -0,0 +1,125 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: session.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +import sec0_pb2 as sec0__pb2 +import sec1_pb2 as sec1__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='session.proto', + package='', + syntax='proto3', + serialized_pb=_b('\n\rsession.proto\x1a\nsec0.proto\x1a\nsec1.proto\"v\n\x0bSessionData\x12\"\n\x07sec_ver\x18\x02 \x01(\x0e\x32\x11.SecSchemeVersion\x12\x1c\n\x04sec0\x18\n \x01(\x0b\x32\x0c.Sec0PayloadH\x00\x12\x1c\n\x04sec1\x18\x0b \x01(\x0b\x32\x0c.Sec1PayloadH\x00\x42\x07\n\x05proto*2\n\x10SecSchemeVersion\x12\x0e\n\nSecScheme0\x10\x00\x12\x0e\n\nSecScheme1\x10\x01\x62\x06proto3') + , + dependencies=[sec0__pb2.DESCRIPTOR,sec1__pb2.DESCRIPTOR,]) + +_SECSCHEMEVERSION = _descriptor.EnumDescriptor( + name='SecSchemeVersion', + full_name='SecSchemeVersion', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='SecScheme0', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SecScheme1', index=1, number=1, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=161, + serialized_end=211, +) +_sym_db.RegisterEnumDescriptor(_SECSCHEMEVERSION) + +SecSchemeVersion = enum_type_wrapper.EnumTypeWrapper(_SECSCHEMEVERSION) +SecScheme0 = 0 +SecScheme1 = 1 + + + +_SESSIONDATA = _descriptor.Descriptor( + name='SessionData', + full_name='SessionData', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='sec_ver', full_name='SessionData.sec_ver', index=0, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sec0', full_name='SessionData.sec0', index=1, + number=10, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sec1', full_name='SessionData.sec1', index=2, + number=11, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='proto', full_name='SessionData.proto', + index=0, containing_type=None, fields=[]), + ], + serialized_start=41, + serialized_end=159, +) + +_SESSIONDATA.fields_by_name['sec_ver'].enum_type = _SECSCHEMEVERSION +_SESSIONDATA.fields_by_name['sec0'].message_type = sec0__pb2._SEC0PAYLOAD +_SESSIONDATA.fields_by_name['sec1'].message_type = sec1__pb2._SEC1PAYLOAD +_SESSIONDATA.oneofs_by_name['proto'].fields.append( + _SESSIONDATA.fields_by_name['sec0']) +_SESSIONDATA.fields_by_name['sec0'].containing_oneof = _SESSIONDATA.oneofs_by_name['proto'] +_SESSIONDATA.oneofs_by_name['proto'].fields.append( + _SESSIONDATA.fields_by_name['sec1']) +_SESSIONDATA.fields_by_name['sec1'].containing_oneof = _SESSIONDATA.oneofs_by_name['proto'] +DESCRIPTOR.message_types_by_name['SessionData'] = _SESSIONDATA +DESCRIPTOR.enum_types_by_name['SecSchemeVersion'] = _SECSCHEMEVERSION +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +SessionData = _reflection.GeneratedProtocolMessageType('SessionData', (_message.Message,), dict( + DESCRIPTOR = _SESSIONDATA, + __module__ = 'session_pb2' + # @@protoc_insertion_point(class_scope:SessionData) + )) +_sym_db.RegisterMessage(SessionData) + + +# @@protoc_insertion_point(module_scope) diff --git a/components/protocomm/test/component.mk b/components/protocomm/test/component.mk new file mode 100644 index 000000000..7a27118a0 --- /dev/null +++ b/components/protocomm/test/component.mk @@ -0,0 +1,2 @@ +COMPONENT_PRIV_INCLUDEDIRS := ../proto-c/ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/protocomm/test/test_protocomm.c b/components/protocomm/test/test_protocomm.c new file mode 100644 index 000000000..cc8500649 --- /dev/null +++ b/components/protocomm/test/test_protocomm.c @@ -0,0 +1,1089 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "session.pb-c.h" + +#ifdef DO_HEAP_TRACING + #include + #define NUM_RECORDS 100 + static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM +#endif + +#define PUBLIC_KEY_LEN 32 +#define SZ_RANDOM 16 + +typedef struct { + uint32_t id; + uint8_t sec_ver; + uint8_t weak; + const protocomm_security_pop_t *pop; + uint8_t device_pubkey[PUBLIC_KEY_LEN]; + uint8_t client_pubkey[PUBLIC_KEY_LEN]; + uint8_t sym_key[PUBLIC_KEY_LEN]; + uint8_t rand[SZ_RANDOM]; + + /* mbedtls context data for Curve25519 */ + mbedtls_ecdh_context ctx_client; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + + /* mbedtls context data for AES */ + mbedtls_aes_context ctx_aes; + unsigned char stb[16]; + size_t nc_off; +} session_t; + +static const char *TAG = "protocomm_test"; + +static protocomm_t *test_pc = NULL; +static const protocomm_security_t *test_sec = NULL; +static uint32_t test_priv_data = 1234; + +static void flip_endian(uint8_t *data, size_t len) +{ + uint8_t swp_buf; + for (int i = 0; i < len/2; i++) { + swp_buf = data[i]; + data[i] = data[len - i - 1]; + data[len - i - 1] = swp_buf; + } +} + +static void hexdump(const char *msg, uint8_t *buf, int len) +{ + ESP_LOGI(TAG, "%s:", msg); + ESP_LOG_BUFFER_HEX(TAG, buf, len); +} + +static esp_err_t prepare_command0(session_t *session, SessionData *req) +{ + Sec1Payload *in = (Sec1Payload *) malloc(sizeof(Sec1Payload)); + if (in == NULL) { + ESP_LOGE(TAG, "Error allocating memory for request"); + return ESP_ERR_NO_MEM; + } + + SessionCmd0 *in_req = (SessionCmd0 *) malloc(sizeof(SessionCmd0)); + if (in_req == NULL) { + ESP_LOGE(TAG, "Error allocating memory for request"); + free(in); + return ESP_ERR_NO_MEM; + } + + sec1_payload__init(in); + session_cmd0__init(in_req); + + in_req->client_pubkey.data = session->client_pubkey; + in_req->client_pubkey.len = PUBLIC_KEY_LEN; + + in->msg = SEC1_MSG_TYPE__Session_Command0; + in->payload_case = SEC1_PAYLOAD__PAYLOAD_SC0; + in->sc0 = in_req; + + req->proto_case = SESSION_DATA__PROTO_SEC1; + req->sec_ver = protocomm_security1.ver; + req->sec1 = in; + + return ESP_OK; +} + +static void cleanup_command0(SessionData *req) +{ + free(req->sec1->sc0); + free(req->sec1); +} + +static esp_err_t verify_response0(session_t *session, SessionData *resp) +{ + if ((resp->proto_case != SESSION_DATA__PROTO_SEC1) || + (resp->sec1->msg != SEC1_MSG_TYPE__Session_Response0)) { + ESP_LOGE(TAG, "Invalid response type"); + return ESP_ERR_INVALID_ARG; + } + + int ret; + Sec1Payload *in = (Sec1Payload *) resp->sec1; + + if (in->sr0->device_pubkey.len != PUBLIC_KEY_LEN) { + ESP_LOGE(TAG, "Device public key length as not as expected"); + return ESP_FAIL; + } + + if (in->sr0->device_random.len != SZ_RANDOM) { + ESP_LOGE(TAG, "Device random data length is not as expected"); + return ESP_FAIL; + } + + uint8_t *cli_pubkey = session->client_pubkey; + uint8_t *dev_pubkey = session->device_pubkey; + memcpy(session->device_pubkey, in->sr0->device_pubkey.data, in->sr0->device_pubkey.len); + + hexdump("Device pubkey", dev_pubkey, PUBLIC_KEY_LEN); + hexdump("Client pubkey", cli_pubkey, PUBLIC_KEY_LEN); + + ret = mbedtls_mpi_lset(&session->ctx_client.Qp.Z, 1); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_lset with error code : %d", ret); + return ESP_FAIL; + } + + flip_endian(session->device_pubkey, PUBLIC_KEY_LEN); + ret = mbedtls_mpi_read_binary(&session->ctx_client.Qp.X, dev_pubkey, PUBLIC_KEY_LEN); + flip_endian(session->device_pubkey, PUBLIC_KEY_LEN); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_read_binary with error code : %d", ret); + return ESP_FAIL; + } + + ret = mbedtls_ecdh_compute_shared(&session->ctx_client.grp, + &session->ctx_client.z, + &session->ctx_client.Qp, + &session->ctx_client.d, + mbedtls_ctr_drbg_random, + &session->ctr_drbg); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ecdh_compute_shared with error code : %d", ret); + return ESP_FAIL; + } + + ret = mbedtls_mpi_write_binary(&session->ctx_client.z, session->sym_key, PUBLIC_KEY_LEN); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_write_binary with error code : %d", ret); + return ESP_FAIL; + } + flip_endian(session->sym_key, PUBLIC_KEY_LEN); + + const protocomm_security_pop_t *pop = session->pop; + if (pop != NULL && pop->data != NULL && pop->len != 0) { + ESP_LOGD(TAG, "Adding proof of possession"); + uint8_t sha_out[PUBLIC_KEY_LEN]; + + ret = mbedtls_sha256_ret((const unsigned char *) pop->data, pop->len, sha_out, 0); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_sha256_ret with error code : %d", ret); + return ESP_FAIL; + } + + for (int i = 0; i < PUBLIC_KEY_LEN; i++) { + session->sym_key[i] ^= sha_out[i]; + } + } + + hexdump("Shared key", session->sym_key, PUBLIC_KEY_LEN); + + memcpy(session->rand, in->sr0->device_random.data, in->sr0->device_random.len); + hexdump("Dev random", session->rand, sizeof(session->rand)); + return ESP_OK; +} + +static esp_err_t prepare_command1(session_t *session, SessionData *req) +{ + int ret; + uint8_t *outbuf = (uint8_t *) malloc(PUBLIC_KEY_LEN); + if (!outbuf) { + ESP_LOGE(TAG, "Error allocating ciphertext buffer"); + return ESP_ERR_NO_MEM; + } + + /* Initialise crypto context */ + mbedtls_aes_init(&session->ctx_aes); + memset(session->stb, 0, sizeof(session->stb)); + session->nc_off = 0; + + ret = mbedtls_aes_setkey_enc(&session->ctx_aes, session->sym_key, + sizeof(session->sym_key)*8); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_aes_setkey_enc with erro code : %d", ret); + free(outbuf); + return ESP_FAIL; + } + + ret = mbedtls_aes_crypt_ctr(&session->ctx_aes, PUBLIC_KEY_LEN, + &session->nc_off, session->rand, + session->stb, session->device_pubkey, outbuf); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_aes_crypt_ctr with erro code : %d", ret); + free(outbuf); + return ESP_FAIL; + } + + Sec1Payload *out = (Sec1Payload *) malloc(sizeof(Sec1Payload)); + if (!out) { + ESP_LOGE(TAG, "Error allocating out buffer"); + free(outbuf); + return ESP_ERR_NO_MEM; + } + sec1_payload__init(out); + + SessionCmd1 *out_req = (SessionCmd1 *) malloc(sizeof(SessionCmd1)); + if (!out_req) { + ESP_LOGE(TAG, "Error allocating out_req buffer"); + free(outbuf); + free(out); + return ESP_ERR_NO_MEM; + } + session_cmd1__init(out_req); + + out_req->client_verify_data.data = outbuf; + out_req->client_verify_data.len = PUBLIC_KEY_LEN; + hexdump("Client verify data", outbuf, PUBLIC_KEY_LEN); + + out->msg = SEC1_MSG_TYPE__Session_Command1; + out->payload_case = SEC1_PAYLOAD__PAYLOAD_SC1; + out->sc1 = out_req; + + req->proto_case = SESSION_DATA__PROTO_SEC1; + req->sec_ver = protocomm_security1.ver; + req->sec1 = out; + + return ESP_OK; +} + +static void cleanup_command1(SessionData *req) +{ + free(req->sec1->sc1->client_verify_data.data); + free(req->sec1->sc1); + free(req->sec1); +} + +static esp_err_t verify_response1(session_t *session, SessionData *resp) +{ + uint8_t *cli_pubkey = session->client_pubkey; + uint8_t *dev_pubkey = session->device_pubkey; + + hexdump("Device pubkey", dev_pubkey, PUBLIC_KEY_LEN); + hexdump("Client pubkey", cli_pubkey, PUBLIC_KEY_LEN); + + if ((resp->proto_case != SESSION_DATA__PROTO_SEC1) || + (resp->sec1->msg != SEC1_MSG_TYPE__Session_Response1)) { + ESP_LOGE(TAG, "Invalid response type"); + return ESP_ERR_INVALID_ARG; + } + + uint8_t check_buf[PUBLIC_KEY_LEN]; + Sec1Payload *in = (Sec1Payload *) resp->sec1; + + int ret = mbedtls_aes_crypt_ctr(&session->ctx_aes, PUBLIC_KEY_LEN, + &session->nc_off, session->rand, session->stb, + in->sr1->device_verify_data.data, check_buf); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_aes_crypt_ctr with erro code : %d", ret); + return ESP_FAIL; + } + hexdump("Dec Device verifier", check_buf, sizeof(check_buf)); + + if (memcmp(check_buf, session->client_pubkey, sizeof(session->client_pubkey)) != 0) { + ESP_LOGE(TAG, "Key mismatch. Close connection"); + return ESP_FAIL; + } + + return ESP_OK; +} + +static esp_err_t test_new_session(session_t *session) +{ + if (session->sec_ver == 0) { + return ESP_OK; + } + + if (!test_sec || !test_sec->new_transport_session) { + return ESP_ERR_INVALID_STATE; + } + + uint32_t session_id = session->id; + if (test_sec->new_transport_session(session_id) != ESP_OK) { + ESP_LOGE(TAG, "Failed to launch new transport session"); + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t test_sec_endpoint(session_t *session) +{ + if (session->sec_ver == 0) { + return ESP_OK; + } + + uint32_t session_id = session->id; + + int ret = ESP_FAIL; + SessionData req; + SessionData *resp; + ssize_t inlen = 0; + uint8_t *inbuf = NULL; + ssize_t outlen = 0; + uint8_t *outbuf = NULL; + + mbedtls_ecdh_init(&session->ctx_client); + mbedtls_ctr_drbg_init(&session->ctr_drbg); + + mbedtls_entropy_init(&session->entropy); + ret = mbedtls_ctr_drbg_seed(&session->ctr_drbg, mbedtls_entropy_func, + &session->entropy, NULL, 0); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ctr_drbg_seed with error code : %d", ret); + goto abort_test_sec_endpoint; + } + + ret = mbedtls_ecp_group_load(&session->ctx_client.grp, MBEDTLS_ECP_DP_CURVE25519); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ecp_group_load with error code : %d", ret); + goto abort_test_sec_endpoint; + } + + ret = mbedtls_ecdh_gen_public(&session->ctx_client.grp, + &session->ctx_client.d, + &session->ctx_client.Q, + mbedtls_ctr_drbg_random, + &session->ctr_drbg); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_ecdh_gen_public with error code : %d", ret); + goto abort_test_sec_endpoint; + } + + if (session->weak) { + /* Read zero client public key */ + ret = mbedtls_mpi_read_binary(&session->ctx_client.Q.X, + session->client_pubkey, + PUBLIC_KEY_LEN); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_read_binary with error code : %d", ret); + goto abort_test_sec_endpoint; + } + } + ret = mbedtls_mpi_write_binary(&session->ctx_client.Q.X, + session->client_pubkey, + PUBLIC_KEY_LEN); + if (ret != 0) { + ESP_LOGE(TAG, "Failed at mbedtls_mpi_write_binary with error code : %d", ret); + goto abort_test_sec_endpoint; + } + flip_endian(session->client_pubkey, PUBLIC_KEY_LEN); + + /*********** Transaction0 = SessionCmd0 + SessionResp0 ****************/ + session_data__init(&req); + if (prepare_command0(session, &req) != ESP_OK) { + ESP_LOGE(TAG, "Failed in prepare_command0"); + goto abort_test_sec_endpoint; + } + + inlen = session_data__get_packed_size(&req); + inbuf = (uint8_t *) malloc(inlen); + if (!inbuf) { + ESP_LOGE(TAG, "Failed to allocate inbuf"); + goto abort_test_sec_endpoint; + } + + session_data__pack(&req, inbuf); + cleanup_command0(&req); + + outlen = 0; + outbuf = NULL; + ret = protocomm_req_handle(test_pc, "test-sec", session_id, + inbuf, inlen, &outbuf, &outlen); + + free(inbuf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "test-sec handler failed"); + free(outbuf); + goto abort_test_sec_endpoint; + } + + resp = session_data__unpack(NULL, outlen, outbuf); + free(outbuf); + if (!resp) { + ESP_LOGE(TAG, "Unable to unpack SessionResp0"); + goto abort_test_sec_endpoint; + } + + if (verify_response0(session, resp) != ESP_OK) { + ESP_LOGE(TAG, "Invalid response 0"); + session_data__free_unpacked(resp, NULL); + goto abort_test_sec_endpoint; + } + + session_data__free_unpacked(resp, NULL); + + /*********** Transaction1 = SessionCmd1 + SessionResp1 ****************/ + session_data__init(&req); + if (prepare_command1(session, &req) != ESP_OK) { + ESP_LOGE(TAG, "Failed in prepare_command1"); + goto abort_test_sec_endpoint; + } + + inlen = session_data__get_packed_size(&req); + inbuf = (uint8_t *) malloc(inlen); + if (!inbuf) { + ESP_LOGE(TAG, "Failed to allocate inbuf"); + goto abort_test_sec_endpoint; + } + + session_data__pack(&req, inbuf); + cleanup_command1(&req); + + outlen = 0; + outbuf = NULL; + ret = protocomm_req_handle(test_pc, "test-sec", session_id, + inbuf, inlen, &outbuf, &outlen); + + free(inbuf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "test-sec handler failed"); + free(outbuf); + goto abort_test_sec_endpoint; + } + + resp = session_data__unpack(NULL, outlen, outbuf); + free(outbuf); + if (!resp) { + ESP_LOGE(TAG, "Unable to unpack SessionResp0"); + goto abort_test_sec_endpoint; + } + + if (verify_response1(session, resp) != ESP_OK) { + ESP_LOGE(TAG, "Invalid response 1"); + session_data__free_unpacked(resp, NULL); + goto abort_test_sec_endpoint; + } + + session_data__free_unpacked(resp, NULL); + mbedtls_ecdh_free(&session->ctx_client); + mbedtls_ctr_drbg_free(&session->ctr_drbg); + mbedtls_entropy_free(&session->entropy); + return ESP_OK; + +abort_test_sec_endpoint: + mbedtls_ecdh_free(&session->ctx_client); + mbedtls_ctr_drbg_free(&session->ctr_drbg); + mbedtls_entropy_free(&session->entropy); + return ESP_FAIL; +} + +static esp_err_t test_req_endpoint(session_t *session) +{ + uint32_t session_id = session->id; + + uint8_t rand_test_data[512], enc_test_data[512]; + getrandom(rand_test_data, sizeof(rand_test_data), 0); + + if (session->sec_ver == 0) { + memcpy(enc_test_data, rand_test_data, sizeof(rand_test_data)); + } + else if (session->sec_ver == 1) { + mbedtls_aes_crypt_ctr(&session->ctx_aes, sizeof(rand_test_data), &session->nc_off, + session->rand, session->stb, rand_test_data, enc_test_data); + } + + ssize_t verify_data_len = 0; + uint8_t *enc_verify_data = NULL; + + esp_err_t ret = protocomm_req_handle(test_pc, "test-ep", session_id, + enc_test_data, sizeof(enc_test_data), + &enc_verify_data, &verify_data_len); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "test-ep handler failed"); + return ESP_FAIL; + } + + uint8_t *verify_data = malloc(verify_data_len); + if (!verify_data) { + ESP_LOGE(TAG, "error allocating memory for decrypted data"); + free(enc_verify_data); + return ESP_FAIL; + } + + if (session->sec_ver == 0) { + memcpy(verify_data, enc_verify_data, verify_data_len); + } + else if (session->sec_ver == 1) { + mbedtls_aes_crypt_ctr(&session->ctx_aes, verify_data_len, &session->nc_off, + session->rand, session->stb, enc_verify_data, verify_data); + } + free(enc_verify_data); + + hexdump("Sent data", rand_test_data, sizeof(rand_test_data)); + hexdump("Recv data", verify_data, verify_data_len); + + ESP_LOGI(TAG, "verify data len : %d", verify_data_len); + ESP_LOGI(TAG, "expected data len : %d", sizeof(rand_test_data)); + + if (verify_data_len != sizeof(rand_test_data)) { + ESP_LOGE(TAG, "incorrect response length from test-ep"); + free(verify_data); + return ESP_FAIL; + } + if (memcmp(rand_test_data, verify_data, verify_data_len)) { + ESP_LOGE(TAG, "incorrect response data from test-ep"); + free(verify_data); + return ESP_FAIL; + } + free(verify_data); + return ESP_OK; +} + +esp_err_t test_req_handler (uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) +{ + *outbuf = malloc(inlen); + if (*outbuf) { + *outlen = inlen; + memcpy(*outbuf, inbuf, inlen); + } else { + ESP_LOGE(TAG, "Error allocating response outbuf"); + *outbuf = NULL; + *outlen = 0; + } + + uint32_t *priv = (uint32_t *) priv_data; + if ((&test_priv_data != priv) || (test_priv_data != *priv)) { + ESP_LOGE(TAG, "Handler private data doesn't match"); + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t start_test_service(uint8_t sec_ver, const protocomm_security_pop_t *pop) +{ + test_pc = protocomm_new(); + if (test_pc == NULL) { + ESP_LOGE(TAG, "Failed to create new protocomm instance"); + return ESP_FAIL; + } + + if (sec_ver == 0) { + if (protocomm_set_security(test_pc, "test-sec", &protocomm_security0, NULL) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Security0"); + return ESP_FAIL; + } + test_sec = &protocomm_security0; + } else if (sec_ver == 1) { + if (protocomm_set_security(test_pc, "test-sec", &protocomm_security1, pop) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Security1"); + return ESP_FAIL; + } + test_sec = &protocomm_security1; + } + + if (protocomm_add_endpoint(test_pc, "test-ep", + test_req_handler, + (void *) &test_priv_data) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set test-ep endpoint handler"); + return ESP_FAIL; + } + return ESP_OK; +} + +static void stop_test_service(void) +{ + protocomm_remove_endpoint(test_pc, "test-ep"); + protocomm_unset_security(test_pc, "test-sec"); + test_sec = NULL; + protocomm_delete(test_pc); + test_pc = NULL; +} + +static esp_err_t test_security1_no_encryption (void) +{ + ESP_LOGI(TAG, "Starting Security 1 no encryption test"); + + const char *pop_data = "test pop"; + protocomm_security_pop_t pop = { + .data = (const uint8_t *)pop_data, + .len = strlen(pop_data) + }; + + session_t *session = calloc(1, sizeof(session_t)); + if (session == NULL) { + ESP_LOGE(TAG, "Error allocating session"); + return ESP_ERR_NO_MEM; + } + + session->id = 1; + session->sec_ver = 1; + session->pop = &pop; + + // Start protocomm service + if (start_test_service(1, &pop) != ESP_OK) { + ESP_LOGE(TAG, "Error starting test"); + free(session); + return ESP_ERR_INVALID_STATE; + } + + // Intialise protocomm session with zero public keys + if (test_new_session(session) != ESP_OK) { + ESP_LOGE(TAG, "Error creating new session"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + // Perform 25519 security handshake to set public keys + if (test_sec_endpoint(session) != ESP_OK) { + ESP_LOGE(TAG, "Error testing security endpoint"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + // Force endpoint with un-encrypted data + session->sec_ver = 0; + + // Send unencrypted request data to echo endpoint. + // Response would be encrypted causing echoed back + // data to not match that which was sent, hence failing. + if (test_req_endpoint(session) == ESP_OK) { + ESP_LOGE(TAG, "Error testing request endpoint"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + stop_test_service(); + free(session); + ESP_LOGI(TAG, "Protocomm test successful"); + return ESP_OK; +} + +static esp_err_t test_security1_session_overflow (void) +{ + ESP_LOGI(TAG, "Starting Security 1 session overflow test"); + + const char *pop_data = "test pop"; + protocomm_security_pop_t pop = { + .data = (const uint8_t *)pop_data, + .len = strlen(pop_data) + }; + + session_t *session1 = calloc(1, sizeof(session_t)); + if (session1 == NULL) { + ESP_LOGE(TAG, "Error allocating session"); + return ESP_ERR_NO_MEM; + } + + session1->id = 2; + session1->sec_ver = 1; + session1->pop = &pop; + + session_t *session2 = calloc(1, sizeof(session_t)); + if (session2 == NULL) { + ESP_LOGE(TAG, "Error allocating session"); + free(session1); + return ESP_ERR_NO_MEM; + } + + session2->id = 3; + session2->sec_ver = 1; + session2->pop = NULL; + + // Start protocomm service + if (start_test_service(1, &pop) != ESP_OK) { + ESP_LOGE(TAG, "Error starting test"); + free(session1); + free(session2); + return ESP_FAIL; + } + + // Intialise protocomm session with zero public keys + if (test_new_session(session1) != ESP_OK) { + ESP_LOGE(TAG, "Error creating new session"); + stop_test_service(); + free(session1); + free(session2); + return ESP_FAIL; + } + + // Perform 25519 security handshake to set public keys + if (test_sec_endpoint(session1) != ESP_OK) { + ESP_LOGE(TAG, "Error testing security endpoint"); + stop_test_service(); + free(session1); + free(session2); + return ESP_FAIL; + } + + // Try to perform security handshake again with different + // session ID without registering new session, hence failing + if (test_sec_endpoint(session2) == ESP_OK) { + ESP_LOGE(TAG, "Error testing security endpoint"); + stop_test_service(); + free(session1); + free(session2); + return ESP_FAIL; + } + + stop_test_service(); + free(session1); + free(session2); + + ESP_LOGI(TAG, "Protocomm test successful"); + return ESP_OK; +} + +static esp_err_t test_security1_wrong_pop (void) +{ + ESP_LOGI(TAG, "Starting Security 1 wrong auth test"); + + const char *pop_data = "test pop"; + protocomm_security_pop_t pop = { + .data = (const uint8_t *)pop_data, + .len = strlen(pop_data) + }; + + session_t *session = calloc(1, sizeof(session_t)); + if (session == NULL) { + ESP_LOGE(TAG, "Error allocating session"); + return ESP_ERR_NO_MEM; + } + + session->id = 4; + session->sec_ver = 1; + session->pop = &pop; + + // Start protocomm service + if (start_test_service(1, &pop) != ESP_OK) { + ESP_LOGE(TAG, "Error starting test"); + free(session); + return ESP_FAIL; + } + + // Intialise protocomm session with zero public keys + if (test_new_session(session) != ESP_OK) { + ESP_LOGE(TAG, "Error creating new session"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + const char *wrong_pop_data = "wrong pop"; + protocomm_security_pop_t wrong_pop = { + .data = (const uint8_t *)wrong_pop_data, + .len = strlen(wrong_pop_data) + }; + + // Force wrong pop during authentication + session->pop = &wrong_pop; + + // Perform 25519 security handshake with + // wrong pop, hence failing + if (test_sec_endpoint(session) == ESP_OK) { + ESP_LOGE(TAG, "Error testing security endpoint"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + stop_test_service(); + free(session); + + ESP_LOGI(TAG, "Protocomm test successful"); + return ESP_OK; +} + +static esp_err_t test_security1_insecure_client (void) +{ + ESP_LOGI(TAG, "Starting Security 1 insecure client test"); + + const char *pop_data = "test pop"; + protocomm_security_pop_t pop = { + .data = (const uint8_t *)pop_data, + .len = strlen(pop_data) + }; + + session_t *session = calloc(1, sizeof(session_t)); + if (session == NULL) { + ESP_LOGE(TAG, "Error allocating session"); + return ESP_ERR_NO_MEM; + } + + session->id = 5; + session->sec_ver = 1; + session->pop = &pop; + + // Start protocomm service + if (start_test_service(1, &pop) != ESP_OK) { + ESP_LOGE(TAG, "Error starting test"); + free(session); + return ESP_FAIL; + } + + // Perform 25519 security handshake without + // initialising session, hence failing + if (test_sec_endpoint(session) == ESP_OK) { + ESP_LOGE(TAG, "Error testing security endpoint"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + // Communicating with request endpoint without + // initialising session, hence failing + if (test_req_endpoint(session) == ESP_OK) { + ESP_LOGE(TAG, "Error testing request endpoint"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + stop_test_service(); + free(session); + + ESP_LOGI(TAG, "Protocomm test successful"); + return ESP_OK; +} + +static esp_err_t test_security1_weak_session (void) +{ + ESP_LOGI(TAG, "Starting Security 1 weak session test"); + + const char *pop_data = "test pop"; + protocomm_security_pop_t pop = { + .data = (const uint8_t *)pop_data, + .len = strlen(pop_data) + }; + + session_t *session = calloc(1, sizeof(session_t)); + if (session == NULL) { + ESP_LOGE(TAG, "Error allocating session"); + return ESP_ERR_NO_MEM; + } + + session->id = 6; + session->sec_ver = 1; + session->pop = &pop; + session->weak = 1; + + // Start protocomm service + if (start_test_service(1, &pop) != ESP_OK) { + ESP_LOGE(TAG, "Error starting test"); + free(session); + return ESP_FAIL; + } + + // Intialise protocomm session with zero public keys + if (test_new_session(session) != ESP_OK) { + ESP_LOGE(TAG, "Error creating new session"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + // Perform 25519 security handshake with weak (zero) + // client public key, hence failing + if (test_sec_endpoint(session) == ESP_OK) { + ESP_LOGE(TAG, "Error testing security endpoint"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + // Sending request data to echo endpoint encrypted with zero + // public keys on both client and server side should pass + if (test_req_endpoint(session) != ESP_OK) { + ESP_LOGE(TAG, "Error testing request endpoint"); + stop_test_service(); + free(session); + return ESP_FAIL; + } + + stop_test_service(); + free(session); + + ESP_LOGI(TAG, "Protocomm test successful"); + return ESP_OK; +} + +static esp_err_t test_protocomm (session_t *session) +{ + ESP_LOGI(TAG, "Starting Protocomm test"); + + // Start protocomm service + if (start_test_service(session->sec_ver, session->pop) != ESP_OK) { + ESP_LOGE(TAG, "Error starting test"); + return ESP_FAIL; + } + + // Intialise protocomm session with zero public keys + if (test_new_session(session) != ESP_OK) { + ESP_LOGE(TAG, "Error creating new session"); + stop_test_service(); + return ESP_FAIL; + } + + // Perform 25519 security handshake to set public keys + if (test_sec_endpoint(session) != ESP_OK) { + ESP_LOGE(TAG, "Error testing security endpoint"); + stop_test_service(); + return ESP_FAIL; + } + + // Send request data to echo endpoint encrypted with + // the set public keys on both client and server side + if (test_req_endpoint(session) != ESP_OK) { + ESP_LOGE(TAG, "Error testing request endpoint"); + stop_test_service(); + return ESP_FAIL; + } + + // Stop protocomm service + stop_test_service(); + ESP_LOGI(TAG, "Protocomm test successful"); + return ESP_OK; +} + +static esp_err_t test_security1 (void) +{ + ESP_LOGI(TAG, "Starting Sec1 test"); + + const char *pop_data = "test pop"; + protocomm_security_pop_t pop = { + .data = (const uint8_t *)pop_data, + .len = strlen(pop_data) + }; + + session_t *session = calloc(1, sizeof(session_t)); + if (session == NULL) { + ESP_LOGE(TAG, "Error allocating session"); + return ESP_ERR_NO_MEM; + } + + session->id = 7; + session->sec_ver = 1; + session->pop = &pop; + + if (test_protocomm (session) != ESP_OK) { + ESP_LOGE(TAG, "Sec1 test failed"); + free(session); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Sec1 test successful"); + free(session); + return ESP_OK; +} + +static esp_err_t test_security0 (void) +{ + ESP_LOGI(TAG, "Starting Sec0 test"); + + session_t *session = calloc(1, sizeof(session_t)); + if (session == NULL) { + ESP_LOGE(TAG, "Error allocating session"); + return ESP_ERR_NO_MEM; + } + + session->id = 8; + session->sec_ver = 0; + session->pop = NULL; + + if (test_protocomm (session) != ESP_OK) { + ESP_LOGE(TAG, "Sec0 test failed"); + free(session); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Sec0 test successful"); + free(session); + return ESP_OK; +} + +TEST_CASE("leak test", "[PROTOCOMM]") +{ +#ifdef DO_HEAP_TRACING + heap_trace_init_standalone(trace_record, NUM_RECORDS); +#endif + + unsigned pre_start_mem = esp_get_free_heap_size(); + +#ifdef DO_HEAP_TRACING + heap_trace_start(HEAP_TRACE_LEAKS); +#endif + + test_security0(); + test_security1(); + +#ifdef DO_HEAP_TRACING + heap_trace_stop(); + heap_trace_dump(); +#endif + + unsigned post_stop_mem = esp_get_free_heap_size(); + + if (pre_start_mem != post_stop_mem) { + ESP_LOGE(TAG, "Mismatch in free heap size"); + } + +#ifdef DO_HEAP_TRACING + TEST_ASSERT(pre_start_mem != post_stop_mem); +#endif +} + +TEST_CASE("security 0 basic test", "[PROTOCOMM]") +{ + TEST_ASSERT(test_security0() == ESP_OK); +} + +TEST_CASE("security 1 basic test", "[PROTOCOMM]") +{ + TEST_ASSERT(test_security1() == ESP_OK); +} + +TEST_CASE("security 1 no encryption test", "[PROTOCOMM]") +{ + TEST_ASSERT(test_security1_no_encryption() == ESP_OK); +} + +TEST_CASE("security 1 session overflow test", "[PROTOCOMM]") +{ + TEST_ASSERT(test_security1_session_overflow() == ESP_OK); +} + +TEST_CASE("security 1 wrong pop test", "[PROTOCOMM]") +{ + TEST_ASSERT(test_security1_wrong_pop() == ESP_OK); +} + +TEST_CASE("security 1 insecure client test", "[PROTOCOMM]") +{ + TEST_ASSERT(test_security1_insecure_client() == ESP_OK); +} + +TEST_CASE("security 1 weak session test", "[PROTOCOMM]") +{ + TEST_ASSERT(test_security1_weak_session() == ESP_OK); +} From 942837536804b06e065e07e051a1733e9bdb0d46 Mon Sep 17 00:00:00 2001 From: Amey Inamdar Date: Mon, 30 Jul 2018 21:36:48 +0530 Subject: [PATCH 4/7] Protocomm : Added support for choice of transport - WiFi (SoftAP+HTTPD), BLE, Console (development friendly transport) Co-Authored-By: Amey Inamdar Co-Authored-By: Anurag Kar --- components/protocomm/CMakeLists.txt | 25 + components/protocomm/component.mk | 10 +- .../include/transports/protocomm_ble.h | 92 ++++ .../include/transports/protocomm_console.h | 59 ++ .../include/transports/protocomm_httpd.h | 73 +++ .../protocomm/src/simple_ble/simple_ble.c | 268 +++++++++ .../protocomm/src/simple_ble/simple_ble.h | 107 ++++ .../protocomm/src/transports/protocomm_ble.c | 510 ++++++++++++++++++ .../src/transports/protocomm_console.c | 224 ++++++++ .../src/transports/protocomm_httpd.c | 249 +++++++++ 10 files changed, 1614 insertions(+), 3 deletions(-) create mode 100644 components/protocomm/CMakeLists.txt create mode 100644 components/protocomm/include/transports/protocomm_ble.h create mode 100644 components/protocomm/include/transports/protocomm_console.h create mode 100644 components/protocomm/include/transports/protocomm_httpd.h create mode 100644 components/protocomm/src/simple_ble/simple_ble.c create mode 100644 components/protocomm/src/simple_ble/simple_ble.h create mode 100644 components/protocomm/src/transports/protocomm_ble.c create mode 100644 components/protocomm/src/transports/protocomm_console.c create mode 100644 components/protocomm/src/transports/protocomm_httpd.c diff --git a/components/protocomm/CMakeLists.txt b/components/protocomm/CMakeLists.txt new file mode 100644 index 000000000..a6609e5aa --- /dev/null +++ b/components/protocomm/CMakeLists.txt @@ -0,0 +1,25 @@ +set(COMPONENT_ADD_INCLUDEDIRS include/common + include/security + include/transports) +set(COMPONENT_PRIV_INCLUDEDIRS proto-c src/common src/simple_ble) +set(COMPONENT_SRCS "src/common/protocomm.c" + "src/security/security0.c" + "src/security/security1.c" + "proto-c/constants.pb-c.c" + "proto-c/sec0.pb-c.c" + "proto-c/sec1.pb-c.c" + "proto-c/session.pb-c.c" + "src/transports/protocomm_console.c" + "src/transports/protocomm_httpd.c") + +set(COMPONENT_PRIV_REQUIRES protobuf-c mbedtls console http_server bt) + +if(CONFIG_BT_ENABLED) + if(CONFIG_BLUEDROID_ENABLED) + list(APPEND COMPONENT_SRCS + "src/simple_ble/simple_ble.c" + "src/transports/protocomm_ble.c") + endif() +endif() + +register_component() diff --git a/components/protocomm/component.mk b/components/protocomm/component.mk index 7941a8e3b..3ed61cbc1 100644 --- a/components/protocomm/component.mk +++ b/components/protocomm/component.mk @@ -1,3 +1,7 @@ -COMPONENT_ADD_INCLUDEDIRS := include/common include/security proto-c -COMPONENT_PRIV_INCLUDEDIRS := src/common -COMPONENT_SRCDIRS := src/common src/security proto-c +COMPONENT_ADD_INCLUDEDIRS := include/common include/security include/transports +COMPONENT_PRIV_INCLUDEDIRS := proto-c src/common src/simple_ble +COMPONENT_SRCDIRS := src/common src/security proto-c src/simple_ble src/transports + +ifneq ($(filter y, $(CONFIG_BT_ENABLED) $(CONFIG_BLUEDROID_ENABLED)),y y) + COMPONENT_OBJEXCLUDE := src/simple_ble/simple_ble.o src/transports/protocomm_ble.o +endif diff --git a/components/protocomm/include/transports/protocomm_ble.h b/components/protocomm/include/transports/protocomm_ble.h new file mode 100644 index 000000000..b86059e57 --- /dev/null +++ b/components/protocomm/include/transports/protocomm_ble.h @@ -0,0 +1,92 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#pragma once + +#include + +/** + * BLE device name cannot be larger than this value + */ +#define MAX_BLE_DEVNAME_LEN 13 + +/** + * @brief This structure maps handler required by protocomm layer to + * UUIDs which are used to uniquely identify BLE characteristics + * from a smartphone or a similar client device. + */ +typedef struct name_uuid { + /** + * Name of the handler, which is passed to protocomm layer + */ + char *name; + + /** + * UUID to be assigned to the BLE characteristic which is + * mapped to the handler + */ + uint16_t uuid; +} protocomm_ble_name_uuid_t; + +/** + * @brief Config parameters for protocomm BLE service + */ +typedef struct { + /** + * BLE device name being broadcast at the time of provisioning + */ + char device_name[MAX_BLE_DEVNAME_LEN]; + uint8_t service_uuid[16]; /*!< SSID of the provisioning service */ + ssize_t nu_lookup_count; /*!< Number of entries in the Name-UUID lookup table */ + + /** + * Pointer to the Name-UUID lookup table + */ + protocomm_ble_name_uuid_t *nu_lookup; +} protocomm_ble_config_t; + +/** + * @brief Start Bluetooth Low Energy based transport layer for provisioning + * + * Initialize and start required BLE service for provisioning. This includes + * the initialization for characteristics/service for BLE. + * + * @param[in] pc Protocomm instance pointer obtained from protocomm_new() + * @param[in] config Pointer to config structure for initialising BLE + * + * @return + * - ESP_OK : if successful + * - ESP_FAIL : Simple BLE start error + * - ESP_ERR_NO_MEM : Error allocating memory for internal resources + * - ESP_ERR_INVALID_STATE : Error in ble config + * - ESP_ERR_INVALID_ARG : Null arguments + */ +esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config); + +/** + * @brief Stop Bluetooth Low Energy based transport layer for provisioning + * + * Stops service/task responsible for BLE based interactions for provisioning + * + * @note You might want to optionally reclaim memory from Bluetooth. + * Refer to the documentation of `esp_bt_mem_release` in that case. + * + * @param[in] pc Same protocomm instance that was passed to protocomm_ble_start() + * + * @return + * - ESP_OK : For success or appropriate error code + * - ESP_FAIL : Simple BLE stop error + * - ESP_ERR_INVALID_ARG : Null / incorrect protocomm instance + */ +esp_err_t protocomm_ble_stop(protocomm_t *pc); diff --git a/components/protocomm/include/transports/protocomm_console.h b/components/protocomm/include/transports/protocomm_console.h new file mode 100644 index 000000000..b7004d02b --- /dev/null +++ b/components/protocomm/include/transports/protocomm_console.h @@ -0,0 +1,59 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#pragma once + +#include + +#define PROTOCOMM_CONSOLE_DEFAULT_CONFIG() { \ + .stack_size = 4096, \ + .task_priority = tskIDLE_PRIORITY + 3, \ +} + +/** + * @brief Config parameters for protocomm console + */ +typedef struct { + size_t stack_size; /*!< Stack size of console taks */ + unsigned task_priority; /*!< Priority of console task */ +} protocomm_console_config_t; + +/** + * @brief Start console based protocomm transport + * + * @note This is a singleton. ie. Protocomm can have multiple instances, but only + * one instance can be bound to a console based transport layer. + * + * @param[in] pc Protocomm instance pointer obtained from protocomm_new() + * @param[in] config Config param structure for protocomm console + * + * @return + * - ESP_OK : Server started succefully + * - ESP_ERR_INVALID_ARG : Null arguments + * - ESP_ERR_NOT_SUPPORTED : Transport layer bound to another protocomm instance + * - ESP_ERR_INVALID_STATE : Transport layer already bound to this protocomm instance + * - ESP_FAIL : Failed to start console thread + */ +esp_err_t protocomm_console_start(protocomm_t *pc, const protocomm_console_config_t *config); + +/** + * @brief Stop console protocomm transport + * + * @param[in] pc Same protocomm instance that was passed to protocomm_console_start() + * + * @return + * - ESP_OK : Server stopped succefully + * - ESP_ERR_INVALID_ARG : Null / incorrect protocomm instance pointer + */ +esp_err_t protocomm_console_stop(protocomm_t *pc); diff --git a/components/protocomm/include/transports/protocomm_httpd.h b/components/protocomm/include/transports/protocomm_httpd.h new file mode 100644 index 000000000..701fa9683 --- /dev/null +++ b/components/protocomm/include/transports/protocomm_httpd.h @@ -0,0 +1,73 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#pragma once + +#include + +#define PROTOCOMM_HTTPD_DEFAULT_CONFIG() { \ + .port = 80, \ + .stack_size = 4096, \ + .task_priority = tskIDLE_PRIORITY + 5, \ +} + +/** + * @brief Config parameters for protocomm HTTP server + */ +typedef struct { + uint16_t port; /*!< Port on which the http server will listen */ + + /** + * Stack size of server task, adjusted depending + * upon stack usage of endpoint handler + */ + size_t stack_size; + unsigned task_priority; /*!< Priority of server task */ +} protocomm_httpd_config_t; + +/** + * @brief Start HTTPD protocomm transport + * + * This API internally creates a framework to allow endpoint registration and security + * configuration for the protocomm. + * + * @note This is a singleton. ie. Protocomm can have multiple instances, but only + * one instance can be bound to an HTTP transport layer. + * + * @param[in] pc Protocomm instance pointer obtained from protocomm_new() + * @param[in] config Pointer to config structure for initialising HTTP server + * + * @return + * - ESP_OK : Server started succefully + * - ESP_ERR_INVALID_ARG : Null arguments + * - ESP_ERR_NOT_SUPPORTED : Transport layer bound to another protocomm instance + * - ESP_ERR_INVALID_STATE : Transport layer already bound to this protocomm instance + * - ESP_ERR_NO_MEM : Memory allocation for server resource failed + * - ESP_ERR_HTTPD_* : HTTP server error on start + */ +esp_err_t protocomm_httpd_start(protocomm_t *pc, const protocomm_httpd_config_t *config); + +/** + * @brief Stop HTTPD protocomm transport + * + * This API cleans up the HTTPD transport protocomm and frees all the handlers registered + * with the protocomm. + * + * @param[in] pc Same protocomm instance that was passed to protocomm_httpd_start() + * + * @return + * - ESP_OK : Server stopped succefully + * - ESP_ERR_INVALID_ARG : Null / incorrect protocomm instance pointer + */ +esp_err_t protocomm_httpd_stop(protocomm_t *pc); diff --git a/components/protocomm/src/simple_ble/simple_ble.c b/components/protocomm/src/simple_ble/simple_ble.c new file mode 100644 index 000000000..e52be0053 --- /dev/null +++ b/components/protocomm/src/simple_ble/simple_ble.c @@ -0,0 +1,268 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 +#include +#include +#include +#include + +#include "simple_ble.h" + +#define SIMPLE_BLE_MAX_GATT_TABLE_SIZE 20 + +static const char *TAG = "simple_ble"; + +static simple_ble_cfg_t *g_ble_cfg_p; +static uint16_t g_gatt_table_map[SIMPLE_BLE_MAX_GATT_TABLE_SIZE]; + +uint16_t simple_ble_get_uuid(uint16_t handle) +{ + uint16_t *uuid_ptr; + + for (int i = 0; i < SIMPLE_BLE_MAX_GATT_TABLE_SIZE; i++) { + if (g_gatt_table_map[i] == handle) { + uuid_ptr = (uint16_t *) g_ble_cfg_p->gatt_db[i].att_desc.uuid_p; + return *uuid_ptr; + } + } + return -1; +} + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params); + + break; + default: + break; + } +} + +static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t p_gatts_if, esp_ble_gatts_cb_param_t *param) +{ + static esp_gatt_if_t gatts_if = ESP_GATT_IF_NONE; + esp_err_t ret; + uint8_t service_instance_id = 0; + if (event == ESP_GATTS_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gatts_if = p_gatts_if; + } else { + ESP_LOGE(TAG, "reg app failed, app_id 0x0x%x, status %d", + param->reg.app_id, + param->reg.status); + return; + } + } + + if (gatts_if != ESP_GATT_IF_NONE && gatts_if != p_gatts_if) { + return; + } + + switch (event) { + case ESP_GATTS_REG_EVT: + ret = esp_ble_gatts_create_attr_tab(g_ble_cfg_p->gatt_db, gatts_if, g_ble_cfg_p->gatt_db_count, service_instance_id); + if (ret) { + ESP_LOGE(TAG, "create attr table failed, error code = 0x%x", ret); + return; + } + ret = esp_ble_gap_set_device_name(g_ble_cfg_p->device_name); + if (ret) { + ESP_LOGE(TAG, "set device name failed, error code = 0x%x", ret); + return; + } + ret = esp_ble_gap_config_adv_data(&g_ble_cfg_p->adv_data); + if (ret) { + ESP_LOGE(TAG, "config adv data failed, error code = 0x%x", ret); + return; + } + break; + case ESP_GATTS_READ_EVT: + g_ble_cfg_p->read_fn(event, gatts_if, param); + break; + case ESP_GATTS_WRITE_EVT: + g_ble_cfg_p->write_fn(event, gatts_if, param); + break; + case ESP_GATTS_EXEC_WRITE_EVT: + g_ble_cfg_p->exec_write_fn(event, gatts_if, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGV(TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); + if (g_ble_cfg_p->set_mtu_fn) { + g_ble_cfg_p->set_mtu_fn(event, gatts_if, param); + } + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGD(TAG, "ESP_GATTS_CONF_EVT, status = %d", param->conf.status); + break; + case ESP_GATTS_START_EVT: + ESP_LOGD(TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle); + break; + case ESP_GATTS_CONNECT_EVT: + ESP_LOGD(TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id); + g_ble_cfg_p->connect_fn(event, gatts_if, param); + esp_ble_conn_update_params_t conn_params = {0}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + /* For the iOS system, please refer the official Apple documents about BLE connection parameters restrictions. */ + conn_params.latency = 0; + conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms + conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms + conn_params.timeout = 400; // timeout = 400*10ms = 4000ms + esp_ble_gap_update_conn_params(&conn_params); + break; + case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGD(TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason); + g_ble_cfg_p->disconnect_fn(event, gatts_if, param); + esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params); + break; + case ESP_GATTS_CREAT_ATTR_TAB_EVT: { + if (param->add_attr_tab.status != ESP_GATT_OK) { + ESP_LOGE(TAG, "creating the attribute table failed, error code=0x%x", param->add_attr_tab.status); + } else if (param->add_attr_tab.num_handle != g_ble_cfg_p->gatt_db_count) { + ESP_LOGE(TAG, "created attribute table abnormally "); + } else { + ESP_LOGD(TAG, "created attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle); + memcpy(g_gatt_table_map, param->add_attr_tab.handles, param->add_attr_tab.num_handle * sizeof(g_gatt_table_map[0])); + /* We assume, for now, that the first entry is always the index to the 'service' definition */ + esp_ble_gatts_start_service(g_gatt_table_map[0]); + } + break; + } + case ESP_GATTS_STOP_EVT: + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + case ESP_GATTS_UNREG_EVT: + case ESP_GATTS_DELETE_EVT: + default: + break; + } +} + +simple_ble_cfg_t *simple_ble_init() +{ + simple_ble_cfg_t *ble_cfg_p = (simple_ble_cfg_t *) malloc(sizeof(simple_ble_cfg_t)); + if (ble_cfg_p == NULL) { + ESP_LOGE(TAG, "No memory for simple_ble_cfg_t"); + return NULL; + } + return ble_cfg_p; +} + +esp_err_t simple_ble_deinit() +{ + free(g_ble_cfg_p->gatt_db); + free(g_ble_cfg_p); + g_ble_cfg_p = NULL; + return ESP_OK; +} + +/* Expects the pointer stays valid throughout */ +esp_err_t simple_ble_start(simple_ble_cfg_t *cfg) +{ + g_ble_cfg_p = cfg; + ESP_LOGD(TAG, "Free mem at start of simple_ble_init %d", esp_get_free_heap_size()); + esp_err_t ret; + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "%s enable controller failed %d", __func__, ret); + return ret; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "%s enable controller failed %d", __func__, ret); + return ret; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(TAG, "%s init bluetooth failed %d", __func__, ret); + return ret; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "%s enable bluetooth failed %d", __func__, ret); + return ret; + } + + ret = esp_ble_gatts_register_callback(gatts_profile_event_handler); + if (ret) { + ESP_LOGE(TAG, "gatts register error, error code = 0x%x", ret); + return ret; + } + + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret) { + ESP_LOGE(TAG, "gap register error, error code = 0x%x", ret); + return ret; + } + + uint16_t app_id = 0x55; + ret = esp_ble_gatts_app_register(app_id); + if (ret) { + ESP_LOGE(TAG, "gatts app register error, error code = 0x%x", ret); + return ret; + } + + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret) { + ESP_LOGE(TAG, "set local MTU failed, error code = 0x%x", local_mtu_ret); + } + ESP_LOGD(TAG, "Free mem at end of simple_ble_init %d", esp_get_free_heap_size()); + return ESP_OK; +} + +esp_err_t simple_ble_stop() +{ + esp_err_t err; + ESP_LOGD(TAG, "Free mem at start of simple_ble_stop %d", esp_get_free_heap_size()); + err = esp_bluedroid_disable(); + if (err != ESP_OK) { + return ESP_FAIL; + } + ESP_LOGD(TAG, "esp_bluedroid_disable called successfully"); + err = esp_bluedroid_deinit(); + if (err != ESP_OK) { + return err; + } + ESP_LOGD(TAG, "esp_bluedroid_deinit called successfully"); + err = esp_bt_controller_disable(); + if (err != ESP_OK) { + return ESP_FAIL; + } + + /* The API `esp_bt_controller_deinit` will have to be removed when we add support for + * `reset to provisioning` + */ + ESP_LOGD(TAG, "esp_bt_controller_disable called successfully"); + err = esp_bt_controller_deinit(); + if (err != ESP_OK) { + return ESP_FAIL; + } + ESP_LOGD(TAG, "esp_bt_controller_deinit called successfully"); + + ESP_LOGD(TAG, "Free mem at end of simple_ble_stop %d", esp_get_free_heap_size()); + return ESP_OK; +} diff --git a/components/protocomm/src/simple_ble/simple_ble.h b/components/protocomm/src/simple_ble/simple_ble.h new file mode 100644 index 000000000..892cd4d55 --- /dev/null +++ b/components/protocomm/src/simple_ble/simple_ble.h @@ -0,0 +1,107 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _SIMPLE_BLE_ +#define _SIMPLE_BLE_ + + +#include +#include +#include + +#include +#include + +typedef void (simple_ble_cb_t)(esp_gatts_cb_event_t event, esp_gatt_if_t p_gatts_if, esp_ble_gatts_cb_param_t *param); + +/** + * This structure is populated with the details required + * to create an instance of BLE easily. It requires function + * pointers, advertising parameters and gatt description table + */ +typedef struct { + /** Name to be displayed to devices scanning for ESP32 */ + const char *device_name; + /** Advertising data content, according to "Supplement to the Bluetooth Core Specification" */ + esp_ble_adv_data_t adv_data; + /** Parameters to configure the nature of advertising */ + esp_ble_adv_params_t adv_params; + /** Descriptor table which consists the configuration required by services and characteristics */ + esp_gatts_attr_db_t *gatt_db; + /** Number of entries in the gatt_db descriptor table */ + ssize_t gatt_db_count; + /** BLE read callback */ + simple_ble_cb_t *read_fn; + /** BLE write callback */ + simple_ble_cb_t *write_fn; + /** BLE exec write callback */ + simple_ble_cb_t *exec_write_fn; + /** Client disconnect callback */ + simple_ble_cb_t *disconnect_fn; + /** Client connect callback */ + simple_ble_cb_t *connect_fn; + /** MTU set callback */ + simple_ble_cb_t *set_mtu_fn; +} simple_ble_cfg_t; + + +/** Initialise a simple ble connection + * + * This function allocates memory and returns a pointer to the + * configuration structure. + * + * @return simple_ble_cfg_t* Pointer to configuration structure + */ +simple_ble_cfg_t *simple_ble_init(); + +/** Deallocates memory + * + * This function deallocate memory of the configuration structure. + * + * @return ESP_OK + */ +esp_err_t simple_ble_deinit(); + +/** Starts BLE service + * + * This function makes calls to the GATT and GAP APIs + * to initialize the BLE service as per parameters stored + * in the config structure. At the end of this function, + * one should be able to scan and connect to the ESP32 device + * using BLE. + * This API sets the MTU size to 500 (this is not part of the config structure) + * + * @return ESP_OK on success, and appropriate error code for failure + */ +esp_err_t simple_ble_start(simple_ble_cfg_t *cfg); + +/** Stops the BLE service + * + * This API is called to stop the BLE service. + * This includes calls to disable and deinit bluedroid and bt controller. + * + * @return ESP_OK on success, and appropriate error code for failure + */ +esp_err_t simple_ble_stop(); + +/** Convert handle to UUID of characteristic + * + * This function can be easily used to get the corresponding + * UUID for a characteristic that has been created, and the one for + * which we only have the handle for. + * + * @return uuid the UUID of the handle, -1 in case of invalid handle + */ +uint16_t simple_ble_get_uuid(uint16_t handle); + +#endif /* _SIMPLE_BLE_ */ diff --git a/components/protocomm/src/transports/protocomm_ble.c b/components/protocomm/src/transports/protocomm_ble.c new file mode 100644 index 000000000..a4b1927d0 --- /dev/null +++ b/components/protocomm/src/transports/protocomm_ble.c @@ -0,0 +1,510 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 +#include + +#include "protocomm_priv.h" +#include "simple_ble.h" + +#define CHAR_VAL_LEN_MAX (256 + 1) +#define PREPARE_BUF_MAX_SIZE CHAR_VAL_LEN_MAX + +static const char *TAG = "protocomm_ble"; + +/* BLE specific configuration parameters */ +const uint16_t GATTS_SERVICE_UUID_PROV = 0xFFFF; +const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE; +const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE; +const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE; + +typedef struct { + uint8_t *prepare_buf; + int prepare_len; + uint16_t handle; +} prepare_type_env_t; + +static prepare_type_env_t prepare_write_env; + +typedef struct _protocomm_ble { + protocomm_t *pc_ble; + protocomm_ble_name_uuid_t *g_nu_lookup; + ssize_t g_nu_lookup_count; + uint16_t gatt_mtu; +} _protocomm_ble_internal_t; + +static _protocomm_ble_internal_t *protoble_internal; + +static esp_ble_adv_params_t adv_params = { + .adv_int_min = 0x100, + .adv_int_max = 0x100, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +static char* protocomm_ble_device_name = NULL; + +/* The length of adv data must be less than 31 bytes */ +static esp_ble_adv_data_t adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x100, + .max_interval = 0x100, + .appearance = ESP_BLE_APPEARANCE_UNKNOWN, + .manufacturer_len = 0, + .p_manufacturer_data = NULL, + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = 0, + .p_service_uuid = NULL, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; + +static void hexdump(const char *msg, uint8_t *buf, int len) +{ + ESP_LOGD(TAG, "%s:", msg); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG); +} + +static const char *handle_to_handler(uint16_t handle) +{ + uint16_t uuid = simple_ble_get_uuid(handle); + for (int i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + if (protoble_internal->g_nu_lookup[i].uuid == uuid ) { + return protoble_internal->g_nu_lookup[i].name; + } + } + return NULL; +} + +static void transport_simple_ble_read(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + static const uint8_t *read_buf = NULL; + static uint16_t read_len = 0; + esp_gatt_status_t status = ESP_OK; + + ESP_LOGD(TAG, "Inside read w/ session - %d on param %d %d", + param->read.conn_id, param->read.handle, read_len); + if (!read_len) { + ESP_LOGD(TAG, "Reading attr value first time"); + status = esp_ble_gatts_get_attr_value(param->read.handle, &read_len, &read_buf); + } else { + ESP_LOGD(TAG, "Subsequent read request for attr value"); + } + + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *) malloc(sizeof(esp_gatt_rsp_t)); + if (gatt_rsp != NULL) { + gatt_rsp->attr_value.len = MIN(read_len, (protoble_internal->gatt_mtu - 1)); + if (read_len && read_buf) { + memcpy(gatt_rsp->attr_value.value, + read_buf + param->read.offset, + gatt_rsp->attr_value.len); + } + read_len -= gatt_rsp->attr_value.len; + } else { + ESP_LOGE(TAG, "%s, malloc failed", __func__); + return; + } + esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->read.conn_id, + param->read.trans_id, status, gatt_rsp); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Send response error in read"); + } + free(gatt_rsp); +} + +static esp_err_t prepare_write_event_env(esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param) +{ + ESP_LOGD(TAG, "prepare write, handle = %d, value len = %d", + param->write.handle, param->write.len); + esp_gatt_status_t status = ESP_GATT_OK; + if (prepare_write_env.prepare_buf == NULL) { + prepare_write_env.prepare_buf = (uint8_t *) malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t)); + if (prepare_write_env.prepare_buf == NULL) { + ESP_LOGE(TAG, "%s , failed tp allocate preparebuf", __func__); + status = ESP_GATT_NO_RESOURCES; + } + /* prepare_write_env.prepare_len = 0; */ + } else { + if (param->write.offset > PREPARE_BUF_MAX_SIZE) { + status = ESP_GATT_INVALID_OFFSET; + } else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) { + status = ESP_GATT_INVALID_ATTR_LEN; + } + } + memcpy(prepare_write_env.prepare_buf + param->write.offset, + param->write.value, + param->write.len); + prepare_write_env.prepare_len += param->write.len; + prepare_write_env.handle = param->write.handle; + if (param->write.need_rsp) { + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *) malloc(sizeof(esp_gatt_rsp_t)); + if (gatt_rsp != NULL) { + gatt_rsp->attr_value.len = param->write.len; + gatt_rsp->attr_value.handle = param->write.handle; + gatt_rsp->attr_value.offset = param->write.offset; + gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; + memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len); + esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, status, + gatt_rsp); + if (response_err != ESP_OK) { + ESP_LOGE(TAG, "Send response error in prep write"); + } + free(gatt_rsp); + } else { + ESP_LOGE(TAG, "%s, malloc failed", __func__); + } + } + if (status != ESP_GATT_OK) { + if (prepare_write_env.prepare_buf) { + free(prepare_write_env.prepare_buf); + prepare_write_env.prepare_buf = NULL; + prepare_write_env.prepare_len = 0; + } + return ESP_FAIL; + } + return ESP_OK; +} + +static void transport_simple_ble_write(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + esp_err_t ret; + + ESP_LOGD(TAG, "Inside write with session - %d on attr handle - %d \nLen -%d IS Prep - %d", + param->write.conn_id, param->write.handle, param->write.len, param->write.is_prep); + + if (param->write.is_prep) { + ret = prepare_write_event_env(gatts_if, param); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error appending to prepare buffer"); + } + return; + } else { + ESP_LOGD(TAG, "is_prep not set"); + } + + ret = protocomm_req_handle(protoble_internal->pc_ble, + handle_to_handler(param->write.handle), + param->exec_write.conn_id, + param->write.value, + param->write.len, + &outbuf, &outlen); + if (ret == ESP_OK) { + ret = esp_ble_gatts_set_attr_value(param->write.handle, outlen, outbuf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set the session attribute value"); + } + ret = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_OK, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Send response error in write"); + } + hexdump("Response from write", outbuf, outlen); + + } else { + ESP_LOGE(TAG, "Invalid content received, killing connection"); + esp_ble_gatts_close(gatts_if, param->write.conn_id); + } + if (outbuf) { + free(outbuf); + } +} + + +static void transport_simple_ble_exec_write(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + esp_err_t err; + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ESP_LOGD(TAG, "Inside exec_write w/ session - %d", param->exec_write.conn_id); + + if ((param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) + && + prepare_write_env.prepare_buf) { + err = protocomm_req_handle(protoble_internal->pc_ble, + handle_to_handler(prepare_write_env.handle), + param->exec_write.conn_id, + prepare_write_env.prepare_buf, + prepare_write_env.prepare_len, + &outbuf, &outlen); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Invalid content received, killing connection"); + esp_ble_gatts_close(gatts_if, param->write.conn_id); + } else { + hexdump("Response from exec write", outbuf, outlen); + esp_ble_gatts_set_attr_value(prepare_write_env.handle, outlen, outbuf); + } + } + if (prepare_write_env.prepare_buf) { + free(prepare_write_env.prepare_buf); + prepare_write_env.prepare_buf = NULL; + prepare_write_env.prepare_len = 0; + } + + err = esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Send response error in exec write"); + } + if (outbuf) { + free(outbuf); + } +} + +static void transport_simple_ble_disconnect(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + esp_err_t ret; + ESP_LOGV(TAG, "Inside disconnect w/ session - %d", param->disconnect.conn_id); + if (protoble_internal->pc_ble->sec && + protoble_internal->pc_ble->sec->close_transport_session) { + ret = protoble_internal->pc_ble->sec->close_transport_session(param->disconnect.conn_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "error closing the session after disconnect"); + } + } + protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE; +} + +static void transport_simple_ble_connect(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + esp_err_t ret; + ESP_LOGV(TAG, "Inside BLE connect w/ conn_id - %d", param->connect.conn_id); + if (protoble_internal->pc_ble->sec && + protoble_internal->pc_ble->sec->new_transport_session) { + ret = protoble_internal->pc_ble->sec->new_transport_session(param->connect.conn_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "error creating the session"); + } + } +} + +static void transport_simple_ble_set_mtu(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + protoble_internal->gatt_mtu = param->mtu.mtu; + return; +} + +static esp_err_t protocomm_ble_add_endpoint(const char *ep_name, + protocomm_req_handler_t req_handler, + void *priv_data) +{ + /* Endpoint UUID already added when protocomm_ble_start() was called */ + return ESP_OK; +} + +static esp_err_t protocomm_ble_remove_endpoint(const char *ep_name) +{ + /* Endpoint UUID will be removed when protocomm_ble_stop() is called */ + return ESP_OK; +} + + +static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated) +{ + int i; + /* We need esp_gatts_attr_db_t of size 2 * number of handlers + 1 for service */ + ssize_t gatt_db_generated_entries = 2 * protoble_internal->g_nu_lookup_count + 1; + + *gatt_db_generated = (esp_gatts_attr_db_t *) malloc(sizeof(esp_gatts_attr_db_t) * + (gatt_db_generated_entries)); + if ((*gatt_db_generated) == NULL) { + ESP_LOGE(TAG, "Failed to assign memory to gatt_db"); + return -1; + } + /* Declare service */ + (*gatt_db_generated)[0].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP; + + (*gatt_db_generated)[0].att_desc.uuid_length = ESP_UUID_LEN_16; + (*gatt_db_generated)[0].att_desc.uuid_p = (uint8_t *) &primary_service_uuid; + (*gatt_db_generated)[0].att_desc.perm = ESP_GATT_PERM_READ; + (*gatt_db_generated)[0].att_desc.max_length = sizeof(uint16_t); + (*gatt_db_generated)[0].att_desc.length = sizeof(GATTS_SERVICE_UUID_PROV); + (*gatt_db_generated)[0].att_desc.value = (uint8_t *) &GATTS_SERVICE_UUID_PROV; + + /* Declare characteristics */ + for (i = 1 ; i < gatt_db_generated_entries ; i++) { + (*gatt_db_generated)[i].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP; + + (*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_16; + (*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ | + ESP_GATT_PERM_WRITE; + + if (i % 2 == 1) { /* Char Declaration */ + (*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *) &character_declaration_uuid; + (*gatt_db_generated)[i].att_desc.max_length = sizeof(uint8_t); + (*gatt_db_generated)[i].att_desc.length = sizeof(uint8_t); + (*gatt_db_generated)[i].att_desc.value = (uint8_t *) &char_prop_read_write; + } else { /* Char Value */ + (*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *)&protoble_internal->g_nu_lookup[i / 2 - 1].uuid; + (*gatt_db_generated)[i].att_desc.max_length = CHAR_VAL_LEN_MAX; + (*gatt_db_generated)[i].att_desc.length = 0; + (*gatt_db_generated)[i].att_desc.value = NULL; + } + } + return gatt_db_generated_entries; +} + +static void protocomm_ble_cleanup(void) +{ + if (protoble_internal) { + if (protoble_internal->g_nu_lookup) { + for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + if (protoble_internal->g_nu_lookup[i].name) { + free(protoble_internal->g_nu_lookup[i].name); + } + } + free(protoble_internal->g_nu_lookup); + } + free(protoble_internal); + protoble_internal = NULL; + } + if (protocomm_ble_device_name) { + free(protocomm_ble_device_name); + protocomm_ble_device_name = NULL; + } + if (adv_data.p_service_uuid) { + free(adv_data.p_service_uuid); + adv_data.p_service_uuid = NULL; + adv_data.service_uuid_len = 0; + } +} + +esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config) +{ + if (!pc || !config || !config->device_name || !config->nu_lookup) { + return ESP_ERR_INVALID_ARG; + } + + if (protoble_internal) { + ESP_LOGE(TAG, "Protocomm BLE already started"); + return ESP_FAIL; + } + + /* Store service UUID internally */ + adv_data.service_uuid_len = sizeof(config->service_uuid); + adv_data.p_service_uuid = malloc(sizeof(config->service_uuid)); + if (adv_data.p_service_uuid == NULL) { + ESP_LOGE(TAG, "Error allocating memory for storing service UUID"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + memcpy(adv_data.p_service_uuid, config->service_uuid, adv_data.service_uuid_len); + + /* Store BLE device name internally */ + protocomm_ble_device_name = strdup(config->device_name); + if (protocomm_ble_device_name == NULL) { + ESP_LOGE(TAG, "Error allocating memory for storing BLE device name"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + + protoble_internal = (_protocomm_ble_internal_t *) calloc(1, sizeof(_protocomm_ble_internal_t)); + if (protoble_internal == NULL) { + ESP_LOGE(TAG, "Error allocating internal protocomm structure"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + + protoble_internal->g_nu_lookup_count = config->nu_lookup_count; + protoble_internal->g_nu_lookup = malloc(config->nu_lookup_count * sizeof(protocomm_ble_name_uuid_t)); + if (protoble_internal->g_nu_lookup == NULL) { + ESP_LOGE(TAG, "Error allocating internal name UUID table"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + + for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + protoble_internal->g_nu_lookup[i].uuid = config->nu_lookup[i].uuid; + protoble_internal->g_nu_lookup[i].name = strdup(config->nu_lookup[i].name); + if (protoble_internal->g_nu_lookup[i].name == NULL) { + ESP_LOGE(TAG, "Error allocating internal name UUID entry"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + } + + pc->add_endpoint = protocomm_ble_add_endpoint; + pc->remove_endpoint = protocomm_ble_remove_endpoint; + protoble_internal->pc_ble = pc; + protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE; + + simple_ble_cfg_t *ble_config = simple_ble_init(); + if (ble_config == NULL) { + ESP_LOGE(TAG, "Ran out of memory for BLE config"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + + /* Set function pointers required for simple BLE layer */ + ble_config->read_fn = transport_simple_ble_read; + ble_config->write_fn = transport_simple_ble_write; + ble_config->exec_write_fn = transport_simple_ble_exec_write; + ble_config->disconnect_fn = transport_simple_ble_disconnect; + ble_config->connect_fn = transport_simple_ble_connect; + ble_config->set_mtu_fn = transport_simple_ble_set_mtu; + + /* Set parameters required for advertising */ + ble_config->adv_data = adv_data; + ble_config->adv_params = adv_params; + + ble_config->device_name = protocomm_ble_device_name; + ble_config->gatt_db_count = populate_gatt_db(&ble_config->gatt_db); + + if (ble_config->gatt_db_count == -1) { + ESP_LOGE(TAG, "Invalid GATT database count"); + simple_ble_deinit(); + protocomm_ble_cleanup(); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t err = simple_ble_start(ble_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "simple_ble_start failed w/ error code 0x%x", err); + simple_ble_deinit(); + protocomm_ble_cleanup(); + return err; + } + + prepare_write_env.prepare_buf = NULL; + ESP_LOGV(TAG, "Waiting for client to connect ......"); + return ESP_OK; +} + +esp_err_t protocomm_ble_stop(protocomm_t *pc) +{ + if ((pc != NULL) && + (protoble_internal != NULL ) && + (pc == protoble_internal->pc_ble)) { + esp_err_t ret = ESP_OK; + ret = simple_ble_stop(); + if (ret) { + ESP_LOGE(TAG, "BLE stop failed"); + } + simple_ble_deinit(); + protocomm_ble_cleanup(); + return ret; + } + return ESP_ERR_INVALID_ARG; +} diff --git a/components/protocomm/src/transports/protocomm_console.c b/components/protocomm/src/transports/protocomm_console.c new file mode 100644 index 000000000..859c3395a --- /dev/null +++ b/components/protocomm/src/transports/protocomm_console.c @@ -0,0 +1,224 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 +#include +#include +#include +#include + +#include +#include + +#include "protocomm_priv.h" + +static const char *TAG = "protocomm_console"; + +static uint32_t session_id = PROTOCOMM_NO_SESSION_ID; +static protocomm_t *pc_console = NULL; /* The global protocomm instance for console */ +static TaskHandle_t console_task = NULL; + +esp_err_t protocomm_console_stop(protocomm_t *pc) +{ + if (pc != pc_console) { + ESP_LOGE(TAG, "Incorrect stop request"); + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGI(TAG, "Stopping console..."); + xTaskNotifyGive(console_task); + return ESP_OK; +} + +static ssize_t hex2bin(const char *hexstr, uint8_t *bytes) +{ + size_t hexstrLen = strlen(hexstr); + ssize_t bytesLen = hexstrLen / 2; + + int count = 0; + const char *pos = hexstr; + + for(count = 0; count < bytesLen; count++) { + sscanf(pos, "%2hhx", &bytes[count]); + pos += 2; + } + + return bytesLen; +} + +static bool stopped(void) +{ + uint32_t flag = 0; + xTaskNotifyWait(0, 0, &flag, (TickType_t) 10/portTICK_RATE_MS); + return (flag != 0); +} + +static void protocomm_console_task(void *arg) +{ + int uart_num = (int) arg; + uint8_t linebuf[256]; + int i, cmd_ret; + esp_err_t ret; + QueueHandle_t uart_queue; + uart_event_t event; + + ESP_LOGV(TAG, "Initialising UART on port %d", uart_num); + uart_driver_install(uart_num, 256, 0, 8, &uart_queue, 0); + /* Initialize the console */ + esp_console_config_t console_config = { + .max_cmdline_args = 8, + .max_cmdline_length = 256, + }; + + esp_console_init(&console_config); + esp_console_register_help_command(); + + while (!stopped()) { + uart_write_bytes(uart_num, "\n>> ", 4); + memset(linebuf, 0, sizeof(linebuf)); + i = 0; + do { + ret = xQueueReceive(uart_queue, (void * )&event, (TickType_t) 10/portTICK_RATE_MS); + if (ret != pdPASS) { + if (stopped()) { + break; + } else { + continue; + } + } + if (event.type == UART_DATA) { + while (uart_read_bytes(uart_num, (uint8_t *) &linebuf[i], 1, 0)) { + if (linebuf[i] == '\r') { + uart_write_bytes(uart_num, "\r\n", 2); + } else { + uart_write_bytes(uart_num, (char *) &linebuf[i], 1); + } + i++; + } + } + } while ((i < 255) && linebuf[i-1] != '\r'); + if (stopped()) { + break; + } + ret = esp_console_run((char *) linebuf, &cmd_ret); + if (ret < 0) { + ESP_LOGE(TAG, "Console dispatcher error\n"); + break; + } + } + + if (pc_console->sec && pc_console->sec->cleanup) { + pc_console->sec->cleanup(); + } + + pc_console = NULL; + esp_console_deinit(); + + ESP_LOGI(TAG, "Console stopped"); + vTaskDelete(NULL); +} + +static int common_cmd_handler(int argc, char** argv) +{ + int i, ret; + + uint32_t cur_session_id = atoi(argv[1]); + + uint8_t *buf = (uint8_t *) malloc(strlen(argv[2])); + uint8_t *outbuf; + ssize_t outlen; + ssize_t len = hex2bin(argv[2], buf); + + if (cur_session_id != session_id) { + if (pc_console->sec && pc_console->sec->new_transport_session) { + ret = pc_console->sec->new_transport_session(cur_session_id); + if (ret == ESP_OK) { + session_id = cur_session_id; + } + } + } + + ret = protocomm_req_handle(pc_console, argv[0], cur_session_id, buf, len, &outbuf, &outlen); + free(buf); + + if (ret == ESP_OK) { + printf("\r\n"); + for (i = 0; i < outlen; i++) { + printf("%02x", outbuf[i]); + } + printf("\r\n"); + + /* Transport is responsible for freeing the transmit buffer */ + free(outbuf); + + return ESP_OK; + } else { + return ret; + } +} + +static esp_err_t protocomm_console_add_endpoint(const char *ep_name, protocomm_req_handler_t req_handler, void *priv_data) +{ + (void) req_handler; + (void) priv_data; + + esp_err_t ret; + esp_console_cmd_t cmd; + memset(&cmd, 0, sizeof(cmd)); + + cmd.command = ep_name; + cmd.help = ""; + cmd.func = common_cmd_handler; + + ret = esp_console_cmd_register(&cmd); + + return ret; +} + +static esp_err_t protocomm_console_remove_endpoint(const char *ep_name) +{ + /* Command deletion happens internally in esp_console_deinit function */ + + return ESP_OK; +} + +esp_err_t protocomm_console_start(protocomm_t *pc, const protocomm_console_config_t *config) +{ + if (pc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (pc_console != NULL) { + if (pc_console == pc) { + return ESP_ERR_INVALID_STATE; + } + else { + return ESP_ERR_NOT_SUPPORTED; + } + } + + + if (xTaskCreate(protocomm_console_task, "protocomm_console", + config->stack_size, NULL, config->task_priority, &console_task) != pdPASS) { + return ESP_FAIL; + } + + pc->add_endpoint = protocomm_console_add_endpoint; + pc->remove_endpoint = protocomm_console_remove_endpoint; + pc_console = pc; + return ESP_OK; +} diff --git a/components/protocomm/src/transports/protocomm_httpd.c b/components/protocomm/src/transports/protocomm_httpd.c new file mode 100644 index 000000000..e8d246603 --- /dev/null +++ b/components/protocomm/src/transports/protocomm_httpd.c @@ -0,0 +1,249 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 + +#include + +#include +#include + +#include "protocomm_priv.h" + +static const char *TAG = "protocomm_httpd"; +static protocomm_t *pc_httpd; /* The global protocomm instance for HTTPD */ +static uint32_t session_id = PROTOCOMM_NO_SESSION_ID; + +#define MAX_REQ_BODY_LEN 4096 + +static esp_err_t common_post_handler(httpd_req_t *req) +{ + esp_err_t ret; + uint8_t *outbuf = NULL; + char *req_body = NULL; + const char *ep_name = NULL; + ssize_t outlen; + + int cur_session_id = httpd_req_to_sockfd(req); + + if (cur_session_id != session_id) { + /* Initialise new security session */ + if (session_id != PROTOCOMM_NO_SESSION_ID) { + ESP_LOGV(TAG, "Closing session with ID: %d", session_id); + /* Presently HTTP server doesn't support callback on socket closure so + * previous session can only be closed when new session is requested */ + if (pc_httpd->sec && pc_httpd->sec->close_transport_session) { + ret = pc_httpd->sec->close_transport_session(session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to close session with ID: %d", session_id); + ret = ESP_FAIL; + goto out; + } + } + session_id = PROTOCOMM_NO_SESSION_ID; + } + if (pc_httpd->sec && pc_httpd->sec->new_transport_session) { + ret = pc_httpd->sec->new_transport_session(cur_session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to launch new session with ID: %d", cur_session_id); + ret = ESP_FAIL; + goto out; + } + } + session_id = cur_session_id; + ESP_LOGV(TAG, "New session with ID: %d", cur_session_id); + } + + if (req->content_len <= 0) { + ESP_LOGE(TAG, "Content length not found"); + ret = ESP_FAIL; + goto out; + } else if (req->content_len > MAX_REQ_BODY_LEN) { + ESP_LOGE(TAG, "Request content length should be less than 4kb"); + ret = ESP_FAIL; + goto out; + } + + req_body = (char *) malloc(req->content_len); + if (!req_body) { + ESP_LOGE(TAG, "Unable to allocate for request length %d", req->content_len); + ret = ESP_ERR_NO_MEM; + goto out; + } + + size_t recv_size = 0; + while (recv_size < req->content_len) { + ret = httpd_req_recv(req, req_body + recv_size, req->content_len - recv_size); + if (ret < 0) { + ret = ESP_FAIL; + goto out; + } + recv_size += ret; + } + + /* Extract the endpoint name from URI string of type "/ep_name" */ + ep_name = req->uri + 1; + + ret = protocomm_req_handle(pc_httpd, ep_name, session_id, + (uint8_t *)req_body, recv_size, &outbuf, &outlen); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Data handler failed"); + ret = ESP_FAIL; + goto out; + } + + ret = httpd_resp_send(req, (char *)outbuf, outlen); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "HTTP send failed"); + ret = ESP_FAIL; + goto out; + } + ret = ESP_OK; +out: + if (req_body) { + free(req_body); + } + if (outbuf) { + free(outbuf); + } + return ret; +} + +esp_err_t protocomm_httpd_add_endpoint(const char *ep_name, + protocomm_req_handler_t req_handler, + void *priv_data) +{ + if (pc_httpd == NULL) { + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGV(TAG, "Adding endpoint : %s", ep_name); + + /* Construct URI name by prepending '/' to ep_name */ + char* ep_uri = calloc(1, strlen(ep_name) + 2); + if (!ep_uri) { + ESP_LOGE(TAG, "Malloc failed for ep uri"); + return ESP_ERR_NO_MEM; + } + + /* Create URI handler structure */ + sprintf(ep_uri, "/%s", ep_name); + httpd_uri_t config_handler = { + .uri = ep_uri, + .method = HTTP_POST, + .handler = common_post_handler, + .user_ctx = NULL + }; + + /* Register URI handler */ + esp_err_t err; + httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv; + if ((err = httpd_register_uri_handler(*server, &config_handler)) != ESP_OK) { + ESP_LOGE(TAG, "Uri handler register failed: %s", esp_err_to_name(err)); + free(ep_uri); + return ESP_FAIL; + } + + free(ep_uri); + return ESP_OK; +} + +static esp_err_t protocomm_httpd_remove_endpoint(const char *ep_name) +{ + if (pc_httpd == NULL) { + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGV(TAG, "Removing endpoint : %s", ep_name); + + /* Construct URI name by prepending '/' to ep_name */ + char* ep_uri = calloc(1, strlen(ep_name) + 2); + if (!ep_uri) { + ESP_LOGE(TAG, "Malloc failed for ep uri"); + return ESP_ERR_NO_MEM; + } + sprintf(ep_uri, "/%s", ep_name); + + /* Unregister URI handler */ + esp_err_t err; + httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv; + if ((err = httpd_unregister_uri_handler(*server, ep_uri, HTTP_POST)) != ESP_OK) { + ESP_LOGE(TAG, "Uri handler de-register failed: %s", esp_err_to_name(err)); + free(ep_uri); + return ESP_FAIL; + } + + free(ep_uri); + return ESP_OK; +} + +esp_err_t protocomm_httpd_start(protocomm_t *pc, const protocomm_httpd_config_t *config) +{ + if (!pc || !config) { + return ESP_ERR_INVALID_ARG; + } + + if (pc_httpd) { + if (pc == pc_httpd) { + ESP_LOGE(TAG, "HTTP server already running for this protocomm instance"); + return ESP_ERR_INVALID_STATE; + } + ESP_LOGE(TAG, "HTTP server started for another protocomm instance"); + return ESP_ERR_NOT_SUPPORTED; + } + + /* Private data will point to the HTTP server handle */ + pc->priv = calloc(1, sizeof(httpd_handle_t)); + if (!pc->priv) { + ESP_LOGE(TAG, "Malloc failed for HTTP server handle"); + return ESP_ERR_NO_MEM; + } + + /* Configure the HTTP server */ + httpd_config_t server_config = HTTPD_DEFAULT_CONFIG(); + server_config.server_port = config->port; + server_config.stack_size = config->stack_size; + server_config.task_priority = config->task_priority; + server_config.lru_purge_enable = true; + server_config.max_open_sockets = 1; + + esp_err_t err; + if ((err = httpd_start((httpd_handle_t *)pc->priv, &server_config)) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start http server: %s", esp_err_to_name(err)); + free(pc->priv); + return err; + } + + pc->add_endpoint = protocomm_httpd_add_endpoint; + pc->remove_endpoint = protocomm_httpd_remove_endpoint; + pc_httpd = pc; + return ESP_OK; +} + +esp_err_t protocomm_httpd_stop(protocomm_t *pc) +{ + if ((pc != NULL) && (pc == pc_httpd)) { + httpd_handle_t *server_handle = (httpd_handle_t *) pc_httpd->priv; + httpd_stop(*server_handle); + free(server_handle); + pc_httpd->priv = NULL; + pc_httpd = NULL; + return ESP_OK; + } + return ESP_ERR_INVALID_ARG; +} From 2b524654161ea43c3c5db9c4fa40d447a4d31e9a Mon Sep 17 00:00:00 2001 From: Amey Inamdar Date: Mon, 30 Jul 2018 21:39:00 +0530 Subject: [PATCH 5/7] WiFi Provisioning : Added component providing framework for configuring WiFi credentials during provisioning Co-Authored-By: Amey Inamdar Co-Authored-By: Anurag Kar --- components/wifi_provisioning/CMakeLists.txt | 10 + components/wifi_provisioning/component.mk | 3 + .../include/wifi_provisioning/wifi_config.h | 120 +++ .../proto-c/wifi_config.pb-c.c | 744 ++++++++++++++++++ .../proto-c/wifi_config.pb-c.h | 321 ++++++++ .../proto-c/wifi_constants.pb-c.c | 240 ++++++ .../proto-c/wifi_constants.pb-c.h | 99 +++ components/wifi_provisioning/proto/makefile | 7 + .../wifi_provisioning/proto/wifi_config.proto | 57 ++ .../proto/wifi_constants.proto | 31 + .../python/wifi_config_pb2.py | 466 +++++++++++ .../python/wifi_constants_pb2.py | 207 +++++ .../wifi_provisioning/src/wifi_config.c | 322 ++++++++ 13 files changed, 2627 insertions(+) create mode 100644 components/wifi_provisioning/CMakeLists.txt create mode 100644 components/wifi_provisioning/component.mk create mode 100644 components/wifi_provisioning/include/wifi_provisioning/wifi_config.h create mode 100644 components/wifi_provisioning/proto-c/wifi_config.pb-c.c create mode 100644 components/wifi_provisioning/proto-c/wifi_config.pb-c.h create mode 100644 components/wifi_provisioning/proto-c/wifi_constants.pb-c.c create mode 100644 components/wifi_provisioning/proto-c/wifi_constants.pb-c.h create mode 100644 components/wifi_provisioning/proto/makefile create mode 100644 components/wifi_provisioning/proto/wifi_config.proto create mode 100644 components/wifi_provisioning/proto/wifi_constants.proto create mode 100644 components/wifi_provisioning/python/wifi_config_pb2.py create mode 100644 components/wifi_provisioning/python/wifi_constants_pb2.py create mode 100644 components/wifi_provisioning/src/wifi_config.c diff --git a/components/wifi_provisioning/CMakeLists.txt b/components/wifi_provisioning/CMakeLists.txt new file mode 100644 index 000000000..0147d685c --- /dev/null +++ b/components/wifi_provisioning/CMakeLists.txt @@ -0,0 +1,10 @@ +set(COMPONENT_ADD_INCLUDEDIRS include) +set(COMPONENT_PRIV_INCLUDEDIRS proto-c ../protocomm/proto-c) +set(COMPONENT_SRCS "src/wifi_config.c" + "proto-c/wifi_config.pb-c.c" + "proto-c/wifi_constants.pb-c.c") + +set(COMPONENT_REQUIRES lwip) +set(COMPONENT_PRIV_REQUIRES protobuf-c protocomm) + +register_component() diff --git a/components/wifi_provisioning/component.mk b/components/wifi_provisioning/component.mk new file mode 100644 index 000000000..efeb597c3 --- /dev/null +++ b/components/wifi_provisioning/component.mk @@ -0,0 +1,3 @@ +COMPONENT_SRCDIRS := src proto-c +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_PRIV_INCLUDEDIRS := proto-c ../protocomm/proto-c/ diff --git a/components/wifi_provisioning/include/wifi_provisioning/wifi_config.h b/components/wifi_provisioning/include/wifi_provisioning/wifi_config.h new file mode 100644 index 000000000..2794ae47d --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/wifi_config.h @@ -0,0 +1,120 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _WIFI_PROV_CONFIG_H_ +#define _WIFI_PROV_CONFIG_H_ + +#include + +/** + * @brief WiFi STA status for conveying back to the provisioning master + */ +typedef enum { + WIFI_PROV_STA_CONNECTING, + WIFI_PROV_STA_CONNECTED, + WIFI_PROV_STA_DISCONNECTED +} wifi_prov_sta_state_t; + +/** + * @brief WiFi STA connection fail reason + */ +typedef enum { + WIFI_PROV_STA_AUTH_ERROR, + WIFI_PROV_STA_AP_NOT_FOUND +} wifi_prov_sta_fail_reason_t; + +/** + * @brief WiFi STA connected status information + */ +typedef struct { + /** + * IP Address received by station + */ + char ip_addr[IP4ADDR_STRLEN_MAX]; + + char bssid[6]; /*!< BSSID of the AP to which connection was estalished */ + char ssid[33]; /*!< SSID of the to which connection was estalished */ + uint8_t channel; /*!< Channel of the AP */ + uint8_t auth_mode; /*!< Authorization mode of the AP */ +} wifi_prov_sta_conn_info_t; + +/** + * @brief WiFi status data to be sent in response to `get_status` request from master + */ +typedef struct { + wifi_prov_sta_state_t wifi_state; /*!< WiFi state of the station */ + union { + /** + * Reason for disconnection (valid only when `wifi_state` is `WIFI_STATION_DISCONNECTED`) + */ + wifi_prov_sta_fail_reason_t fail_reason; + + /** + * Connection information (valid only when `wifi_state` is `WIFI_STATION_CONNECTED`) + */ + wifi_prov_sta_conn_info_t conn_info; + }; +} wifi_prov_config_get_data_t; + +/** + * @brief WiFi config data received by slave during `set_config` request from master + */ +typedef struct { + char ssid[33]; /*!< SSID of the AP to which the slave is to be connected */ + char password[65]; /*!< Password of the AP */ + char bssid[6]; /*!< BSSID of the AP */ + uint8_t channel; /*!< Channel of the AP */ +} wifi_prov_config_set_data_t; + +/** + * @brief Internal handlers for receiving and responding to protocomm + * requests from master + * + * This is to be passed as priv_data for protocomm request handler + * (refer to `wifi_prov_config_data_handler()`) when calling `protocomm_add_endpoint()`. + */ +typedef struct wifi_prov_config_handlers { + /** + * Handler function called when connection status + * of the slave (in WiFi station mode) is requested + */ + esp_err_t (*get_status_handler)(wifi_prov_config_get_data_t *resp_data); + + /** + * Handler function called when WiFi connection configuration + * (eg. AP SSID, password, etc.) of the slave (in WiFi station mode) + * is to be set to user provided values + */ + esp_err_t (*set_config_handler)(const wifi_prov_config_set_data_t *req_data); + + /** + * Handler function for applying the configuration that was set in + * `set_config_handler`. After applying the station may get connected to + * the AP or may fail to connect. The slave must be ready to convey the + * updated connection status information when `get_status_handler` is + * invoked again by the master. + */ + esp_err_t (*apply_config_handler)(void); +} wifi_prov_config_handlers_t; + +/** + * @brief Handler for receiving and responding to requests from master + * + * This is to be registered as the `wifi_config` endpoint handler + * (protocomm `protocomm_req_handler_t`) using `protocomm_add_endpoint()` + */ +esp_err_t wifi_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, void *priv_data); + +#endif diff --git a/components/wifi_provisioning/proto-c/wifi_config.pb-c.c b/components/wifi_provisioning/proto-c/wifi_config.pb-c.c new file mode 100644 index 000000000..0be6dcd86 --- /dev/null +++ b/components/wifi_provisioning/proto-c/wifi_config.pb-c.c @@ -0,0 +1,744 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: wifi_config.proto */ + +/* Do not generate deprecated warnings for self */ +#ifndef PROTOBUF_C__NO_DEPRECATED +#define PROTOBUF_C__NO_DEPRECATED +#endif + +#include "wifi_config.pb-c.h" +void cmd_get_status__init + (CmdGetStatus *message) +{ + static const CmdGetStatus init_value = CMD_GET_STATUS__INIT; + *message = init_value; +} +size_t cmd_get_status__get_packed_size + (const CmdGetStatus *message) +{ + assert(message->base.descriptor == &cmd_get_status__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t cmd_get_status__pack + (const CmdGetStatus *message, + uint8_t *out) +{ + assert(message->base.descriptor == &cmd_get_status__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t cmd_get_status__pack_to_buffer + (const CmdGetStatus *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &cmd_get_status__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +CmdGetStatus * + cmd_get_status__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (CmdGetStatus *) + protobuf_c_message_unpack (&cmd_get_status__descriptor, + allocator, len, data); +} +void cmd_get_status__free_unpacked + (CmdGetStatus *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &cmd_get_status__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void resp_get_status__init + (RespGetStatus *message) +{ + static const RespGetStatus init_value = RESP_GET_STATUS__INIT; + *message = init_value; +} +size_t resp_get_status__get_packed_size + (const RespGetStatus *message) +{ + assert(message->base.descriptor == &resp_get_status__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t resp_get_status__pack + (const RespGetStatus *message, + uint8_t *out) +{ + assert(message->base.descriptor == &resp_get_status__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t resp_get_status__pack_to_buffer + (const RespGetStatus *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &resp_get_status__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +RespGetStatus * + resp_get_status__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (RespGetStatus *) + protobuf_c_message_unpack (&resp_get_status__descriptor, + allocator, len, data); +} +void resp_get_status__free_unpacked + (RespGetStatus *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &resp_get_status__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void cmd_set_config__init + (CmdSetConfig *message) +{ + static const CmdSetConfig init_value = CMD_SET_CONFIG__INIT; + *message = init_value; +} +size_t cmd_set_config__get_packed_size + (const CmdSetConfig *message) +{ + assert(message->base.descriptor == &cmd_set_config__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t cmd_set_config__pack + (const CmdSetConfig *message, + uint8_t *out) +{ + assert(message->base.descriptor == &cmd_set_config__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t cmd_set_config__pack_to_buffer + (const CmdSetConfig *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &cmd_set_config__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +CmdSetConfig * + cmd_set_config__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (CmdSetConfig *) + protobuf_c_message_unpack (&cmd_set_config__descriptor, + allocator, len, data); +} +void cmd_set_config__free_unpacked + (CmdSetConfig *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &cmd_set_config__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void resp_set_config__init + (RespSetConfig *message) +{ + static const RespSetConfig init_value = RESP_SET_CONFIG__INIT; + *message = init_value; +} +size_t resp_set_config__get_packed_size + (const RespSetConfig *message) +{ + assert(message->base.descriptor == &resp_set_config__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t resp_set_config__pack + (const RespSetConfig *message, + uint8_t *out) +{ + assert(message->base.descriptor == &resp_set_config__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t resp_set_config__pack_to_buffer + (const RespSetConfig *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &resp_set_config__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +RespSetConfig * + resp_set_config__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (RespSetConfig *) + protobuf_c_message_unpack (&resp_set_config__descriptor, + allocator, len, data); +} +void resp_set_config__free_unpacked + (RespSetConfig *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &resp_set_config__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void cmd_apply_config__init + (CmdApplyConfig *message) +{ + static const CmdApplyConfig init_value = CMD_APPLY_CONFIG__INIT; + *message = init_value; +} +size_t cmd_apply_config__get_packed_size + (const CmdApplyConfig *message) +{ + assert(message->base.descriptor == &cmd_apply_config__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t cmd_apply_config__pack + (const CmdApplyConfig *message, + uint8_t *out) +{ + assert(message->base.descriptor == &cmd_apply_config__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t cmd_apply_config__pack_to_buffer + (const CmdApplyConfig *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &cmd_apply_config__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +CmdApplyConfig * + cmd_apply_config__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (CmdApplyConfig *) + protobuf_c_message_unpack (&cmd_apply_config__descriptor, + allocator, len, data); +} +void cmd_apply_config__free_unpacked + (CmdApplyConfig *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &cmd_apply_config__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void resp_apply_config__init + (RespApplyConfig *message) +{ + static const RespApplyConfig init_value = RESP_APPLY_CONFIG__INIT; + *message = init_value; +} +size_t resp_apply_config__get_packed_size + (const RespApplyConfig *message) +{ + assert(message->base.descriptor == &resp_apply_config__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t resp_apply_config__pack + (const RespApplyConfig *message, + uint8_t *out) +{ + assert(message->base.descriptor == &resp_apply_config__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t resp_apply_config__pack_to_buffer + (const RespApplyConfig *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &resp_apply_config__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +RespApplyConfig * + resp_apply_config__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (RespApplyConfig *) + protobuf_c_message_unpack (&resp_apply_config__descriptor, + allocator, len, data); +} +void resp_apply_config__free_unpacked + (RespApplyConfig *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &resp_apply_config__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void wi_fi_config_payload__init + (WiFiConfigPayload *message) +{ + static const WiFiConfigPayload init_value = WI_FI_CONFIG_PAYLOAD__INIT; + *message = init_value; +} +size_t wi_fi_config_payload__get_packed_size + (const WiFiConfigPayload *message) +{ + assert(message->base.descriptor == &wi_fi_config_payload__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t wi_fi_config_payload__pack + (const WiFiConfigPayload *message, + uint8_t *out) +{ + assert(message->base.descriptor == &wi_fi_config_payload__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t wi_fi_config_payload__pack_to_buffer + (const WiFiConfigPayload *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &wi_fi_config_payload__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +WiFiConfigPayload * + wi_fi_config_payload__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (WiFiConfigPayload *) + protobuf_c_message_unpack (&wi_fi_config_payload__descriptor, + allocator, len, data); +} +void wi_fi_config_payload__free_unpacked + (WiFiConfigPayload *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &wi_fi_config_payload__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +#define cmd_get_status__field_descriptors NULL +#define cmd_get_status__field_indices_by_name NULL +#define cmd_get_status__number_ranges NULL +const ProtobufCMessageDescriptor cmd_get_status__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "CmdGetStatus", + "CmdGetStatus", + "CmdGetStatus", + "", + sizeof(CmdGetStatus), + 0, + cmd_get_status__field_descriptors, + cmd_get_status__field_indices_by_name, + 0, cmd_get_status__number_ranges, + (ProtobufCMessageInit) cmd_get_status__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor resp_get_status__field_descriptors[4] = +{ + { + "status", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(RespGetStatus, status), + &status__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "sta_state", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(RespGetStatus, sta_state), + &wifi_station_state__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "fail_reason", + 10, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + offsetof(RespGetStatus, state_case), + offsetof(RespGetStatus, fail_reason), + &wifi_connect_failed_reason__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "connected", + 11, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(RespGetStatus, state_case), + offsetof(RespGetStatus, connected), + &wifi_connected_state__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned resp_get_status__field_indices_by_name[] = { + 3, /* field[3] = connected */ + 2, /* field[2] = fail_reason */ + 1, /* field[1] = sta_state */ + 0, /* field[0] = status */ +}; +static const ProtobufCIntRange resp_get_status__number_ranges[2 + 1] = +{ + { 1, 0 }, + { 10, 2 }, + { 0, 4 } +}; +const ProtobufCMessageDescriptor resp_get_status__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "RespGetStatus", + "RespGetStatus", + "RespGetStatus", + "", + sizeof(RespGetStatus), + 4, + resp_get_status__field_descriptors, + resp_get_status__field_indices_by_name, + 2, resp_get_status__number_ranges, + (ProtobufCMessageInit) resp_get_status__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor cmd_set_config__field_descriptors[4] = +{ + { + "ssid", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(CmdSetConfig, ssid), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "passphrase", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(CmdSetConfig, passphrase), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "bssid", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(CmdSetConfig, bssid), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "channel", + 4, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(CmdSetConfig, channel), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned cmd_set_config__field_indices_by_name[] = { + 2, /* field[2] = bssid */ + 3, /* field[3] = channel */ + 1, /* field[1] = passphrase */ + 0, /* field[0] = ssid */ +}; +static const ProtobufCIntRange cmd_set_config__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 4 } +}; +const ProtobufCMessageDescriptor cmd_set_config__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "CmdSetConfig", + "CmdSetConfig", + "CmdSetConfig", + "", + sizeof(CmdSetConfig), + 4, + cmd_set_config__field_descriptors, + cmd_set_config__field_indices_by_name, + 1, cmd_set_config__number_ranges, + (ProtobufCMessageInit) cmd_set_config__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor resp_set_config__field_descriptors[1] = +{ + { + "status", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(RespSetConfig, status), + &status__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned resp_set_config__field_indices_by_name[] = { + 0, /* field[0] = status */ +}; +static const ProtobufCIntRange resp_set_config__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 1 } +}; +const ProtobufCMessageDescriptor resp_set_config__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "RespSetConfig", + "RespSetConfig", + "RespSetConfig", + "", + sizeof(RespSetConfig), + 1, + resp_set_config__field_descriptors, + resp_set_config__field_indices_by_name, + 1, resp_set_config__number_ranges, + (ProtobufCMessageInit) resp_set_config__init, + NULL,NULL,NULL /* reserved[123] */ +}; +#define cmd_apply_config__field_descriptors NULL +#define cmd_apply_config__field_indices_by_name NULL +#define cmd_apply_config__number_ranges NULL +const ProtobufCMessageDescriptor cmd_apply_config__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "CmdApplyConfig", + "CmdApplyConfig", + "CmdApplyConfig", + "", + sizeof(CmdApplyConfig), + 0, + cmd_apply_config__field_descriptors, + cmd_apply_config__field_indices_by_name, + 0, cmd_apply_config__number_ranges, + (ProtobufCMessageInit) cmd_apply_config__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor resp_apply_config__field_descriptors[1] = +{ + { + "status", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(RespApplyConfig, status), + &status__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned resp_apply_config__field_indices_by_name[] = { + 0, /* field[0] = status */ +}; +static const ProtobufCIntRange resp_apply_config__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 1 } +}; +const ProtobufCMessageDescriptor resp_apply_config__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "RespApplyConfig", + "RespApplyConfig", + "RespApplyConfig", + "", + sizeof(RespApplyConfig), + 1, + resp_apply_config__field_descriptors, + resp_apply_config__field_indices_by_name, + 1, resp_apply_config__number_ranges, + (ProtobufCMessageInit) resp_apply_config__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor wi_fi_config_payload__field_descriptors[7] = +{ + { + "msg", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(WiFiConfigPayload, msg), + &wi_fi_config_msg_type__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "cmd_get_status", + 10, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiConfigPayload, payload_case), + offsetof(WiFiConfigPayload, cmd_get_status), + &cmd_get_status__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "resp_get_status", + 11, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiConfigPayload, payload_case), + offsetof(WiFiConfigPayload, resp_get_status), + &resp_get_status__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "cmd_set_config", + 12, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiConfigPayload, payload_case), + offsetof(WiFiConfigPayload, cmd_set_config), + &cmd_set_config__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "resp_set_config", + 13, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiConfigPayload, payload_case), + offsetof(WiFiConfigPayload, resp_set_config), + &resp_set_config__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "cmd_apply_config", + 14, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiConfigPayload, payload_case), + offsetof(WiFiConfigPayload, cmd_apply_config), + &cmd_apply_config__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "resp_apply_config", + 15, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiConfigPayload, payload_case), + offsetof(WiFiConfigPayload, resp_apply_config), + &resp_apply_config__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned wi_fi_config_payload__field_indices_by_name[] = { + 5, /* field[5] = cmd_apply_config */ + 1, /* field[1] = cmd_get_status */ + 3, /* field[3] = cmd_set_config */ + 0, /* field[0] = msg */ + 6, /* field[6] = resp_apply_config */ + 2, /* field[2] = resp_get_status */ + 4, /* field[4] = resp_set_config */ +}; +static const ProtobufCIntRange wi_fi_config_payload__number_ranges[2 + 1] = +{ + { 1, 0 }, + { 10, 1 }, + { 0, 7 } +}; +const ProtobufCMessageDescriptor wi_fi_config_payload__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "WiFiConfigPayload", + "WiFiConfigPayload", + "WiFiConfigPayload", + "", + sizeof(WiFiConfigPayload), + 7, + wi_fi_config_payload__field_descriptors, + wi_fi_config_payload__field_indices_by_name, + 2, wi_fi_config_payload__number_ranges, + (ProtobufCMessageInit) wi_fi_config_payload__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCEnumValue wi_fi_config_msg_type__enum_values_by_number[6] = +{ + { "TypeCmdGetStatus", "WI_FI_CONFIG_MSG_TYPE__TypeCmdGetStatus", 0 }, + { "TypeRespGetStatus", "WI_FI_CONFIG_MSG_TYPE__TypeRespGetStatus", 1 }, + { "TypeCmdSetConfig", "WI_FI_CONFIG_MSG_TYPE__TypeCmdSetConfig", 2 }, + { "TypeRespSetConfig", "WI_FI_CONFIG_MSG_TYPE__TypeRespSetConfig", 3 }, + { "TypeCmdApplyConfig", "WI_FI_CONFIG_MSG_TYPE__TypeCmdApplyConfig", 4 }, + { "TypeRespApplyConfig", "WI_FI_CONFIG_MSG_TYPE__TypeRespApplyConfig", 5 }, +}; +static const ProtobufCIntRange wi_fi_config_msg_type__value_ranges[] = { +{0, 0},{0, 6} +}; +static const ProtobufCEnumValueIndex wi_fi_config_msg_type__enum_values_by_name[6] = +{ + { "TypeCmdApplyConfig", 4 }, + { "TypeCmdGetStatus", 0 }, + { "TypeCmdSetConfig", 2 }, + { "TypeRespApplyConfig", 5 }, + { "TypeRespGetStatus", 1 }, + { "TypeRespSetConfig", 3 }, +}; +const ProtobufCEnumDescriptor wi_fi_config_msg_type__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "WiFiConfigMsgType", + "WiFiConfigMsgType", + "WiFiConfigMsgType", + "", + 6, + wi_fi_config_msg_type__enum_values_by_number, + 6, + wi_fi_config_msg_type__enum_values_by_name, + 1, + wi_fi_config_msg_type__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; diff --git a/components/wifi_provisioning/proto-c/wifi_config.pb-c.h b/components/wifi_provisioning/proto-c/wifi_config.pb-c.h new file mode 100644 index 000000000..8c6107f42 --- /dev/null +++ b/components/wifi_provisioning/proto-c/wifi_config.pb-c.h @@ -0,0 +1,321 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: wifi_config.proto */ + +#ifndef PROTOBUF_C_wifi_5fconfig_2eproto__INCLUDED +#define PROTOBUF_C_wifi_5fconfig_2eproto__INCLUDED + +#include + +PROTOBUF_C__BEGIN_DECLS + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. +#elif 1003000 < PROTOBUF_C_MIN_COMPILER_VERSION +# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. +#endif + +#include "constants.pb-c.h" +#include "wifi_constants.pb-c.h" + +typedef struct _CmdGetStatus CmdGetStatus; +typedef struct _RespGetStatus RespGetStatus; +typedef struct _CmdSetConfig CmdSetConfig; +typedef struct _RespSetConfig RespSetConfig; +typedef struct _CmdApplyConfig CmdApplyConfig; +typedef struct _RespApplyConfig RespApplyConfig; +typedef struct _WiFiConfigPayload WiFiConfigPayload; + + +/* --- enums --- */ + +typedef enum _WiFiConfigMsgType { + WI_FI_CONFIG_MSG_TYPE__TypeCmdGetStatus = 0, + WI_FI_CONFIG_MSG_TYPE__TypeRespGetStatus = 1, + WI_FI_CONFIG_MSG_TYPE__TypeCmdSetConfig = 2, + WI_FI_CONFIG_MSG_TYPE__TypeRespSetConfig = 3, + WI_FI_CONFIG_MSG_TYPE__TypeCmdApplyConfig = 4, + WI_FI_CONFIG_MSG_TYPE__TypeRespApplyConfig = 5 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WI_FI_CONFIG_MSG_TYPE) +} WiFiConfigMsgType; + +/* --- messages --- */ + +struct _CmdGetStatus +{ + ProtobufCMessage base; +}; +#define CMD_GET_STATUS__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&cmd_get_status__descriptor) \ + } + + +typedef enum { + RESP_GET_STATUS__STATE__NOT_SET = 0, + RESP_GET_STATUS__STATE_FAIL_REASON = 10, + RESP_GET_STATUS__STATE_CONNECTED = 11 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(RESP_GET_STATUS__STATE) +} RespGetStatus__StateCase; + +struct _RespGetStatus +{ + ProtobufCMessage base; + Status status; + WifiStationState sta_state; + RespGetStatus__StateCase state_case; + union { + WifiConnectFailedReason fail_reason; + WifiConnectedState *connected; + }; +}; +#define RESP_GET_STATUS__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&resp_get_status__descriptor) \ + , STATUS__Success, WIFI_STATION_STATE__Connected, RESP_GET_STATUS__STATE__NOT_SET, {0} } + + +struct _CmdSetConfig +{ + ProtobufCMessage base; + ProtobufCBinaryData ssid; + ProtobufCBinaryData passphrase; + ProtobufCBinaryData bssid; + int32_t channel; +}; +#define CMD_SET_CONFIG__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&cmd_set_config__descriptor) \ + , {0,NULL}, {0,NULL}, {0,NULL}, 0 } + + +struct _RespSetConfig +{ + ProtobufCMessage base; + Status status; +}; +#define RESP_SET_CONFIG__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&resp_set_config__descriptor) \ + , STATUS__Success } + + +struct _CmdApplyConfig +{ + ProtobufCMessage base; +}; +#define CMD_APPLY_CONFIG__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&cmd_apply_config__descriptor) \ + } + + +struct _RespApplyConfig +{ + ProtobufCMessage base; + Status status; +}; +#define RESP_APPLY_CONFIG__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&resp_apply_config__descriptor) \ + , STATUS__Success } + + +typedef enum { + WI_FI_CONFIG_PAYLOAD__PAYLOAD__NOT_SET = 0, + WI_FI_CONFIG_PAYLOAD__PAYLOAD_CMD_GET_STATUS = 10, + WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_GET_STATUS = 11, + WI_FI_CONFIG_PAYLOAD__PAYLOAD_CMD_SET_CONFIG = 12, + WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_SET_CONFIG = 13, + WI_FI_CONFIG_PAYLOAD__PAYLOAD_CMD_APPLY_CONFIG = 14, + WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_APPLY_CONFIG = 15 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WI_FI_CONFIG_PAYLOAD__PAYLOAD) +} WiFiConfigPayload__PayloadCase; + +struct _WiFiConfigPayload +{ + ProtobufCMessage base; + WiFiConfigMsgType msg; + WiFiConfigPayload__PayloadCase payload_case; + union { + CmdGetStatus *cmd_get_status; + RespGetStatus *resp_get_status; + CmdSetConfig *cmd_set_config; + RespSetConfig *resp_set_config; + CmdApplyConfig *cmd_apply_config; + RespApplyConfig *resp_apply_config; + }; +}; +#define WI_FI_CONFIG_PAYLOAD__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&wi_fi_config_payload__descriptor) \ + , WI_FI_CONFIG_MSG_TYPE__TypeCmdGetStatus, WI_FI_CONFIG_PAYLOAD__PAYLOAD__NOT_SET, {0} } + + +/* CmdGetStatus methods */ +void cmd_get_status__init + (CmdGetStatus *message); +size_t cmd_get_status__get_packed_size + (const CmdGetStatus *message); +size_t cmd_get_status__pack + (const CmdGetStatus *message, + uint8_t *out); +size_t cmd_get_status__pack_to_buffer + (const CmdGetStatus *message, + ProtobufCBuffer *buffer); +CmdGetStatus * + cmd_get_status__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void cmd_get_status__free_unpacked + (CmdGetStatus *message, + ProtobufCAllocator *allocator); +/* RespGetStatus methods */ +void resp_get_status__init + (RespGetStatus *message); +size_t resp_get_status__get_packed_size + (const RespGetStatus *message); +size_t resp_get_status__pack + (const RespGetStatus *message, + uint8_t *out); +size_t resp_get_status__pack_to_buffer + (const RespGetStatus *message, + ProtobufCBuffer *buffer); +RespGetStatus * + resp_get_status__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void resp_get_status__free_unpacked + (RespGetStatus *message, + ProtobufCAllocator *allocator); +/* CmdSetConfig methods */ +void cmd_set_config__init + (CmdSetConfig *message); +size_t cmd_set_config__get_packed_size + (const CmdSetConfig *message); +size_t cmd_set_config__pack + (const CmdSetConfig *message, + uint8_t *out); +size_t cmd_set_config__pack_to_buffer + (const CmdSetConfig *message, + ProtobufCBuffer *buffer); +CmdSetConfig * + cmd_set_config__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void cmd_set_config__free_unpacked + (CmdSetConfig *message, + ProtobufCAllocator *allocator); +/* RespSetConfig methods */ +void resp_set_config__init + (RespSetConfig *message); +size_t resp_set_config__get_packed_size + (const RespSetConfig *message); +size_t resp_set_config__pack + (const RespSetConfig *message, + uint8_t *out); +size_t resp_set_config__pack_to_buffer + (const RespSetConfig *message, + ProtobufCBuffer *buffer); +RespSetConfig * + resp_set_config__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void resp_set_config__free_unpacked + (RespSetConfig *message, + ProtobufCAllocator *allocator); +/* CmdApplyConfig methods */ +void cmd_apply_config__init + (CmdApplyConfig *message); +size_t cmd_apply_config__get_packed_size + (const CmdApplyConfig *message); +size_t cmd_apply_config__pack + (const CmdApplyConfig *message, + uint8_t *out); +size_t cmd_apply_config__pack_to_buffer + (const CmdApplyConfig *message, + ProtobufCBuffer *buffer); +CmdApplyConfig * + cmd_apply_config__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void cmd_apply_config__free_unpacked + (CmdApplyConfig *message, + ProtobufCAllocator *allocator); +/* RespApplyConfig methods */ +void resp_apply_config__init + (RespApplyConfig *message); +size_t resp_apply_config__get_packed_size + (const RespApplyConfig *message); +size_t resp_apply_config__pack + (const RespApplyConfig *message, + uint8_t *out); +size_t resp_apply_config__pack_to_buffer + (const RespApplyConfig *message, + ProtobufCBuffer *buffer); +RespApplyConfig * + resp_apply_config__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void resp_apply_config__free_unpacked + (RespApplyConfig *message, + ProtobufCAllocator *allocator); +/* WiFiConfigPayload methods */ +void wi_fi_config_payload__init + (WiFiConfigPayload *message); +size_t wi_fi_config_payload__get_packed_size + (const WiFiConfigPayload *message); +size_t wi_fi_config_payload__pack + (const WiFiConfigPayload *message, + uint8_t *out); +size_t wi_fi_config_payload__pack_to_buffer + (const WiFiConfigPayload *message, + ProtobufCBuffer *buffer); +WiFiConfigPayload * + wi_fi_config_payload__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void wi_fi_config_payload__free_unpacked + (WiFiConfigPayload *message, + ProtobufCAllocator *allocator); +/* --- per-message closures --- */ + +typedef void (*CmdGetStatus_Closure) + (const CmdGetStatus *message, + void *closure_data); +typedef void (*RespGetStatus_Closure) + (const RespGetStatus *message, + void *closure_data); +typedef void (*CmdSetConfig_Closure) + (const CmdSetConfig *message, + void *closure_data); +typedef void (*RespSetConfig_Closure) + (const RespSetConfig *message, + void *closure_data); +typedef void (*CmdApplyConfig_Closure) + (const CmdApplyConfig *message, + void *closure_data); +typedef void (*RespApplyConfig_Closure) + (const RespApplyConfig *message, + void *closure_data); +typedef void (*WiFiConfigPayload_Closure) + (const WiFiConfigPayload *message, + void *closure_data); + +/* --- services --- */ + + +/* --- descriptors --- */ + +extern const ProtobufCEnumDescriptor wi_fi_config_msg_type__descriptor; +extern const ProtobufCMessageDescriptor cmd_get_status__descriptor; +extern const ProtobufCMessageDescriptor resp_get_status__descriptor; +extern const ProtobufCMessageDescriptor cmd_set_config__descriptor; +extern const ProtobufCMessageDescriptor resp_set_config__descriptor; +extern const ProtobufCMessageDescriptor cmd_apply_config__descriptor; +extern const ProtobufCMessageDescriptor resp_apply_config__descriptor; +extern const ProtobufCMessageDescriptor wi_fi_config_payload__descriptor; + +PROTOBUF_C__END_DECLS + + +#endif /* PROTOBUF_C_wifi_5fconfig_2eproto__INCLUDED */ diff --git a/components/wifi_provisioning/proto-c/wifi_constants.pb-c.c b/components/wifi_provisioning/proto-c/wifi_constants.pb-c.c new file mode 100644 index 000000000..1ef5f5711 --- /dev/null +++ b/components/wifi_provisioning/proto-c/wifi_constants.pb-c.c @@ -0,0 +1,240 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: wifi_constants.proto */ + +/* Do not generate deprecated warnings for self */ +#ifndef PROTOBUF_C__NO_DEPRECATED +#define PROTOBUF_C__NO_DEPRECATED +#endif + +#include "wifi_constants.pb-c.h" +void wifi_connected_state__init + (WifiConnectedState *message) +{ + static const WifiConnectedState init_value = WIFI_CONNECTED_STATE__INIT; + *message = init_value; +} +size_t wifi_connected_state__get_packed_size + (const WifiConnectedState *message) +{ + assert(message->base.descriptor == &wifi_connected_state__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t wifi_connected_state__pack + (const WifiConnectedState *message, + uint8_t *out) +{ + assert(message->base.descriptor == &wifi_connected_state__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t wifi_connected_state__pack_to_buffer + (const WifiConnectedState *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &wifi_connected_state__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +WifiConnectedState * + wifi_connected_state__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (WifiConnectedState *) + protobuf_c_message_unpack (&wifi_connected_state__descriptor, + allocator, len, data); +} +void wifi_connected_state__free_unpacked + (WifiConnectedState *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &wifi_connected_state__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +static const ProtobufCFieldDescriptor wifi_connected_state__field_descriptors[5] = +{ + { + "ip4_addr", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(WifiConnectedState, ip4_addr), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "auth_mode", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(WifiConnectedState, auth_mode), + &wifi_auth_mode__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "ssid", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(WifiConnectedState, ssid), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "bssid", + 4, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(WifiConnectedState, bssid), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "channel", + 5, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(WifiConnectedState, channel), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned wifi_connected_state__field_indices_by_name[] = { + 1, /* field[1] = auth_mode */ + 3, /* field[3] = bssid */ + 4, /* field[4] = channel */ + 0, /* field[0] = ip4_addr */ + 2, /* field[2] = ssid */ +}; +static const ProtobufCIntRange wifi_connected_state__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 5 } +}; +const ProtobufCMessageDescriptor wifi_connected_state__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "WifiConnectedState", + "WifiConnectedState", + "WifiConnectedState", + "", + sizeof(WifiConnectedState), + 5, + wifi_connected_state__field_descriptors, + wifi_connected_state__field_indices_by_name, + 1, wifi_connected_state__number_ranges, + (ProtobufCMessageInit) wifi_connected_state__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCEnumValue wifi_station_state__enum_values_by_number[4] = +{ + { "Connected", "WIFI_STATION_STATE__Connected", 0 }, + { "Connecting", "WIFI_STATION_STATE__Connecting", 1 }, + { "Disconnected", "WIFI_STATION_STATE__Disconnected", 2 }, + { "ConnectionFailed", "WIFI_STATION_STATE__ConnectionFailed", 3 }, +}; +static const ProtobufCIntRange wifi_station_state__value_ranges[] = { +{0, 0},{0, 4} +}; +static const ProtobufCEnumValueIndex wifi_station_state__enum_values_by_name[4] = +{ + { "Connected", 0 }, + { "Connecting", 1 }, + { "ConnectionFailed", 3 }, + { "Disconnected", 2 }, +}; +const ProtobufCEnumDescriptor wifi_station_state__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "WifiStationState", + "WifiStationState", + "WifiStationState", + "", + 4, + wifi_station_state__enum_values_by_number, + 4, + wifi_station_state__enum_values_by_name, + 1, + wifi_station_state__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; +static const ProtobufCEnumValue wifi_connect_failed_reason__enum_values_by_number[2] = +{ + { "AuthError", "WIFI_CONNECT_FAILED_REASON__AuthError", 0 }, + { "NetworkNotFound", "WIFI_CONNECT_FAILED_REASON__NetworkNotFound", 1 }, +}; +static const ProtobufCIntRange wifi_connect_failed_reason__value_ranges[] = { +{0, 0},{0, 2} +}; +static const ProtobufCEnumValueIndex wifi_connect_failed_reason__enum_values_by_name[2] = +{ + { "AuthError", 0 }, + { "NetworkNotFound", 1 }, +}; +const ProtobufCEnumDescriptor wifi_connect_failed_reason__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "WifiConnectFailedReason", + "WifiConnectFailedReason", + "WifiConnectFailedReason", + "", + 2, + wifi_connect_failed_reason__enum_values_by_number, + 2, + wifi_connect_failed_reason__enum_values_by_name, + 1, + wifi_connect_failed_reason__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; +static const ProtobufCEnumValue wifi_auth_mode__enum_values_by_number[6] = +{ + { "Open", "WIFI_AUTH_MODE__Open", 0 }, + { "WEP", "WIFI_AUTH_MODE__WEP", 1 }, + { "WPA_PSK", "WIFI_AUTH_MODE__WPA_PSK", 2 }, + { "WPA2_PSK", "WIFI_AUTH_MODE__WPA2_PSK", 3 }, + { "WPA_WPA2_PSK", "WIFI_AUTH_MODE__WPA_WPA2_PSK", 4 }, + { "WPA2_ENTERPRISE", "WIFI_AUTH_MODE__WPA2_ENTERPRISE", 5 }, +}; +static const ProtobufCIntRange wifi_auth_mode__value_ranges[] = { +{0, 0},{0, 6} +}; +static const ProtobufCEnumValueIndex wifi_auth_mode__enum_values_by_name[6] = +{ + { "Open", 0 }, + { "WEP", 1 }, + { "WPA2_ENTERPRISE", 5 }, + { "WPA2_PSK", 3 }, + { "WPA_PSK", 2 }, + { "WPA_WPA2_PSK", 4 }, +}; +const ProtobufCEnumDescriptor wifi_auth_mode__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "WifiAuthMode", + "WifiAuthMode", + "WifiAuthMode", + "", + 6, + wifi_auth_mode__enum_values_by_number, + 6, + wifi_auth_mode__enum_values_by_name, + 1, + wifi_auth_mode__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; diff --git a/components/wifi_provisioning/proto-c/wifi_constants.pb-c.h b/components/wifi_provisioning/proto-c/wifi_constants.pb-c.h new file mode 100644 index 000000000..071550554 --- /dev/null +++ b/components/wifi_provisioning/proto-c/wifi_constants.pb-c.h @@ -0,0 +1,99 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: wifi_constants.proto */ + +#ifndef PROTOBUF_C_wifi_5fconstants_2eproto__INCLUDED +#define PROTOBUF_C_wifi_5fconstants_2eproto__INCLUDED + +#include + +PROTOBUF_C__BEGIN_DECLS + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. +#elif 1003000 < PROTOBUF_C_MIN_COMPILER_VERSION +# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. +#endif + + +typedef struct _WifiConnectedState WifiConnectedState; + + +/* --- enums --- */ + +typedef enum _WifiStationState { + WIFI_STATION_STATE__Connected = 0, + WIFI_STATION_STATE__Connecting = 1, + WIFI_STATION_STATE__Disconnected = 2, + WIFI_STATION_STATE__ConnectionFailed = 3 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WIFI_STATION_STATE) +} WifiStationState; +typedef enum _WifiConnectFailedReason { + WIFI_CONNECT_FAILED_REASON__AuthError = 0, + WIFI_CONNECT_FAILED_REASON__NetworkNotFound = 1 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WIFI_CONNECT_FAILED_REASON) +} WifiConnectFailedReason; +typedef enum _WifiAuthMode { + WIFI_AUTH_MODE__Open = 0, + WIFI_AUTH_MODE__WEP = 1, + WIFI_AUTH_MODE__WPA_PSK = 2, + WIFI_AUTH_MODE__WPA2_PSK = 3, + WIFI_AUTH_MODE__WPA_WPA2_PSK = 4, + WIFI_AUTH_MODE__WPA2_ENTERPRISE = 5 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WIFI_AUTH_MODE) +} WifiAuthMode; + +/* --- messages --- */ + +struct _WifiConnectedState +{ + ProtobufCMessage base; + char *ip4_addr; + WifiAuthMode auth_mode; + ProtobufCBinaryData ssid; + ProtobufCBinaryData bssid; + int32_t channel; +}; +#define WIFI_CONNECTED_STATE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&wifi_connected_state__descriptor) \ + , (char *)protobuf_c_empty_string, WIFI_AUTH_MODE__Open, {0,NULL}, {0,NULL}, 0 } + + +/* WifiConnectedState methods */ +void wifi_connected_state__init + (WifiConnectedState *message); +size_t wifi_connected_state__get_packed_size + (const WifiConnectedState *message); +size_t wifi_connected_state__pack + (const WifiConnectedState *message, + uint8_t *out); +size_t wifi_connected_state__pack_to_buffer + (const WifiConnectedState *message, + ProtobufCBuffer *buffer); +WifiConnectedState * + wifi_connected_state__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void wifi_connected_state__free_unpacked + (WifiConnectedState *message, + ProtobufCAllocator *allocator); +/* --- per-message closures --- */ + +typedef void (*WifiConnectedState_Closure) + (const WifiConnectedState *message, + void *closure_data); + +/* --- services --- */ + + +/* --- descriptors --- */ + +extern const ProtobufCEnumDescriptor wifi_station_state__descriptor; +extern const ProtobufCEnumDescriptor wifi_connect_failed_reason__descriptor; +extern const ProtobufCEnumDescriptor wifi_auth_mode__descriptor; +extern const ProtobufCMessageDescriptor wifi_connected_state__descriptor; + +PROTOBUF_C__END_DECLS + + +#endif /* PROTOBUF_C_wifi_5fconstants_2eproto__INCLUDED */ diff --git a/components/wifi_provisioning/proto/makefile b/components/wifi_provisioning/proto/makefile new file mode 100644 index 000000000..585e42267 --- /dev/null +++ b/components/wifi_provisioning/proto/makefile @@ -0,0 +1,7 @@ +all: c_proto python_proto + +c_proto: *.proto + @protoc-c --c_out=../proto-c/ -I . -I ../../protocomm/proto/ *.proto + +python_proto: *.proto + @protoc --python_out=../python/ -I . -I ../../protocomm/proto/ *.proto diff --git a/components/wifi_provisioning/proto/wifi_config.proto b/components/wifi_provisioning/proto/wifi_config.proto new file mode 100644 index 000000000..e273dc8e5 --- /dev/null +++ b/components/wifi_provisioning/proto/wifi_config.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +import "constants.proto"; +import "wifi_constants.proto"; + +message CmdGetStatus { + +} + +message RespGetStatus { + Status status = 1; + WifiStationState sta_state = 2; + oneof state { + WifiConnectFailedReason fail_reason = 10; + WifiConnectedState connected = 11; + } +} + +message CmdSetConfig { + bytes ssid = 1; + bytes passphrase = 2; + bytes bssid = 3; + int32 channel = 4; +} + +message RespSetConfig { + Status status = 1; +} + +message CmdApplyConfig { + +} + +message RespApplyConfig { + Status status = 1; +} + +enum WiFiConfigMsgType { + TypeCmdGetStatus = 0; + TypeRespGetStatus = 1; + TypeCmdSetConfig = 2; + TypeRespSetConfig = 3; + TypeCmdApplyConfig = 4; + TypeRespApplyConfig = 5; +} + +message WiFiConfigPayload { + WiFiConfigMsgType msg = 1; + oneof payload { + CmdGetStatus cmd_get_status = 10; + RespGetStatus resp_get_status = 11; + CmdSetConfig cmd_set_config = 12; + RespSetConfig resp_set_config = 13; + CmdApplyConfig cmd_apply_config = 14; + RespApplyConfig resp_apply_config = 15; + } +} diff --git a/components/wifi_provisioning/proto/wifi_constants.proto b/components/wifi_provisioning/proto/wifi_constants.proto new file mode 100644 index 000000000..95c761224 --- /dev/null +++ b/components/wifi_provisioning/proto/wifi_constants.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +enum WifiStationState { + Connected = 0; + Connecting = 1; + Disconnected = 2; + ConnectionFailed = 3; +} + +enum WifiConnectFailedReason { + AuthError = 0; + NetworkNotFound = 1; +} + +enum WifiAuthMode { + Open = 0; + WEP = 1; + WPA_PSK = 2; + WPA2_PSK = 3; + WPA_WPA2_PSK = 4; + WPA2_ENTERPRISE = 5; +} + +message WifiConnectedState { + string ip4_addr = 1; + WifiAuthMode auth_mode = 2; + bytes ssid = 3; + bytes bssid = 4; + int32 channel = 5; +} + diff --git a/components/wifi_provisioning/python/wifi_config_pb2.py b/components/wifi_provisioning/python/wifi_config_pb2.py new file mode 100644 index 000000000..0dd6d43d4 --- /dev/null +++ b/components/wifi_provisioning/python/wifi_config_pb2.py @@ -0,0 +1,466 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: wifi_config.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +import constants_pb2 as constants__pb2 +import wifi_constants_pb2 as wifi__constants__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='wifi_config.proto', + package='', + syntax='proto3', + serialized_pb=_b('\n\x11wifi_config.proto\x1a\x0f\x63onstants.proto\x1a\x14wifi_constants.proto\"\x0e\n\x0c\x43mdGetStatus\"\xb2\x01\n\rRespGetStatus\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12$\n\tsta_state\x18\x02 \x01(\x0e\x32\x11.WifiStationState\x12/\n\x0b\x66\x61il_reason\x18\n \x01(\x0e\x32\x18.WifiConnectFailedReasonH\x00\x12(\n\tconnected\x18\x0b \x01(\x0b\x32\x13.WifiConnectedStateH\x00\x42\x07\n\x05state\"P\n\x0c\x43mdSetConfig\x12\x0c\n\x04ssid\x18\x01 \x01(\x0c\x12\x12\n\npassphrase\x18\x02 \x01(\x0c\x12\r\n\x05\x62ssid\x18\x03 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x04 \x01(\x05\"(\n\rRespSetConfig\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"\x10\n\x0e\x43mdApplyConfig\"*\n\x0fRespApplyConfig\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"\xc3\x02\n\x11WiFiConfigPayload\x12\x1f\n\x03msg\x18\x01 \x01(\x0e\x32\x12.WiFiConfigMsgType\x12\'\n\x0e\x63md_get_status\x18\n \x01(\x0b\x32\r.CmdGetStatusH\x00\x12)\n\x0fresp_get_status\x18\x0b \x01(\x0b\x32\x0e.RespGetStatusH\x00\x12\'\n\x0e\x63md_set_config\x18\x0c \x01(\x0b\x32\r.CmdSetConfigH\x00\x12)\n\x0fresp_set_config\x18\r \x01(\x0b\x32\x0e.RespSetConfigH\x00\x12+\n\x10\x63md_apply_config\x18\x0e \x01(\x0b\x32\x0f.CmdApplyConfigH\x00\x12-\n\x11resp_apply_config\x18\x0f \x01(\x0b\x32\x10.RespApplyConfigH\x00\x42\t\n\x07payload*\x9e\x01\n\x11WiFiConfigMsgType\x12\x14\n\x10TypeCmdGetStatus\x10\x00\x12\x15\n\x11TypeRespGetStatus\x10\x01\x12\x14\n\x10TypeCmdSetConfig\x10\x02\x12\x15\n\x11TypeRespSetConfig\x10\x03\x12\x16\n\x12TypeCmdApplyConfig\x10\x04\x12\x17\n\x13TypeRespApplyConfig\x10\x05\x62\x06proto3') + , + dependencies=[constants__pb2.DESCRIPTOR,wifi__constants__pb2.DESCRIPTOR,]) + +_WIFICONFIGMSGTYPE = _descriptor.EnumDescriptor( + name='WiFiConfigMsgType', + full_name='WiFiConfigMsgType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='TypeCmdGetStatus', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeRespGetStatus', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeCmdSetConfig', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeRespSetConfig', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeCmdApplyConfig', index=4, number=4, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeRespApplyConfig', index=5, number=5, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=770, + serialized_end=928, +) +_sym_db.RegisterEnumDescriptor(_WIFICONFIGMSGTYPE) + +WiFiConfigMsgType = enum_type_wrapper.EnumTypeWrapper(_WIFICONFIGMSGTYPE) +TypeCmdGetStatus = 0 +TypeRespGetStatus = 1 +TypeCmdSetConfig = 2 +TypeRespSetConfig = 3 +TypeCmdApplyConfig = 4 +TypeRespApplyConfig = 5 + + + +_CMDGETSTATUS = _descriptor.Descriptor( + name='CmdGetStatus', + full_name='CmdGetStatus', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=60, + serialized_end=74, +) + + +_RESPGETSTATUS = _descriptor.Descriptor( + name='RespGetStatus', + full_name='RespGetStatus', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='RespGetStatus.status', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sta_state', full_name='RespGetStatus.sta_state', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='fail_reason', full_name='RespGetStatus.fail_reason', index=2, + number=10, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='connected', full_name='RespGetStatus.connected', index=3, + number=11, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='state', full_name='RespGetStatus.state', + index=0, containing_type=None, fields=[]), + ], + serialized_start=77, + serialized_end=255, +) + + +_CMDSETCONFIG = _descriptor.Descriptor( + name='CmdSetConfig', + full_name='CmdSetConfig', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='ssid', full_name='CmdSetConfig.ssid', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='passphrase', full_name='CmdSetConfig.passphrase', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bssid', full_name='CmdSetConfig.bssid', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='channel', full_name='CmdSetConfig.channel', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=257, + serialized_end=337, +) + + +_RESPSETCONFIG = _descriptor.Descriptor( + name='RespSetConfig', + full_name='RespSetConfig', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='RespSetConfig.status', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=339, + serialized_end=379, +) + + +_CMDAPPLYCONFIG = _descriptor.Descriptor( + name='CmdApplyConfig', + full_name='CmdApplyConfig', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=381, + serialized_end=397, +) + + +_RESPAPPLYCONFIG = _descriptor.Descriptor( + name='RespApplyConfig', + full_name='RespApplyConfig', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='RespApplyConfig.status', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=399, + serialized_end=441, +) + + +_WIFICONFIGPAYLOAD = _descriptor.Descriptor( + name='WiFiConfigPayload', + full_name='WiFiConfigPayload', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='msg', full_name='WiFiConfigPayload.msg', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='cmd_get_status', full_name='WiFiConfigPayload.cmd_get_status', index=1, + number=10, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='resp_get_status', full_name='WiFiConfigPayload.resp_get_status', index=2, + number=11, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='cmd_set_config', full_name='WiFiConfigPayload.cmd_set_config', index=3, + number=12, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='resp_set_config', full_name='WiFiConfigPayload.resp_set_config', index=4, + number=13, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='cmd_apply_config', full_name='WiFiConfigPayload.cmd_apply_config', index=5, + number=14, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='resp_apply_config', full_name='WiFiConfigPayload.resp_apply_config', index=6, + number=15, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='payload', full_name='WiFiConfigPayload.payload', + index=0, containing_type=None, fields=[]), + ], + serialized_start=444, + serialized_end=767, +) + +_RESPGETSTATUS.fields_by_name['status'].enum_type = constants__pb2._STATUS +_RESPGETSTATUS.fields_by_name['sta_state'].enum_type = wifi__constants__pb2._WIFISTATIONSTATE +_RESPGETSTATUS.fields_by_name['fail_reason'].enum_type = wifi__constants__pb2._WIFICONNECTFAILEDREASON +_RESPGETSTATUS.fields_by_name['connected'].message_type = wifi__constants__pb2._WIFICONNECTEDSTATE +_RESPGETSTATUS.oneofs_by_name['state'].fields.append( + _RESPGETSTATUS.fields_by_name['fail_reason']) +_RESPGETSTATUS.fields_by_name['fail_reason'].containing_oneof = _RESPGETSTATUS.oneofs_by_name['state'] +_RESPGETSTATUS.oneofs_by_name['state'].fields.append( + _RESPGETSTATUS.fields_by_name['connected']) +_RESPGETSTATUS.fields_by_name['connected'].containing_oneof = _RESPGETSTATUS.oneofs_by_name['state'] +_RESPSETCONFIG.fields_by_name['status'].enum_type = constants__pb2._STATUS +_RESPAPPLYCONFIG.fields_by_name['status'].enum_type = constants__pb2._STATUS +_WIFICONFIGPAYLOAD.fields_by_name['msg'].enum_type = _WIFICONFIGMSGTYPE +_WIFICONFIGPAYLOAD.fields_by_name['cmd_get_status'].message_type = _CMDGETSTATUS +_WIFICONFIGPAYLOAD.fields_by_name['resp_get_status'].message_type = _RESPGETSTATUS +_WIFICONFIGPAYLOAD.fields_by_name['cmd_set_config'].message_type = _CMDSETCONFIG +_WIFICONFIGPAYLOAD.fields_by_name['resp_set_config'].message_type = _RESPSETCONFIG +_WIFICONFIGPAYLOAD.fields_by_name['cmd_apply_config'].message_type = _CMDAPPLYCONFIG +_WIFICONFIGPAYLOAD.fields_by_name['resp_apply_config'].message_type = _RESPAPPLYCONFIG +_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFICONFIGPAYLOAD.fields_by_name['cmd_get_status']) +_WIFICONFIGPAYLOAD.fields_by_name['cmd_get_status'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload'] +_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFICONFIGPAYLOAD.fields_by_name['resp_get_status']) +_WIFICONFIGPAYLOAD.fields_by_name['resp_get_status'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload'] +_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFICONFIGPAYLOAD.fields_by_name['cmd_set_config']) +_WIFICONFIGPAYLOAD.fields_by_name['cmd_set_config'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload'] +_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFICONFIGPAYLOAD.fields_by_name['resp_set_config']) +_WIFICONFIGPAYLOAD.fields_by_name['resp_set_config'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload'] +_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFICONFIGPAYLOAD.fields_by_name['cmd_apply_config']) +_WIFICONFIGPAYLOAD.fields_by_name['cmd_apply_config'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload'] +_WIFICONFIGPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFICONFIGPAYLOAD.fields_by_name['resp_apply_config']) +_WIFICONFIGPAYLOAD.fields_by_name['resp_apply_config'].containing_oneof = _WIFICONFIGPAYLOAD.oneofs_by_name['payload'] +DESCRIPTOR.message_types_by_name['CmdGetStatus'] = _CMDGETSTATUS +DESCRIPTOR.message_types_by_name['RespGetStatus'] = _RESPGETSTATUS +DESCRIPTOR.message_types_by_name['CmdSetConfig'] = _CMDSETCONFIG +DESCRIPTOR.message_types_by_name['RespSetConfig'] = _RESPSETCONFIG +DESCRIPTOR.message_types_by_name['CmdApplyConfig'] = _CMDAPPLYCONFIG +DESCRIPTOR.message_types_by_name['RespApplyConfig'] = _RESPAPPLYCONFIG +DESCRIPTOR.message_types_by_name['WiFiConfigPayload'] = _WIFICONFIGPAYLOAD +DESCRIPTOR.enum_types_by_name['WiFiConfigMsgType'] = _WIFICONFIGMSGTYPE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +CmdGetStatus = _reflection.GeneratedProtocolMessageType('CmdGetStatus', (_message.Message,), dict( + DESCRIPTOR = _CMDGETSTATUS, + __module__ = 'wifi_config_pb2' + # @@protoc_insertion_point(class_scope:CmdGetStatus) + )) +_sym_db.RegisterMessage(CmdGetStatus) + +RespGetStatus = _reflection.GeneratedProtocolMessageType('RespGetStatus', (_message.Message,), dict( + DESCRIPTOR = _RESPGETSTATUS, + __module__ = 'wifi_config_pb2' + # @@protoc_insertion_point(class_scope:RespGetStatus) + )) +_sym_db.RegisterMessage(RespGetStatus) + +CmdSetConfig = _reflection.GeneratedProtocolMessageType('CmdSetConfig', (_message.Message,), dict( + DESCRIPTOR = _CMDSETCONFIG, + __module__ = 'wifi_config_pb2' + # @@protoc_insertion_point(class_scope:CmdSetConfig) + )) +_sym_db.RegisterMessage(CmdSetConfig) + +RespSetConfig = _reflection.GeneratedProtocolMessageType('RespSetConfig', (_message.Message,), dict( + DESCRIPTOR = _RESPSETCONFIG, + __module__ = 'wifi_config_pb2' + # @@protoc_insertion_point(class_scope:RespSetConfig) + )) +_sym_db.RegisterMessage(RespSetConfig) + +CmdApplyConfig = _reflection.GeneratedProtocolMessageType('CmdApplyConfig', (_message.Message,), dict( + DESCRIPTOR = _CMDAPPLYCONFIG, + __module__ = 'wifi_config_pb2' + # @@protoc_insertion_point(class_scope:CmdApplyConfig) + )) +_sym_db.RegisterMessage(CmdApplyConfig) + +RespApplyConfig = _reflection.GeneratedProtocolMessageType('RespApplyConfig', (_message.Message,), dict( + DESCRIPTOR = _RESPAPPLYCONFIG, + __module__ = 'wifi_config_pb2' + # @@protoc_insertion_point(class_scope:RespApplyConfig) + )) +_sym_db.RegisterMessage(RespApplyConfig) + +WiFiConfigPayload = _reflection.GeneratedProtocolMessageType('WiFiConfigPayload', (_message.Message,), dict( + DESCRIPTOR = _WIFICONFIGPAYLOAD, + __module__ = 'wifi_config_pb2' + # @@protoc_insertion_point(class_scope:WiFiConfigPayload) + )) +_sym_db.RegisterMessage(WiFiConfigPayload) + + +# @@protoc_insertion_point(module_scope) diff --git a/components/wifi_provisioning/python/wifi_constants_pb2.py b/components/wifi_provisioning/python/wifi_constants_pb2.py new file mode 100644 index 000000000..8090568d2 --- /dev/null +++ b/components/wifi_provisioning/python/wifi_constants_pb2.py @@ -0,0 +1,207 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: wifi_constants.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='wifi_constants.proto', + package='', + syntax='proto3', + serialized_pb=_b('\n\x14wifi_constants.proto\"v\n\x12WifiConnectedState\x12\x10\n\x08ip4_addr\x18\x01 \x01(\t\x12 \n\tauth_mode\x18\x02 \x01(\x0e\x32\r.WifiAuthMode\x12\x0c\n\x04ssid\x18\x03 \x01(\x0c\x12\r\n\x05\x62ssid\x18\x04 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x05 \x01(\x05*Y\n\x10WifiStationState\x12\r\n\tConnected\x10\x00\x12\x0e\n\nConnecting\x10\x01\x12\x10\n\x0c\x44isconnected\x10\x02\x12\x14\n\x10\x43onnectionFailed\x10\x03*=\n\x17WifiConnectFailedReason\x12\r\n\tAuthError\x10\x00\x12\x13\n\x0fNetworkNotFound\x10\x01*c\n\x0cWifiAuthMode\x12\x08\n\x04Open\x10\x00\x12\x07\n\x03WEP\x10\x01\x12\x0b\n\x07WPA_PSK\x10\x02\x12\x0c\n\x08WPA2_PSK\x10\x03\x12\x10\n\x0cWPA_WPA2_PSK\x10\x04\x12\x13\n\x0fWPA2_ENTERPRISE\x10\x05\x62\x06proto3') +) + +_WIFISTATIONSTATE = _descriptor.EnumDescriptor( + name='WifiStationState', + full_name='WifiStationState', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='Connected', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='Connecting', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='Disconnected', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ConnectionFailed', index=3, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=144, + serialized_end=233, +) +_sym_db.RegisterEnumDescriptor(_WIFISTATIONSTATE) + +WifiStationState = enum_type_wrapper.EnumTypeWrapper(_WIFISTATIONSTATE) +_WIFICONNECTFAILEDREASON = _descriptor.EnumDescriptor( + name='WifiConnectFailedReason', + full_name='WifiConnectFailedReason', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='AuthError', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NetworkNotFound', index=1, number=1, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=235, + serialized_end=296, +) +_sym_db.RegisterEnumDescriptor(_WIFICONNECTFAILEDREASON) + +WifiConnectFailedReason = enum_type_wrapper.EnumTypeWrapper(_WIFICONNECTFAILEDREASON) +_WIFIAUTHMODE = _descriptor.EnumDescriptor( + name='WifiAuthMode', + full_name='WifiAuthMode', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='Open', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='WEP', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='WPA_PSK', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='WPA2_PSK', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='WPA_WPA2_PSK', index=4, number=4, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='WPA2_ENTERPRISE', index=5, number=5, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=298, + serialized_end=397, +) +_sym_db.RegisterEnumDescriptor(_WIFIAUTHMODE) + +WifiAuthMode = enum_type_wrapper.EnumTypeWrapper(_WIFIAUTHMODE) +Connected = 0 +Connecting = 1 +Disconnected = 2 +ConnectionFailed = 3 +AuthError = 0 +NetworkNotFound = 1 +Open = 0 +WEP = 1 +WPA_PSK = 2 +WPA2_PSK = 3 +WPA_WPA2_PSK = 4 +WPA2_ENTERPRISE = 5 + + + +_WIFICONNECTEDSTATE = _descriptor.Descriptor( + name='WifiConnectedState', + full_name='WifiConnectedState', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='ip4_addr', full_name='WifiConnectedState.ip4_addr', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='auth_mode', full_name='WifiConnectedState.auth_mode', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ssid', full_name='WifiConnectedState.ssid', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bssid', full_name='WifiConnectedState.bssid', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='channel', full_name='WifiConnectedState.channel', index=4, + number=5, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=24, + serialized_end=142, +) + +_WIFICONNECTEDSTATE.fields_by_name['auth_mode'].enum_type = _WIFIAUTHMODE +DESCRIPTOR.message_types_by_name['WifiConnectedState'] = _WIFICONNECTEDSTATE +DESCRIPTOR.enum_types_by_name['WifiStationState'] = _WIFISTATIONSTATE +DESCRIPTOR.enum_types_by_name['WifiConnectFailedReason'] = _WIFICONNECTFAILEDREASON +DESCRIPTOR.enum_types_by_name['WifiAuthMode'] = _WIFIAUTHMODE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +WifiConnectedState = _reflection.GeneratedProtocolMessageType('WifiConnectedState', (_message.Message,), dict( + DESCRIPTOR = _WIFICONNECTEDSTATE, + __module__ = 'wifi_constants_pb2' + # @@protoc_insertion_point(class_scope:WifiConnectedState) + )) +_sym_db.RegisterMessage(WifiConnectedState) + + +# @@protoc_insertion_point(module_scope) diff --git a/components/wifi_provisioning/src/wifi_config.c b/components/wifi_provisioning/src/wifi_config.c new file mode 100644 index 000000000..6ce1798a7 --- /dev/null +++ b/components/wifi_provisioning/src/wifi_config.c @@ -0,0 +1,322 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 + +#include "wifi_constants.pb-c.h" +#include "wifi_config.pb-c.h" + +#include + +static const char* TAG = "WiFiProvConfig"; + +typedef struct wifi_prov_config_cmd { + int cmd_num; + esp_err_t (*command_handler)(WiFiConfigPayload *req, + WiFiConfigPayload *resp, void *priv_data); +} wifi_prov_config_cmd_t; + +static esp_err_t cmd_get_status_handler(WiFiConfigPayload *req, + WiFiConfigPayload *resp, void *priv_data); + +static esp_err_t cmd_set_config_handler(WiFiConfigPayload *req, + WiFiConfigPayload *resp, void *priv_data); + +static esp_err_t cmd_apply_config_handler(WiFiConfigPayload *req, + WiFiConfigPayload *resp, void *priv_data); + +static wifi_prov_config_cmd_t cmd_table[] = { + { + .cmd_num = WI_FI_CONFIG_MSG_TYPE__TypeCmdGetStatus, + .command_handler = cmd_get_status_handler + }, + { + .cmd_num = WI_FI_CONFIG_MSG_TYPE__TypeCmdSetConfig, + .command_handler = cmd_set_config_handler + }, + { + .cmd_num = WI_FI_CONFIG_MSG_TYPE__TypeCmdApplyConfig, + .command_handler = cmd_apply_config_handler + } +}; + +static esp_err_t cmd_get_status_handler(WiFiConfigPayload *req, + WiFiConfigPayload *resp, void *priv_data) +{ + ESP_LOGD(TAG, "Enter cmd_get_status_handler"); + wifi_prov_config_handlers_t *h = (wifi_prov_config_handlers_t *) priv_data; + if (!h) { + ESP_LOGE(TAG, "Command invoked without handlers"); + return ESP_ERR_INVALID_STATE; + } + + RespGetStatus *resp_payload = (RespGetStatus *) malloc(sizeof(RespGetStatus)); + if (!resp_payload) { + ESP_LOGE(TAG, "Error allocating memory"); + return ESP_ERR_NO_MEM; + } + resp_get_status__init(resp_payload); + + wifi_prov_config_get_data_t resp_data; + if (h->get_status_handler(&resp_data) == ESP_OK) { + if (resp_data.wifi_state == WIFI_PROV_STA_CONNECTING) { + resp_payload->sta_state = WIFI_STATION_STATE__Connecting; + resp_payload->state_case = RESP_GET_STATUS__STATE_CONNECTED; + } else if (resp_data.wifi_state == WIFI_PROV_STA_CONNECTED) { + resp_payload->sta_state = WIFI_STATION_STATE__Connected; + resp_payload->state_case = RESP_GET_STATUS__STATE_CONNECTED; + WifiConnectedState *connected = (WifiConnectedState *)( + malloc(sizeof(WifiConnectedState))); + if (!connected) { + ESP_LOGE(TAG, "Error allocating memory"); + return ESP_ERR_NO_MEM; + } + resp_payload->connected = connected; + wifi_connected_state__init(connected); + + connected->ip4_addr = strdup(resp_data.conn_info.ip_addr); + if (connected->ip4_addr == NULL) { + free(resp_payload); + return ESP_ERR_NO_MEM; + } + + connected->bssid.len = sizeof(resp_data.conn_info.bssid); + connected->bssid.data = (uint8_t *) strndup(resp_data.conn_info.bssid, + sizeof(resp_data.conn_info.bssid)); + if (connected->bssid.data == NULL) { + free(connected->ip4_addr); + free(resp_payload); + return ESP_ERR_NO_MEM; + } + + connected->ssid.len = strlen(resp_data.conn_info.ssid); + connected->ssid.data = (uint8_t *) strdup(resp_data.conn_info.ssid); + if (connected->ssid.data == NULL) { + free(connected->bssid.data); + free(connected->ip4_addr); + free(resp_payload); + return ESP_ERR_NO_MEM; + } + + connected->channel = resp_data.conn_info.channel; + connected->auth_mode = resp_data.conn_info.auth_mode; + } else if (resp_data.wifi_state == WIFI_PROV_STA_DISCONNECTED) { + resp_payload->sta_state = WIFI_STATION_STATE__ConnectionFailed; + resp_payload->state_case = RESP_GET_STATUS__STATE_FAIL_REASON; + + if (resp_data.fail_reason == WIFI_PROV_STA_AUTH_ERROR) { + resp_payload->fail_reason = WIFI_CONNECT_FAILED_REASON__AuthError; + } else if (resp_data.fail_reason == WIFI_PROV_STA_AP_NOT_FOUND) { + resp_payload->fail_reason = WIFI_CONNECT_FAILED_REASON__NetworkNotFound; + } + } + resp_payload->status = STATUS__Success; + } + + resp->payload_case = WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_GET_STATUS; + resp->resp_get_status = resp_payload; + return ESP_OK; +} + +static esp_err_t cmd_set_config_handler(WiFiConfigPayload *req, + WiFiConfigPayload *resp, void *priv_data) +{ + ESP_LOGD(TAG, "Enter cmd_set_config_handler"); + wifi_prov_config_handlers_t *h = (wifi_prov_config_handlers_t *) priv_data; + if (!h) { + ESP_LOGE(TAG, "Command invoked without handlers"); + return ESP_ERR_INVALID_STATE; + } + + RespSetConfig *resp_payload = (RespSetConfig *) malloc(sizeof(RespSetConfig)); + if (resp_payload == NULL) { + ESP_LOGE(TAG, "Error allocating memory"); + return ESP_ERR_NO_MEM; + } + resp_set_config__init(resp_payload); + + wifi_prov_config_set_data_t req_data; + memset(&req_data, 0, sizeof(req_data)); + memcpy(req_data.ssid, req->cmd_set_config->ssid.data, + req->cmd_set_config->ssid.len); + memcpy(req_data.password, req->cmd_set_config->passphrase.data, + req->cmd_set_config->passphrase.len); + memcpy(req_data.bssid, req->cmd_set_config->bssid.data, + req->cmd_set_config->bssid.len); + req_data.channel = req->cmd_set_config->channel; + if (h->set_config_handler(&req_data) == ESP_OK) { + resp_payload->status = STATUS__Success; + } + + resp->payload_case = WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_SET_CONFIG; + resp->resp_set_config = resp_payload; + return ESP_OK; +} + +static esp_err_t cmd_apply_config_handler(WiFiConfigPayload *req, + WiFiConfigPayload *resp, void *priv_data) +{ + ESP_LOGD(TAG, "Enter cmd_apply_config_handler"); + wifi_prov_config_handlers_t *h = (wifi_prov_config_handlers_t *) priv_data; + if (!h) { + ESP_LOGE(TAG, "Command invoked without handlers"); + return ESP_ERR_INVALID_STATE; + } + + RespApplyConfig *resp_payload = (RespApplyConfig *) malloc(sizeof(RespApplyConfig)); + if (!resp_payload) { + ESP_LOGE(TAG, "Error allocating memory"); + return ESP_ERR_NO_MEM; + } + + resp_apply_config__init(resp_payload); + + if (h->apply_config_handler() == ESP_OK) { + resp_payload->status = STATUS__Success; + } else { + resp_payload->status = STATUS__InvalidArgument; + } + + resp->payload_case = WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_APPLY_CONFIG; + resp->resp_apply_config = resp_payload; + return ESP_OK; +} + +static int lookup_cmd_handler(int cmd_id) +{ + int i; + + for (i = 0; i < sizeof(cmd_table)/sizeof(wifi_prov_config_cmd_t); i++) { + if (cmd_table[i].cmd_num == cmd_id) { + return i; + } + } + + return -1; +} +static void wifi_prov_config_command_cleanup(WiFiConfigPayload *resp, void *priv_data) +{ + if (!resp) { + return; + } + + switch (resp->msg) { + case WI_FI_CONFIG_MSG_TYPE__TypeRespGetStatus: + { + switch (resp->resp_get_status->sta_state) { + case WIFI_STATION_STATE__Connecting: + break; + case WIFI_STATION_STATE__Connected: + if (resp->resp_get_status->connected) { + if (resp->resp_get_status->connected->ip4_addr) { + free(resp->resp_get_status->connected->ip4_addr); + } + if (resp->resp_get_status->connected->bssid.data) { + free(resp->resp_get_status->connected->bssid.data); + } + if (resp->resp_get_status->connected->ssid.data) { + free(resp->resp_get_status->connected->ssid.data); + } + free(resp->resp_get_status->connected); + } + break; + case WIFI_STATION_STATE__ConnectionFailed: + break; + default: + break; + } + free(resp->resp_get_status); + } + break; + case WI_FI_CONFIG_MSG_TYPE__TypeRespSetConfig: + { + free(resp->resp_set_config); + } + break; + case WI_FI_CONFIG_MSG_TYPE__TypeRespApplyConfig: + { + free(resp->resp_apply_config); + } + break; + default: + ESP_LOGE(TAG, "Unsupported response type in cleanup_handler"); + break; + } + return; +} + +static esp_err_t wifi_prov_config_command_dispatcher(WiFiConfigPayload *req, + WiFiConfigPayload *resp, void *priv_data) +{ + esp_err_t ret; + + ESP_LOGD(TAG, "In wifi_prov_config_command_dispatcher Cmd=%d", req->msg); + + int cmd_index = lookup_cmd_handler(req->msg); + if (cmd_index < 0) { + ESP_LOGE(TAG, "Invalid command handler lookup"); + return ESP_FAIL; + } + + ret = cmd_table[cmd_index].command_handler(req, resp, priv_data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error executing command handler"); + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t wifi_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, void *priv_data) +{ + WiFiConfigPayload *req; + WiFiConfigPayload resp; + esp_err_t ret; + + req = wi_fi_config_payload__unpack(NULL, inlen, inbuf); + if (!req) { + ESP_LOGE(TAG, "Unable to unpack config data"); + return ESP_ERR_INVALID_ARG; + } + + wi_fi_config_payload__init(&resp); + ret = wifi_prov_config_command_dispatcher(req, &resp, priv_data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Proto command dispatcher error %d", ret); + return ESP_FAIL; + } + + resp.msg = req->msg + 1; /* Response is request + 1 */ + wi_fi_config_payload__free_unpacked(req, NULL); + + *outlen = wi_fi_config_payload__get_packed_size(&resp); + if (*outlen <= 0) { + ESP_LOGE(TAG, "Invalid encoding for response"); + return ESP_FAIL; + } + + *outbuf = (uint8_t *) malloc(*outlen); + if (!*outbuf) { + ESP_LOGE(TAG, "System out of memory"); + return ESP_ERR_NO_MEM; + } + wi_fi_config_payload__pack(&resp, *outbuf); + wifi_prov_config_command_cleanup(&resp, priv_data); + + return ESP_OK; +} From 84f094453bf2c39537942d072e88e17217e7843a Mon Sep 17 00:00:00 2001 From: Anurag Kar Date: Mon, 30 Jul 2018 21:40:10 +0530 Subject: [PATCH 6/7] Examples : Added provisioning examples, tests and client tool. * BLE and softAP provisioning examples added along with tests. * An application specific example added for demonstrating custom provisioning scheme. * tools/esp_prov/esp_prov.py can be used as a provisioning client for SoftAP, BLE and CLI based transports. Co-Authored-By: Amey Inamdar Co-Authored-By: Anurag Kar --- .gitlab-ci.yml | 14 +- components/protocomm/proto/README.md | 13 + .../src/transports/protocomm_console.c | 4 + components/wifi_provisioning/proto/README.md | 7 + examples/provisioning/README.md | 19 + examples/provisioning/ble_prov/CMakeLists.txt | 6 + examples/provisioning/ble_prov/Makefile | 9 + examples/provisioning/ble_prov/README.md | 196 +++++++++ .../provisioning/ble_prov/ble_prov_test.py | 113 +++++ .../provisioning/ble_prov/main/CMakeLists.txt | 6 + .../ble_prov/main/Kconfig.projbuild | 34 ++ .../provisioning/ble_prov/main/app_main.c | 114 +++++ .../provisioning/ble_prov/main/app_prov.c | 370 ++++++++++++++++ .../provisioning/ble_prov/main/app_prov.h | 96 +++++ .../ble_prov/main/app_prov_handlers.c | 106 +++++ .../provisioning/ble_prov/main/component.mk | 8 + examples/provisioning/ble_prov/partitions.csv | 5 + .../provisioning/ble_prov/sdkconfig.defaults | 10 + .../provisioning/console_prov/CMakeLists.txt | 6 + examples/provisioning/console_prov/Makefile | 9 + examples/provisioning/console_prov/README.md | 212 ++++++++++ .../console_prov/main/CMakeLists.txt | 6 + .../console_prov/main/Kconfig.projbuild | 34 ++ .../provisioning/console_prov/main/app_main.c | 114 +++++ .../provisioning/console_prov/main/app_prov.c | 328 +++++++++++++++ .../provisioning/console_prov/main/app_prov.h | 96 +++++ .../console_prov/main/app_prov_handlers.c | 106 +++++ .../console_prov/main/component.mk | 8 + .../provisioning/custom_config/CMakeLists.txt | 6 + examples/provisioning/custom_config/Makefile | 9 + examples/provisioning/custom_config/README.md | 144 +++++++ .../custom_provisioning/CMakeLists.txt | 8 + .../custom_provisioning/component.mk | 3 + .../custom_provisioning/custom_config.h | 44 ++ .../proto-c/custom_config.pb-c.c | 229 ++++++++++ .../proto-c/custom_config.pb-c.h | 113 +++++ .../custom_provisioning/proto/README.md | 11 + .../proto/custom_config.proto | 16 + .../custom_provisioning/proto/makefile | 7 + .../python/custom_config_pb2.py | 150 +++++++ .../custom_provisioning/src/custom_config.c | 68 +++ .../custom_config/main/CMakeLists.txt | 6 + .../custom_config/main/Kconfig.projbuild | 52 +++ .../custom_config/main/app_main.c | 115 +++++ .../custom_config/main/app_prov.c | 398 ++++++++++++++++++ .../custom_config/main/app_prov.h | 103 +++++ .../custom_config/main/app_prov_handlers.c | 118 ++++++ .../custom_config/main/component.mk | 8 + .../provisioning/softap_prov/CMakeLists.txt | 6 + examples/provisioning/softap_prov/Makefile | 9 + examples/provisioning/softap_prov/README.md | 141 +++++++ .../softap_prov/main/CMakeLists.txt | 6 + .../softap_prov/main/Kconfig.projbuild | 53 +++ .../provisioning/softap_prov/main/app_main.c | 131 ++++++ .../provisioning/softap_prov/main/app_prov.c | 386 +++++++++++++++++ .../provisioning/softap_prov/main/app_prov.h | 102 +++++ .../softap_prov/main/app_prov_handlers.c | 106 +++++ .../softap_prov/main/component.mk | 8 + .../softap_prov/softap_prov_test.py | 125 ++++++ .../softap_prov/utils/wifi_tools.py | 98 +++++ tools/esp_prov/README.md | 109 +++++ tools/esp_prov/esp_prov.py | 208 +++++++++ tools/esp_prov/proto/__init__.py | 32 ++ tools/esp_prov/prov/__init__.py | 17 + tools/esp_prov/prov/custom_prov.py | 43 ++ tools/esp_prov/prov/wifi_prov.py | 91 ++++ tools/esp_prov/requirements.txt | 3 + tools/esp_prov/requirements_linux_extra.txt | 1 + tools/esp_prov/security/__init__.py | 17 + tools/esp_prov/security/security.py | 21 + tools/esp_prov/security/security0.py | 65 +++ tools/esp_prov/security/security1.py | 164 ++++++++ tools/esp_prov/transport/__init__.py | 18 + tools/esp_prov/transport/ble_cli.py | 202 +++++++++ tools/esp_prov/transport/transport.py | 28 ++ tools/esp_prov/transport/transport_ble.py | 57 +++ tools/esp_prov/transport/transport_console.py | 32 ++ tools/esp_prov/transport/transport_softap.py | 39 ++ tools/esp_prov/utils/__init__.py | 16 + tools/esp_prov/utils/convenience.py | 29 ++ 80 files changed, 6016 insertions(+), 4 deletions(-) create mode 100644 components/protocomm/proto/README.md create mode 100644 components/wifi_provisioning/proto/README.md create mode 100644 examples/provisioning/README.md create mode 100644 examples/provisioning/ble_prov/CMakeLists.txt create mode 100644 examples/provisioning/ble_prov/Makefile create mode 100644 examples/provisioning/ble_prov/README.md create mode 100644 examples/provisioning/ble_prov/ble_prov_test.py create mode 100644 examples/provisioning/ble_prov/main/CMakeLists.txt create mode 100644 examples/provisioning/ble_prov/main/Kconfig.projbuild create mode 100644 examples/provisioning/ble_prov/main/app_main.c create mode 100644 examples/provisioning/ble_prov/main/app_prov.c create mode 100644 examples/provisioning/ble_prov/main/app_prov.h create mode 100644 examples/provisioning/ble_prov/main/app_prov_handlers.c create mode 100644 examples/provisioning/ble_prov/main/component.mk create mode 100644 examples/provisioning/ble_prov/partitions.csv create mode 100644 examples/provisioning/ble_prov/sdkconfig.defaults create mode 100644 examples/provisioning/console_prov/CMakeLists.txt create mode 100644 examples/provisioning/console_prov/Makefile create mode 100644 examples/provisioning/console_prov/README.md create mode 100644 examples/provisioning/console_prov/main/CMakeLists.txt create mode 100644 examples/provisioning/console_prov/main/Kconfig.projbuild create mode 100644 examples/provisioning/console_prov/main/app_main.c create mode 100644 examples/provisioning/console_prov/main/app_prov.c create mode 100644 examples/provisioning/console_prov/main/app_prov.h create mode 100644 examples/provisioning/console_prov/main/app_prov_handlers.c create mode 100644 examples/provisioning/console_prov/main/component.mk create mode 100644 examples/provisioning/custom_config/CMakeLists.txt create mode 100644 examples/provisioning/custom_config/Makefile create mode 100644 examples/provisioning/custom_config/README.md create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/CMakeLists.txt create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/component.mk create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.c create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.h create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/proto/README.md create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/proto/custom_config.proto create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/proto/makefile create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py create mode 100644 examples/provisioning/custom_config/components/custom_provisioning/src/custom_config.c create mode 100644 examples/provisioning/custom_config/main/CMakeLists.txt create mode 100644 examples/provisioning/custom_config/main/Kconfig.projbuild create mode 100644 examples/provisioning/custom_config/main/app_main.c create mode 100644 examples/provisioning/custom_config/main/app_prov.c create mode 100644 examples/provisioning/custom_config/main/app_prov.h create mode 100644 examples/provisioning/custom_config/main/app_prov_handlers.c create mode 100644 examples/provisioning/custom_config/main/component.mk create mode 100644 examples/provisioning/softap_prov/CMakeLists.txt create mode 100644 examples/provisioning/softap_prov/Makefile create mode 100644 examples/provisioning/softap_prov/README.md create mode 100644 examples/provisioning/softap_prov/main/CMakeLists.txt create mode 100644 examples/provisioning/softap_prov/main/Kconfig.projbuild create mode 100644 examples/provisioning/softap_prov/main/app_main.c create mode 100644 examples/provisioning/softap_prov/main/app_prov.c create mode 100644 examples/provisioning/softap_prov/main/app_prov.h create mode 100644 examples/provisioning/softap_prov/main/app_prov_handlers.c create mode 100644 examples/provisioning/softap_prov/main/component.mk create mode 100644 examples/provisioning/softap_prov/softap_prov_test.py create mode 100644 examples/provisioning/softap_prov/utils/wifi_tools.py create mode 100644 tools/esp_prov/README.md create mode 100644 tools/esp_prov/esp_prov.py create mode 100644 tools/esp_prov/proto/__init__.py create mode 100644 tools/esp_prov/prov/__init__.py create mode 100644 tools/esp_prov/prov/custom_prov.py create mode 100644 tools/esp_prov/prov/wifi_prov.py create mode 100644 tools/esp_prov/requirements.txt create mode 100644 tools/esp_prov/requirements_linux_extra.txt create mode 100644 tools/esp_prov/security/__init__.py create mode 100644 tools/esp_prov/security/security.py create mode 100644 tools/esp_prov/security/security0.py create mode 100644 tools/esp_prov/security/security1.py create mode 100644 tools/esp_prov/transport/__init__.py create mode 100644 tools/esp_prov/transport/ble_cli.py create mode 100644 tools/esp_prov/transport/transport.py create mode 100644 tools/esp_prov/transport/transport_ble.py create mode 100644 tools/esp_prov/transport/transport_console.py create mode 100644 tools/esp_prov/transport/transport_softap.py create mode 100644 tools/esp_prov/utils/__init__.py create mode 100644 tools/esp_prov/utils/convenience.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 70c31f8c4..f3f3fbe6d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,10 +42,10 @@ variables: # are removed. .git_clean_stale_submodules: &git_clean_stale_submodules > find . -name '.git' -not -path './.git' -printf '%P\n' - | sed 's|/.git||' + | sed 's|/.git||' | xargs -I {} sh -c ' - grep -q {} .gitmodules - || (echo "Removing {}, has .git directory but not in .gitmodules file" + grep -q {} .gitmodules + || (echo "Removing {}, has .git directory but not in .gitmodules file" && rm -rf {});' # before each job, we need to check if this job is filtered by bot stage/job filter @@ -326,7 +326,7 @@ verify_cmake_style: tags: - host_test dependencies: [] - + test_nvs_on_host: <<: *host_test_template script: @@ -822,6 +822,12 @@ example_test_004_01: - ESP32 - Example_CAN +example_test_005_01: + <<: *example_test_template + tags: + - ESP32 + - Example_WIFI_BT + UT_001_01: <<: *unit_test_template tags: diff --git a/components/protocomm/proto/README.md b/components/protocomm/proto/README.md new file mode 100644 index 000000000..655e193a3 --- /dev/null +++ b/components/protocomm/proto/README.md @@ -0,0 +1,13 @@ +# Protobuf files for implementing protocol communication packets + +Protocomm uses Google Protobuf for language, transport and architecture agnostic protocol communication. These proto files define the protocomm packet structure, separated across multiple files: +* contants.proto - Defines the "Status" structure for conveying the success or failure of a single protocomm transaction +* sec0.proto - Defines the Security0 Command and Response packet structures +* sec1.proto - Defines the Security1 Command and Response packet structures +* session.proto - Defines the protocomm transacion packets for session establishment which internally has a Security packet as payload + +Note : These proto files are not automatically compiled during the build process. + +Run "make" (Optional) to generate the respective C and Python files. The generated C files are used by protocomm itself to create, delete and manipulate transaction packets. The generated Python files can be used by python based applications for implementing client side interface to protocomm layer. + +Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under "protocomm/proto-c" and "protocomm/python" directories, and thus running make (and installing the Protobuf compilers) is optional. diff --git a/components/protocomm/src/transports/protocomm_console.c b/components/protocomm/src/transports/protocomm_console.c index 859c3395a..c7cf8c921 100644 --- a/components/protocomm/src/transports/protocomm_console.c +++ b/components/protocomm/src/transports/protocomm_console.c @@ -134,6 +134,10 @@ static void protocomm_console_task(void *arg) static int common_cmd_handler(int argc, char** argv) { + if (argc < 3) { + return ESP_ERR_INVALID_ARG; + } + int i, ret; uint32_t cur_session_id = atoi(argv[1]); diff --git a/components/wifi_provisioning/proto/README.md b/components/wifi_provisioning/proto/README.md new file mode 100644 index 000000000..10c428be7 --- /dev/null +++ b/components/wifi_provisioning/proto/README.md @@ -0,0 +1,7 @@ +# Protobuf files for defining Wi-Fi config-data packet structures + +Note : These proto files are not automatically compiled during the build process. + +Run "make" (Optional) to generate the respective C and Python files. The generated C files are used by protocomm itself to create, delete and manipulate transaction packets. The generated Python files can be used by python based applications for implementing client side interface to protocomm layer. + +Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under "protocomm/proto-c" and "protocomm/python" directories, and thus running make (and installing the Protobuf compilers) is optional. diff --git a/examples/provisioning/README.md b/examples/provisioning/README.md new file mode 100644 index 000000000..d265300e3 --- /dev/null +++ b/examples/provisioning/README.md @@ -0,0 +1,19 @@ +# Provisioning Application Examples + +These consist of the following examples : + +* softap_prov + Provisioning involves Wi-Fi station configuration via an HTTP server running on the device, which is initially configured to be in SoftAP mode. After provisioning, device runs in Wi-Fi station mode only and connects to the AP whose credentials were provided during provisioning. + +* ble_prov + Provisioning involves Wi-Fi station configuration via BLE service endpoints running on the device initially. After provisioning, BLE is turned off and device runs in Wi-Fi station mode, connecting to the AP whose credentials were provided during provisioning. + +* console_prov + Provisioning involves Wi-Fi station configuration via UART console. This is intended for debugging protocomm and provisioning related features. + +* custom_config + Similar to softap_prov examples, but allows for configuration of custom (device-local) information during provisioning. This is intended as an example for implementing custom provisioning schemes. + +A python based utility is provided under "$IDF_PATH/tools/esp_prov" for testing the examples over a host. + +Refer to the README.md files in each example directory for more information. diff --git a/examples/provisioning/ble_prov/CMakeLists.txt b/examples/provisioning/ble_prov/CMakeLists.txt new file mode 100644 index 000000000..30012d3e4 --- /dev/null +++ b/examples/provisioning/ble_prov/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_prov) diff --git a/examples/provisioning/ble_prov/Makefile b/examples/provisioning/ble_prov/Makefile new file mode 100644 index 000000000..ccd2a96b4 --- /dev/null +++ b/examples/provisioning/ble_prov/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_prov + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/provisioning/ble_prov/README.md b/examples/provisioning/ble_prov/README.md new file mode 100644 index 000000000..8b61235a8 --- /dev/null +++ b/examples/provisioning/ble_prov/README.md @@ -0,0 +1,196 @@ +# BLE based Provisioning Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +`ble_prov` example demonstrates the implementation and integration of various IDF components for building a provisioning application. + +For this example BLE is chosen as the mode of transport, over which the provisioning related communication is to take place, between the device (to be provisioned) and the client (owner of the device). + +In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed. + +Right after provisioning is complete, BLE is turned off and disabled to free the memory used by the BLE stack. Though, that is specific to this example, and the user can choose to keep BLE on in their own application. + +`ble_prov` uses the following components : +* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration +* `protocomm` : for protocol based communication and secure session establishment +* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures +* `bt` : ESP32 BLE stack for transport of protobuf packets + +This example can be used, as it is, for adding a provisioning service to any application intended for IoT. + +## How to use example + +### Hardware Required + +Example should be able to run on any commonly available ESP32 development board. + +### Application Required + +To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). Make sure to satisfy all the dependencies prior to running the script. + +Presently, `esp_prov` supports BLE transport only for Linux platform. For Windows/macOS it falls back to console mode and requires another application (for BLE) through which the communication can take place. + +There are various applications, specific to Windows and macOS platform which can be used. The `esp_prov` console will guide you through the provisioning process of locating the correct BLE GATT services and characteristics, the values to write, and input read values. + +For android, a provisioning tool along with source code is available [here](https://github.com/espressif/esp-idf-provisioning-android). This offers a simpler and more close to actual user experience. + +### Configure the project + +``` +make menuconfig +``` + +* Set serial port under Serial Flasher Options. + +* Under Example Configuration set the following : + * Security Version (default 1) + * Proof of Possession (default "abcd1234") + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (550) app: Starting BLE provisioning +I (1130) app_prov: Provisioning started with BLE devname : PROV_261FCC +``` + +Make sure to note down the BLE device name (starting with PROV_) displayed in the serial monitor log (eg. PROV_261FCC). This will depend on the MAC ID and will be unique for every device. + +In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration : + +``` +python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport ble --ble_devname PROV_261FCC +``` + +Above command will perform the provisioning steps, and the monitor log should display something like this : + +``` +I (682950) app_prov_handler: WiFi Credentials Received : + ssid : myssid + password : mypassword +. +. +. +I (683130) app_prov: STA Start +I (683130) app_prov_handler: WiFi Credentials Applied +. +. +. +I (688270) app_prov_handler: Connecting state +. +. +. +I (688390) app_prov: STA Got IP +I (688390) app: got ip:192.168.43.220 +I (693410) app_prov_handler: Connected state +``` + +After sometime the provisioning app will exit and BLE will be turned off + +``` +I (718390) app_prov: Stopping provisioning +I (718670) app_prov: Provisioning stopped +``` + +## Troubleshooting + +### Provisioning failed + +It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason : + +``` +E (39291) app_prov: STA Disconnected +E (39291) app_prov: Disconnect reason : 201 +I (39291) app_prov: STA AP Not found +I (42021) app_prov_handler: Disconnected state +``` + +### Provisioning does not start + +If the serial monitor log is different, as shown below : + +``` +I (539) app_prov: Found ssid myssid +I (539) app_prov: Found password mypassword +I (549) app: Starting WiFi station +``` + +It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example + +``` +make erase_flash +make -j4 flash monitor +``` + +Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution. + +### Unsupported platform + +If the platform requirement, for running `esp_prov` is not satisfied, then the script execution will fallback to console mode, in which case the full process (involving user inputs) will look like this : + +``` +==== Esp_Prov Version: V0.1 ==== +BLE client is running in console mode + This could be due to your platform not being supported or dependencies not being met + Please ensure all pre-requisites are met to run the full fledged client +BLECLI >> Please connect to BLE device `PROV_261FCC` manually using your tool of choice +BLECLI >> Was the device connected successfully? [y/n] y +BLECLI >> List available attributes of the connected device +BLECLI >> Is the service UUID '0000ffff-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y +BLECLI >> Is the characteristic UUID '0000ff53-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y +BLECLI >> Is the characteristic UUID '0000ff51-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y +BLECLI >> Is the characteristic UUID '0000ff52-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y + +==== Verifying protocol version ==== +BLECLI >> Write following data to characteristic with UUID '0000ff53-0000-1000-8000-00805f9b34fb' : + >> 56302e31 +BLECLI >> Enter data read from characteristic (in hex) : + << 53554343455353 +==== Verified protocol version successfully ==== + +==== Starting Session ==== +BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' : + >> 10015a25a201220a20ae6d9d5d1029f8c366892252d2d5a0ffa7ce1ee5829312545dd5f2aba057294d +BLECLI >> Enter data read from characteristic (in hex) : + << 10015a390801aa0134122048008bfc365fad4753dc75912e0c764d60749cb26dd609595b6fbc72e12614031a1089733af233c7448e7d7fb7963682c6d8 +BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' : + >> 10015a270802b2012212204051088dc294fe4621fac934a8ea22e948fcc3e8ac458aac088ce705c65dbfb9 +BLECLI >> Enter data read from characteristic (in hex) : + << 10015a270803ba01221a20c8d38059d5206a3d92642973ac6ba8ac2f6ecf2b7a3632964eb35a0f20133adb +==== Session Established ==== + +==== Sending Wifi credential to esp32 ==== +BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : + >> 98471ac4019a46765c28d87df8c8ae71c1ae6cfe0bc9c615bc6d2c +BLECLI >> Enter data read from characteristic (in hex) : + << 3271f39a +==== Wifi Credentials sent successfully ==== + +==== Applying config to esp32 ==== +BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : + >> 5355 +BLECLI >> Enter data read from characteristic (in hex) : + << 1664db24 +==== Apply config sent successfully ==== + +==== Wifi connection state ==== +BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : + >> 290d +BLECLI >> Enter data read from characteristic (in hex) : + << 505f72a9f8521025c1964d7789c4d7edc56aedebd144e1b667bc7c0975757b80cc091aa9f3e95b06eaefbc30290fa1 +++++ WiFi state: connected ++++ +==== Provisioning was successful ==== +``` + +The write data is to be copied from the console output ```>>``` to the platform specific application and the data read from the application is to be pasted at the user input prompt ```<<``` of the console, in the format (hex) indicated in above sample log. diff --git a/examples/provisioning/ble_prov/ble_prov_test.py b/examples/provisioning/ble_prov/ble_prov_test.py new file mode 100644 index 000000000..537dc9309 --- /dev/null +++ b/examples/provisioning/ble_prov/ble_prov_test.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. + +from __future__ import print_function +import imp +import re +import os +import sys +import time + +# This environment variable is expected on the host machine +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +# When running on local machine execute the following before running this script +# > export TEST_FW_PATH='~/esp/esp-idf/tools/tiny-test-fw' +# > make print_flash_cmd | tail -n 1 > build/download.config +# > make app bootloader + +import TinyFW +import IDF + +# Import esp_prov tool +idf_path = os.environ['IDF_PATH'] +esp_prov = imp.load_source("esp_prov", idf_path + "/tools/esp_prov/esp_prov.py") + +@IDF.idf_example_test(env_tag="Example_WIFI_BT") +def test_examples_provisioning_ble(env, extra_data): + # Acquire DUT + dut1 = env.get_dut("ble_prov", "examples/provisioning/ble_prov") + + # Get binary file + binary_file = os.path.join(dut1.app.binary_path, "ble_prov.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("ble_prov_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("ble_prov_bin_size", bin_size//1024) + + # Upload binary and start testing + dut1.start_app() + + # Parse BLE devname + devname = dut1.expect(re.compile(r"(?:[\s\S]*) Provisioning started with BLE devname : (PROV_\S\S\S\S\S\S)"))[0] + print("BLE Device Alias for DUT :", devname) + + # Match additional headers sent in the request + dut1.expect("BLE Provisioning started") + + print("Starting Provisioning") + verbose = False + protover = "V0.1" + secver = 1 + pop = "abcd1234" + provmode = "ble" + ap_ssid = "myssid" + ap_password = "mypassword" + + print("Getting security") + security = esp_prov.get_security(secver, pop, verbose) + if security == None: + raise RuntimeError("Failed to get security") + + print("Getting transport") + transport = esp_prov.get_transport(provmode, None, devname) + if transport == None: + raise RuntimeError("Failed to get transport") + + print("Verifying protocol version") + if not esp_prov.version_match(transport, protover): + raise RuntimeError("Mismatch in protocol version") + + print("Starting Session") + if not esp_prov.establish_session(transport, security): + raise RuntimeError("Failed to start session") + + print("Sending Wifi credential to DUT") + if not esp_prov.send_wifi_config(transport, security, ap_ssid, ap_password): + raise RuntimeError("Failed to send Wi-Fi config") + + print("Applying config") + if not esp_prov.apply_wifi_config(transport, security): + raise RuntimeError("Failed to send apply config") + + success = False + while True: + time.sleep(5) + print("Wi-Fi connection state") + ret = esp_prov.get_wifi_config(transport, security) + if (ret == 1): + continue + elif (ret == 0): + print("Provisioning was successful") + success = True + break + + if not success: + raise RuntimeError("Provisioning failed") + +if __name__ == '__main__': + test_examples_provisioning_ble() diff --git a/examples/provisioning/ble_prov/main/CMakeLists.txt b/examples/provisioning/ble_prov/main/CMakeLists.txt new file mode 100644 index 000000000..3c44a1a36 --- /dev/null +++ b/examples/provisioning/ble_prov/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCS "app_main.c" + "app_prov.c" + "app_prov_handlers.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/provisioning/ble_prov/main/Kconfig.projbuild b/examples/provisioning/ble_prov/main/Kconfig.projbuild new file mode 100644 index 000000000..b669a1e5a --- /dev/null +++ b/examples/provisioning/ble_prov/main/Kconfig.projbuild @@ -0,0 +1,34 @@ +menu "Example Configuration" + +config USE_SEC_1 + bool + default y + prompt "Use Security Version 1" + help + Security version 1 used Curve25519 key exchange for establishing + secure session between device and client during provisioning + +config USE_POP + bool + depends on USE_SEC_1 + default y + prompt "Use proof-of-possession" + help + Proof-of-possession can be optionally used to prove that the device is indeed + in possession of the user who is provisioning the device. This proof-of-possession + is internally used to generate the shared secret through key exchange. + +config POP + string "Proof-of-possession" + default "abcd1234" + depends on USE_POP + +config RESET_PROVISIONED + bool + default n + prompt "Reset provisioned status of the device" + help + This erases the NVS to reset provisioned status of the device on every reboot. + Provisioned status is determined by the WiFi STA configuration, saved on the NVS. + +endmenu diff --git a/examples/provisioning/ble_prov/main/app_main.c b/examples/provisioning/ble_prov/main/app_main.c new file mode 100644 index 000000000..bcb11a8a3 --- /dev/null +++ b/examples/provisioning/ble_prov/main/app_main.c @@ -0,0 +1,114 @@ +/* BLE based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "app_prov.h" + +static const char *TAG = "app"; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + /* Invoke Provisioning event handler first */ + app_prov_event_handler(ctx, event); + + switch(event->event_id) { + case SYSTEM_EVENT_AP_START: + ESP_LOGI(TAG, "SoftAP started"); + break; + case SYSTEM_EVENT_AP_STOP: + ESP_LOGI(TAG, "SoftAP stopped"); + break; + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:"MACSTR" join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:"MACSTR"leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + break; + default: + break; + } + return ESP_OK; +} + +static void wifi_init_sta() +{ + /* Start wifi in station mode with credentials set during provisioning */ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_start() ); +} + +void app_main() +{ + /* Security version */ + int security = 0; + /* Proof of possession */ + const protocomm_security_pop_t *pop = NULL; + +#ifdef CONFIG_USE_SEC_1 + security = 1; +#endif + + /* Having proof of possession is optional */ +#ifdef CONFIG_USE_POP + const static protocomm_security_pop_t app_pop = { + .data = (uint8_t *) CONFIG_POP, + .len = (sizeof(CONFIG_POP)-1) + }; + pop = &app_pop; +#endif + + /* Initialize networking stack */ + tcpip_adapter_init(); + + /* Set our event handling */ + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + + /* Check if device is provisioned */ + bool provisioned; + if (app_prov_is_provisioned(&provisioned) != ESP_OK) { + ESP_LOGE(TAG, "Error getting device provisioning state"); + return; + } + + if (provisioned == false) { + /* If not provisioned, start provisioning via BLE */ + ESP_LOGI(TAG, "Starting BLE provisioning"); + app_prov_start_ble_provisioning(security, pop); + } else { + /* Else start as station with credentials set during provisioning */ + ESP_LOGI(TAG, "Starting WiFi station"); + wifi_init_sta(NULL); + } +} diff --git a/examples/provisioning/ble_prov/main/app_prov.c b/examples/provisioning/ble_prov/main/app_prov.c new file mode 100644 index 000000000..b574513ce --- /dev/null +++ b/examples/provisioning/ble_prov/main/app_prov.c @@ -0,0 +1,370 @@ +/* BLE based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "app_prov.h" + +static const char *TAG = "app_prov"; +static const char *ssid_prefix = "PROV_"; + +/* Handlers for wifi_config provisioning endpoint */ +extern wifi_prov_config_handlers_t wifi_prov_handlers; + +/** + * @brief Data relevant to provisioning application + */ +struct app_prov_data { + protocomm_t *pc; /*!< Protocomm handler */ + int security; /*!< Type of security to use with protocomm */ + const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */ + esp_timer_handle_t timer; /*!< Handle to timer */ + + /* State of WiFi Station */ + wifi_prov_sta_state_t wifi_state; + + /* Code for WiFi station disconnection (if disconnected) */ + wifi_prov_sta_fail_reason_t wifi_disconnect_reason; +}; + +/* Pointer to provisioning application data */ +static struct app_prov_data *g_prov; + +static esp_err_t app_prov_start_service(void) +{ + /* Create new protocomm instance */ + g_prov->pc = protocomm_new(); + if (g_prov->pc == NULL) { + ESP_LOGE(TAG, "Failed to create new protocomm instance"); + return ESP_FAIL; + } + + /* Endpoint UUIDs */ + protocomm_ble_name_uuid_t nu_lookup_table[] = { + {"prov-session", 0xFF51}, + {"prov-config", 0xFF52}, + {"proto-ver", 0xFF53}, + }; + + /* Config for protocomm_ble_start() */ + protocomm_ble_config_t config = { + .service_uuid = { + /* LSB <--------------------------------------- + * ---------------------------------------> MSB */ + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, + }, + .nu_lookup_count = sizeof(nu_lookup_table)/sizeof(nu_lookup_table[0]), + .nu_lookup = nu_lookup_table + }; + uint8_t eth_mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, eth_mac); + snprintf(config.device_name, sizeof(config.device_name), "%s%02X%02X%02X", + ssid_prefix, eth_mac[3], eth_mac[4], eth_mac[5]); + + /* Release BT memory, as we need only BLE */ + esp_err_t err = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (err) { + ESP_LOGE(TAG, "bt_controller_mem_release failed %d", err); + if (err != ESP_ERR_INVALID_STATE) { + return err; + } + } + + /* Start protocomm layer on top of BLE */ + if (protocomm_ble_start(g_prov->pc, &config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start BLE provisioning"); + return ESP_FAIL; + } + + /* Set protocomm version verification endpoint for protocol */ + protocomm_set_version(g_prov->pc, "proto-ver", "V0.1"); + + /* Set protocomm security type for endpoint */ + if (g_prov->security == 0) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL); + } else if (g_prov->security == 1) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop); + } + + /* Add endpoint for provisioning to set wifi station config */ + if (protocomm_add_endpoint(g_prov->pc, "prov-config", + wifi_prov_config_data_handler, + (void *) &wifi_prov_handlers) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set provisioning endpoint"); + protocomm_ble_stop(g_prov->pc); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Provisioning started with BLE devname : %s", config.device_name); + return ESP_OK; +} + +static void app_prov_stop_service(void) +{ + /* Remove provisioning endpoint */ + protocomm_remove_endpoint(g_prov->pc, "prov-config"); + /* Unset provisioning security */ + protocomm_unset_security(g_prov->pc, "prov-session"); + /* Unset provisioning version endpoint */ + protocomm_unset_version(g_prov->pc, "proto-ver"); + /* Stop protocomm ble service */ + protocomm_ble_stop(g_prov->pc); + /* Delete protocomm instance */ + protocomm_delete(g_prov->pc); + + /* Release memory used by BT stack */ + esp_bt_mem_release(ESP_BT_MODE_BTDM); +} + +/* Task spawned by timer callback */ +static void stop_prov_task(void * arg) +{ + ESP_LOGI(TAG, "Stopping provisioning"); + app_prov_stop_service(); + + /* Timer not needed anymore */ + esp_timer_handle_t timer = g_prov->timer; + esp_timer_delete(timer); + g_prov->timer = NULL; + + /* Free provisioning process data */ + free(g_prov); + g_prov = NULL; + ESP_LOGI(TAG, "Provisioning stopped"); + + vTaskDelete(NULL); +} + +/* Callback to be invoked by timer */ +static void _stop_prov_cb(void * arg) +{ + xTaskCreate(&stop_prov_task, "stop_prov", 2048, NULL, tskIDLE_PRIORITY, NULL); +} + +/* Event handler for starting/stopping provisioning. + * To be called from within the context of the main + * event handler. + */ +esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) +{ + /* For accessing reason codes in case of disconnection */ + system_event_info_t *info = &event->event_info; + + /* If pointer to provisioning application data is NULL + * then provisioning is not running, therefore return without + * error */ + if (!g_prov) { + return ESP_OK; + } + + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + ESP_LOGI(TAG, "STA Start"); + /* Once configuration is received through protocomm, + * device is started as station. Once station starts, + * wait for connection to establish with configured + * host SSID and password */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + break; + + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "STA Got IP"); + /* Station got IP. That means configuraion is successful. + * Schedule timer to stop provisioning app after 30 seconds. */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTED; + if (g_prov && g_prov->timer) { + esp_timer_start_once(g_prov->timer, 30000*1000U); + } + break; + + case SYSTEM_EVENT_STA_DISCONNECTED: + ESP_LOGE(TAG, "STA Disconnected"); + /* Station couldn't connect to configured host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED; + ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason); + + /* Set code corresponding to the reason for disconnection */ + switch (info->disconnected.reason) { + case WIFI_REASON_AUTH_EXPIRE: + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + case WIFI_REASON_BEACON_TIMEOUT: + case WIFI_REASON_AUTH_FAIL: + case WIFI_REASON_ASSOC_FAIL: + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ESP_LOGI(TAG, "STA Auth Error"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR; + break; + case WIFI_REASON_NO_AP_FOUND: + ESP_LOGI(TAG, "STA AP Not found"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND; + break; + default: + /* If none of the expected reasons, + * retry connecting to host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + esp_wifi_connect(); + } + break; + + default: + break; + } + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state) +{ + if (g_prov == NULL || state == NULL) { + return ESP_FAIL; + } + + *state = g_prov->wifi_state; + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason) +{ + if (g_prov == NULL || reason == NULL) { + return ESP_FAIL; + } + + if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) { + return ESP_FAIL; + } + + *reason = g_prov->wifi_disconnect_reason; + return ESP_OK; +} + +esp_err_t app_prov_is_provisioned(bool *provisioned) +{ + *provisioned = false; + +#ifdef CONFIG_RESET_PROVISIONED + nvs_flash_erase(); +#endif + + if (nvs_flash_init() != ESP_OK) { + ESP_LOGE(TAG, "Failed to init NVS"); + return ESP_FAIL; + } + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + if (esp_wifi_init(&cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to init wifi"); + return ESP_FAIL; + } + + /* Get WiFi Station configuration */ + wifi_config_t wifi_cfg; + if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) { + return ESP_FAIL; + } + + if (strlen((const char*) wifi_cfg.sta.ssid)) { + *provisioned = true; + ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid); + ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password); + } + return ESP_OK; +} + +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg) +{ + /* Initialize WiFi with default config */ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + if (esp_wifi_init(&cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to init WiFi"); + return ESP_FAIL; + } + /* Configure WiFi as station */ + if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode"); + return ESP_FAIL; + } + /* Configure WiFi station with host credentials + * provided during provisioning */ + if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi configuration"); + return ESP_FAIL; + } + /* Start WiFi */ + if (esp_wifi_start() != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi configuration"); + return ESP_FAIL; + } + /* Connect to AP */ + if (esp_wifi_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect WiFi"); + return ESP_FAIL; + } + + if (g_prov) { + /* Reset wifi station state for provisioning app */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + } + return ESP_OK; +} + +esp_err_t app_prov_start_ble_provisioning(int security, const protocomm_security_pop_t *pop) +{ + /* If provisioning app data present, + * means provisioning app is already running */ + if (g_prov) { + ESP_LOGI(TAG, "Invalid provisioning state"); + return ESP_FAIL; + } + + /* Allocate memory for provisioning app data */ + g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data)); + if (!g_prov) { + ESP_LOGI(TAG, "Unable to allocate prov data"); + return ESP_ERR_NO_MEM; + } + + /* Initialise app data */ + g_prov->pop = pop; + g_prov->security = security; + + /* Create timer object as a member of app data */ + esp_timer_create_args_t timer_conf = { + .callback = _stop_prov_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "stop_ble_tm" + }; + esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to create timer"); + return err; + } + + /* Start provisioning service through BLE */ + err = app_prov_start_service(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Provisioning failed to start"); + return err; + } + + ESP_LOGI(TAG, "BLE Provisioning started"); + return ESP_OK; +} diff --git a/examples/provisioning/ble_prov/main/app_prov.h b/examples/provisioning/ble_prov/main/app_prov.h new file mode 100644 index 000000000..01b9ba07c --- /dev/null +++ b/examples/provisioning/ble_prov/main/app_prov.h @@ -0,0 +1,96 @@ +/* BLE based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#pragma once + +#include + +#include +#include + +/** + * @brief Get state of WiFi Station during provisioning + * + * @note WiFi is initially configured as AP, when + * provisioning starts. After provisioning data + * is provided by user, the WiFi is reconfigured + * to run as both AP and Station. + * + * @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi state + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state); + +/** + * @brief Get reason code in case of WiFi station + * disconnection during provisioning + * +* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi disconnect reason + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason); + +/** + * @brief Event handler for provisioning app + * + * This is called from the main event handler and controls the + * provisioning application, depeding on WiFi events + * + * @param[in] ctx Event context data + * @param[in] event Event info + * + * @return + * - ESP_OK : Event handled successfully + * - ESP_FAIL : Failed to start server on event AP start + */ +esp_err_t app_prov_event_handler(void *ctx, system_event_t *event); + +/** + * @brief Checks if device is provisioned + * * + * @param[out] provisioned True if provisioned, else false + * + * @return + * - ESP_OK : Retrieved provision state successfully + * - ESP_FAIL : Failed to retrieve provision state + */ +esp_err_t app_prov_is_provisioned(bool *provisioned); + +/** + * @brief Runs WiFi as Station + * + * Configures the WiFi station mode to connect to the + * SSID and password specified in config structure, + * and starts WiFi to run as station + * + * @param[in] wifi_cfg Pointer to WiFi cofiguration structure + * + * @return + * - ESP_OK : WiFi configured and started successfully + * - ESP_FAIL : Failed to set configuration + */ +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg); + +/** + * @brief Start provisioning via Bluetooth + * + * @param[in] security Security mode + * @param[in] pop Pointer to proof of possession (NULL if not present) + * + * @return + * - ESP_OK : Provisioning started successfully + * - ESP_FAIL : Failed to start + */ +esp_err_t app_prov_start_ble_provisioning(int security, const protocomm_security_pop_t *pop); diff --git a/examples/provisioning/ble_prov/main/app_prov_handlers.c b/examples/provisioning/ble_prov/main/app_prov_handlers.c new file mode 100644 index 000000000..ca6726949 --- /dev/null +++ b/examples/provisioning/ble_prov/main/app_prov_handlers.c @@ -0,0 +1,106 @@ +/* BLE based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* This file is mostly a boiler-plate code that applications can use without much change */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "app_prov.h" + +static const char* TAG = "app_prov_handler"; + +static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data) +{ + /* Initialise to zero */ + memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t)); + + if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) { + ESP_LOGW(TAG, "Prov app not running"); + return ESP_FAIL; + } + + if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) { + ESP_LOGI(TAG, "Connected state"); + + /* IP Addr assigned to STA */ + tcpip_adapter_ip_info_t ip_info; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); + char *ip_addr = ip4addr_ntoa(&ip_info.ip); + strcpy(resp_data->conn_info.ip_addr, ip_addr); + + /* AP information to which STA is connected */ + wifi_ap_record_t ap_info; + esp_wifi_sta_get_ap_info(&ap_info); + memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid)); + memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid)); + resp_data->conn_info.channel = ap_info.primary; + resp_data->conn_info.auth_mode = ap_info.authmode; + } else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Disconnected state"); + + /* If disconnected, convey reason */ + app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason); + } else { + ESP_LOGI(TAG, "Connecting state"); + } + return ESP_OK; +} + +static wifi_config_t *wifi_cfg; + +static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data) +{ + if (wifi_cfg) { + free(wifi_cfg); + wifi_cfg = NULL; + } + + wifi_cfg = (wifi_config_t *) calloc(1, sizeof(wifi_config_t)); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Unable to alloc wifi config"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s", + req_data->ssid, req_data->password); + memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid, + strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid))); + memcpy((char *) wifi_cfg->sta.password, req_data->password, + strnlen(req_data->password, sizeof(wifi_cfg->sta.password))); + return ESP_OK; +} + +static esp_err_t apply_config_handler(void) +{ + if (!wifi_cfg) { + ESP_LOGE(TAG, "WiFi config not set"); + return ESP_FAIL; + } + + app_prov_configure_sta(wifi_cfg); + ESP_LOGI(TAG, "WiFi Credentials Applied"); + + free(wifi_cfg); + wifi_cfg = NULL; + return ESP_OK; +} + +wifi_prov_config_handlers_t wifi_prov_handlers = { + .get_status_handler = get_status_handler, + .set_config_handler = set_config_handler, + .apply_config_handler = apply_config_handler, +}; diff --git a/examples/provisioning/ble_prov/main/component.mk b/examples/provisioning/ble_prov/main/component.mk new file mode 100644 index 000000000..61f8990c3 --- /dev/null +++ b/examples/provisioning/ble_prov/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/provisioning/ble_prov/partitions.csv b/examples/provisioning/ble_prov/partitions.csv new file mode 100644 index 000000000..e382463f9 --- /dev/null +++ b/examples/provisioning/ble_prov/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1200000, diff --git a/examples/provisioning/ble_prov/sdkconfig.defaults b/examples/provisioning/ble_prov/sdkconfig.defaults new file mode 100644 index 000000000..47e578314 --- /dev/null +++ b/examples/provisioning/ble_prov/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Override some defaults so BT stack is enabled and +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y +CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY= +CONFIG_BTDM_CONTROLLER_MODE_BTDM= + +# Binary is larger than default size +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" diff --git a/examples/provisioning/console_prov/CMakeLists.txt b/examples/provisioning/console_prov/CMakeLists.txt new file mode 100644 index 000000000..654200588 --- /dev/null +++ b/examples/provisioning/console_prov/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(console_prov) diff --git a/examples/provisioning/console_prov/Makefile b/examples/provisioning/console_prov/Makefile new file mode 100644 index 000000000..c84ccd93f --- /dev/null +++ b/examples/provisioning/console_prov/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := console_prov + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/provisioning/console_prov/README.md b/examples/provisioning/console_prov/README.md new file mode 100644 index 000000000..1584b32bd --- /dev/null +++ b/examples/provisioning/console_prov/README.md @@ -0,0 +1,212 @@ +# Console based Provisioning Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +`console_prov` example demonstrates the implementation and integration of various IDF components for building a console based provisioning application. + +For this example UART console is chosen as the mode of transport, over which the provisioning related communication is to take place, between the device (to be provisioned) and the client (owner of the device). + +In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed. + +Right after provisioning is complete, the UART console is deactivated. + +`console_prov` uses the following components : +* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration +* `protocomm` : for protocol based communication and secure session establishment +* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures + +This example can be used, as it is, for adding a provisioning service to any application intended for IoT. But it is more suitable for debugging protocomm and provisioning related components and feature additions. + +## How to use example + +### Hardware Required + +Example should be able to run on any commonly available ESP32 development board. + +### Application Required + +To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). This feature of `esp_prov` should work on all platforms as long long as all Python dependencies are satisfied. + +### Configure the project + +``` +make menuconfig +``` + +* Set serial port under Serial Flasher Options. + +* Under Example Configuration set the following : + * Security Version (default 1) + * Proof of Possession (default "abcd1234") + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (388) app: Starting console provisioning +I (398) app_prov: Console provisioning started +. +. +. +>> +``` + +In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration, the script should be run as follows : + +``` +python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport console +``` + +A console will open up and the `Client->Device` commands have to be copied manually to the serial monitor console prompt : + +``` +==== Esp_Prov Version: V0.1 ==== + +==== Verifying protocol version ==== +Client->Device msg : proto-ver 0 56302e31 +Enter device->client msg : +``` + +On pasting the command on the serial monitor console, a `Device->Client` message will appear for each command : + +``` +>> proto-ver 0 56302e31 +53554343455353 +``` + +Copy this message back to the `esp_prov` console for proceeding to the next command : + +``` +==== Verifying protocol version ==== +Client->Device msg : proto-ver 0 56302e31 +Enter device->client msg : 53554343455353 +==== Verified protocol version successfully ==== + +==== Starting Session ==== +Client->Device msg : prov-session 0 10015a25a201220a20677106cc2f5b2acb5d8da26f0ad443df006daa1cd5bb3d75a8324d81ec5ef970 +Enter device->client msg : +``` + +This process keeps on till the device gets provisioned. + +Note that the commands are in the following format : + +``` + +``` + +This is helpful in understanding the provisioning process and the order in which the endpoints are communicated with. + +The full execution sequence of `esp_prov`, as seen on the console, is shown here : + +``` +==== Esp_Prov Version: V0.1 ==== + +==== Verifying protocol version ==== +Client->Device msg : proto-ver 0 56302e31 +Enter device->client msg : 53554343455353 +==== Verified protocol version successfully ==== + +==== Starting Session ==== +Client->Device msg : prov-session 0 10015a25a201220a20677106cc2f5b2acb5d8da26f0ad443df006daa1cd5bb3d75a8324d81ec5ef970 +Enter device->client msg : 10015a390801aa013412207566f4de191f600ea42de5c2b1df73f1f16685c2edb43d7c3ffc83d6b81ff61b1a103db6476536a88db10b7e0a172d4adef8 +Client->Device msg : prov-session 0 10015a270802b20122122084ca311e51c904a94f8a249c049f7aed33b39671cc11f0b92b15b299ef5653b7 +Enter device->client msg : 10015a270803ba01221a203246230190d5c1f5d94c01b56ac8cace1086cfb2d937a4a46cb6c79db7a35a8b +==== Session Established ==== + +==== Sending Wifi credential to esp32 ==== +Client->Device msg : prov-config 0 8f0c8cb6f2d53c4cc53b29be8ba1aac3edbb1dead39117c34687d6 +Enter device->client msg : 2e1f0eb0 +==== Wifi Credentials sent successfully ==== + +==== Applying config to esp32 ==== +Client->Device msg : prov-config 0 e8df +Enter device->client msg : 245c83f0 +==== Apply config sent successfully ==== + +==== Wifi connection state ==== +Client->Device msg : prov-config 0 2d36 +Enter device->client msg : 1b38a7411b6e2608aae50a6571807e04a6e90520b3b1e3c1e5b38cea4b9022e56485b92ff84289df218311972a42eb +++++ WiFi state: connected ++++ +==== Provisioning was successful ==== +``` + +The serial monitor console, for above sequence of commands, would look like : + +``` +>> proto-ver 0 56302e31 +53554343455353 +>> prov-session 0 10015a25a201220a20677106cc2f5b2acb5d8da26f0ad443df006daa1cd5bb3d75a8324d81ec5ef970 +10015a390801aa013412207566f4de191f600ea42de5c2b1df73f1f16685c2edb43d7c3ffc83d6b81ff61b1a103db6476536a88db10b7e0a172d4adef8 +>> prov-session 0 10015a270802b20122122084ca311e51c904a94f8a249c049f7aed33b39671cc11f0b92b15b299ef5653b7 +10015a270803ba01221a203246230190d5c1f5d94c01b56ac8cace1086cfb2d937a4a46cb6c79db7a35a8b +>> prov-config 0 8f0c8cb6f2d53c4cc53b29be8ba1aac3edbb1dead39117c34687d6 +I (1073738) app_prov_handler: WiFi Credentials Received : + ssid : myssid + password : mypassword + +2e1f0eb0 +>> prov-config 0 e8df +I (1084218) app_prov_handler: WiFi Credentials Applied + +245c83f0 +>> prov-config 0 2d36 +I (1089728) app_prov: STA Got IP +I (1089728) app: got ip:192.168.43.220 +I (1099698) app_prov_handler: Connected state + +1b38a7411b6e2608aae50a6571807e04a6e90520b3b1e3c1e5b38cea4b9022e56485b92ff84289df218311972a42eb +>> +``` + +After sometime the provisioning app will exit and UART console will be stopped + +``` +I (1119728) app_prov: Stopping provisioning +I (1119728) protocomm_console: Stopping console... +I (1119728) app_prov: Provisioning stopped +I (1119748) protocomm_console: Console stopped +``` + +## Troubleshooting + +### Provisioning failed + +It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason : + +``` +E (39291) app_prov: STA Disconnected +E (39291) app_prov: Disconnect reason : 201 +I (39291) app_prov: STA AP Not found +I (42021) app_prov_handler: Disconnected state +``` + +### Provisioning does not start + +If the serial monitor log is different, as shown below : + +``` +I (539) app_prov: Found ssid myssid +I (539) app_prov: Found password mypassword +I (549) app: Starting WiFi station +``` + +It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example + +``` +make erase_flash +make -j4 flash monitor +``` + +Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution. diff --git a/examples/provisioning/console_prov/main/CMakeLists.txt b/examples/provisioning/console_prov/main/CMakeLists.txt new file mode 100644 index 000000000..3c44a1a36 --- /dev/null +++ b/examples/provisioning/console_prov/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCS "app_main.c" + "app_prov.c" + "app_prov_handlers.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/provisioning/console_prov/main/Kconfig.projbuild b/examples/provisioning/console_prov/main/Kconfig.projbuild new file mode 100644 index 000000000..b669a1e5a --- /dev/null +++ b/examples/provisioning/console_prov/main/Kconfig.projbuild @@ -0,0 +1,34 @@ +menu "Example Configuration" + +config USE_SEC_1 + bool + default y + prompt "Use Security Version 1" + help + Security version 1 used Curve25519 key exchange for establishing + secure session between device and client during provisioning + +config USE_POP + bool + depends on USE_SEC_1 + default y + prompt "Use proof-of-possession" + help + Proof-of-possession can be optionally used to prove that the device is indeed + in possession of the user who is provisioning the device. This proof-of-possession + is internally used to generate the shared secret through key exchange. + +config POP + string "Proof-of-possession" + default "abcd1234" + depends on USE_POP + +config RESET_PROVISIONED + bool + default n + prompt "Reset provisioned status of the device" + help + This erases the NVS to reset provisioned status of the device on every reboot. + Provisioned status is determined by the WiFi STA configuration, saved on the NVS. + +endmenu diff --git a/examples/provisioning/console_prov/main/app_main.c b/examples/provisioning/console_prov/main/app_main.c new file mode 100644 index 000000000..9edfdd2ef --- /dev/null +++ b/examples/provisioning/console_prov/main/app_main.c @@ -0,0 +1,114 @@ +/* Console based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "app_prov.h" + +static const char *TAG = "app"; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + /* Invoke Provisioning event handler first */ + app_prov_event_handler(ctx, event); + + switch(event->event_id) { + case SYSTEM_EVENT_AP_START: + ESP_LOGI(TAG, "SoftAP started"); + break; + case SYSTEM_EVENT_AP_STOP: + ESP_LOGI(TAG, "SoftAP stopped"); + break; + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:"MACSTR" join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:"MACSTR"leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + break; + default: + break; + } + return ESP_OK; +} + +static void wifi_init_sta() +{ + /* Start wifi in station mode with credentials set during provisioning */ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_start() ); +} + +void app_main() +{ + /* Security version */ + int security = 0; + /* Proof of possession */ + const protocomm_security_pop_t *pop = NULL; + +#ifdef CONFIG_USE_SEC_1 + security = 1; +#endif + + /* Having proof of possession is optional */ +#ifdef CONFIG_USE_POP + const static protocomm_security_pop_t app_pop = { + .data = (uint8_t *) CONFIG_POP, + .len = (sizeof(CONFIG_POP)-1) + }; + pop = &app_pop; +#endif + + /* Initialize networking stack */ + tcpip_adapter_init(); + + /* Set our event handling */ + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + + /* Check if device is provisioned */ + bool provisioned; + if (app_prov_is_provisioned(&provisioned) != ESP_OK) { + ESP_LOGE(TAG, "Error getting device provisioning state"); + return; + } + + if (provisioned == false) { + /* If not provisioned, start provisioning via console */ + ESP_LOGI(TAG, "Starting console provisioning"); + app_prov_start_console_provisioning(security, pop); + } else { + /* Else start as station with credentials set during provisioning */ + ESP_LOGI(TAG, "Starting WiFi station"); + wifi_init_sta(NULL); + } +} diff --git a/examples/provisioning/console_prov/main/app_prov.c b/examples/provisioning/console_prov/main/app_prov.c new file mode 100644 index 000000000..7eccb8b5f --- /dev/null +++ b/examples/provisioning/console_prov/main/app_prov.c @@ -0,0 +1,328 @@ +/* Console based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "app_prov.h" + +static const char *TAG = "app_prov"; + +/* Handlers for wifi_config provisioning endpoint */ +extern wifi_prov_config_handlers_t wifi_prov_handlers; + +/** + * @brief Data relevant to provisioning application + */ +struct app_prov_data { + protocomm_t *pc; /*!< Protocomm handler */ + int security; /*!< Type of security to use with protocomm */ + const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */ + esp_timer_handle_t timer; /*!< Handle to timer */ + + /* State of WiFi Station */ + wifi_prov_sta_state_t wifi_state; + + /* Code for WiFi station disconnection (if disconnected) */ + wifi_prov_sta_fail_reason_t wifi_disconnect_reason; +}; + +/* Pointer to provisioning application data */ +static struct app_prov_data *g_prov; + +static esp_err_t app_prov_start_service(void) +{ + /* Create new protocomm instance */ + g_prov->pc = protocomm_new(); + if (g_prov->pc == NULL) { + ESP_LOGE(TAG, "Failed to create new protocomm instance"); + return ESP_FAIL; + } + + /* Config for protocomm_console_start() */ + protocomm_console_config_t config = PROTOCOMM_CONSOLE_DEFAULT_CONFIG(); + + /* Start protocomm using console */ + if (protocomm_console_start(g_prov->pc, &config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start console provisioning"); + return ESP_FAIL; + } + + /* Set protocomm version verification endpoint for protocol */ + protocomm_set_version(g_prov->pc, "proto-ver", "V0.1"); + + /* Set protocomm security type for endpoint */ + if (g_prov->security == 0) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL); + } else if (g_prov->security == 1) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop); + } + + /* Add endpoint for provisioning to set wifi station config */ + if (protocomm_add_endpoint(g_prov->pc, "prov-config", + wifi_prov_config_data_handler, + (void *) &wifi_prov_handlers) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set provisioning endpoint"); + protocomm_console_stop(g_prov->pc); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Provisioning started"); + return ESP_OK; +} + +static void app_prov_stop_service(void) +{ + /* Remove provisioning endpoint */ + protocomm_remove_endpoint(g_prov->pc, "prov-config"); + /* Unset provisioning security */ + protocomm_unset_security(g_prov->pc, "prov-session"); + /* Unset provisioning version endpoint */ + protocomm_unset_version(g_prov->pc, "proto-ver"); + /* Stop protocomm console service */ + protocomm_console_stop(g_prov->pc); + /* Delete protocomm instance */ + protocomm_delete(g_prov->pc); +} + +/* Callback to be invoked by timer */ +static void _stop_prov_cb(void * arg) +{ + ESP_LOGI(TAG, "Stopping provisioning"); + app_prov_stop_service(); + + /* Timer not needed anymore */ + esp_timer_handle_t timer = g_prov->timer; + esp_timer_delete(timer); + g_prov->timer = NULL; + + /* Free provisioning process data */ + free(g_prov); + g_prov = NULL; + ESP_LOGI(TAG, "Provisioning stopped"); +} + +/* Event handler for starting/stopping provisioning. + * To be called from within the context of the main + * event handler. + */ +esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) +{ + /* For accessing reason codes in case of disconnection */ + system_event_info_t *info = &event->event_info; + + /* If pointer to provisioning application data is NULL + * then provisioning is not running, therefore return without + * error */ + if (!g_prov) { + return ESP_OK; + } + + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + ESP_LOGI(TAG, "STA Start"); + /* Once configuration is received through protocomm, + * device is started as station. Once station starts, + * wait for connection to establish with configured + * host SSID and password */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + break; + + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "STA Got IP"); + /* Station got IP. That means configuraion is successful. + * Schedule timer to stop provisioning app after 30 seconds. */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTED; + if (g_prov && g_prov->timer) { + esp_timer_start_once(g_prov->timer, 30000*1000U); + } + break; + + case SYSTEM_EVENT_STA_DISCONNECTED: + ESP_LOGE(TAG, "STA Disconnected"); + /* Station couldn't connect to configured host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED; + ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason); + + /* Set code corresponding to the reason for disconnection */ + switch (info->disconnected.reason) { + case WIFI_REASON_AUTH_EXPIRE: + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + case WIFI_REASON_BEACON_TIMEOUT: + case WIFI_REASON_AUTH_FAIL: + case WIFI_REASON_ASSOC_FAIL: + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ESP_LOGI(TAG, "STA Auth Error"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR; + break; + case WIFI_REASON_NO_AP_FOUND: + ESP_LOGI(TAG, "STA AP Not found"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND; + break; + default: + /* If none of the expected reasons, + * retry connecting to host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + esp_wifi_connect(); + } + break; + + default: + break; + } + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state) +{ + if (g_prov == NULL || state == NULL) { + return ESP_FAIL; + } + + *state = g_prov->wifi_state; + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason) +{ + if (g_prov == NULL || reason == NULL) { + return ESP_FAIL; + } + + if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) { + return ESP_FAIL; + } + + *reason = g_prov->wifi_disconnect_reason; + return ESP_OK; +} + +esp_err_t app_prov_is_provisioned(bool *provisioned) +{ + *provisioned = false; + +#ifdef CONFIG_RESET_PROVISIONED + nvs_flash_erase(); +#endif + + if (nvs_flash_init() != ESP_OK) { + ESP_LOGE(TAG, "Failed to init NVS"); + return ESP_FAIL; + } + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + if (esp_wifi_init(&cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to init wifi"); + return ESP_FAIL; + } + + /* Get WiFi Station configuration */ + wifi_config_t wifi_cfg; + if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) { + return ESP_FAIL; + } + + if (strlen((const char*) wifi_cfg.sta.ssid)) { + *provisioned = true; + ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid); + ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password); + } + return ESP_OK; +} + +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg) +{ + /* Initialize WiFi with default config */ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + if (esp_wifi_init(&cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to init WiFi"); + return ESP_FAIL; + } + /* Configure WiFi as station */ + if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode"); + return ESP_FAIL; + } + /* Configure WiFi station with host credentials + * provided during provisioning */ + if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi configuration"); + return ESP_FAIL; + } + /* Start WiFi */ + if (esp_wifi_start() != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi configuration"); + return ESP_FAIL; + } + /* Connect to AP */ + if (esp_wifi_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect WiFi"); + return ESP_FAIL; + } + + if (g_prov) { + /* Reset wifi station state for provisioning app */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + } + return ESP_OK; +} + +esp_err_t app_prov_start_console_provisioning(int security, const protocomm_security_pop_t *pop) +{ + /* If provisioning app data present, + * means provisioning app is already running */ + if (g_prov) { + ESP_LOGI(TAG, "Invalid provisioning state"); + return ESP_FAIL; + } + + /* Allocate memory for provisioning app data */ + g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data)); + if (!g_prov) { + ESP_LOGI(TAG, "Unable to allocate prov data"); + return ESP_ERR_NO_MEM; + } + + /* Initialise app data */ + g_prov->pop = pop; + g_prov->security = security; + + /* Create timer object as a member of app data */ + esp_timer_create_args_t timer_conf = { + .callback = _stop_prov_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "stop_softap_tm" + }; + esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to create timer"); + return err; + } + + /* Start provisioning service through console */ + err = app_prov_start_service(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Provisioning failed to start"); + return err; + } + + ESP_LOGI(TAG, "Console provisioning started"); + return ESP_OK; +} diff --git a/examples/provisioning/console_prov/main/app_prov.h b/examples/provisioning/console_prov/main/app_prov.h new file mode 100644 index 000000000..c1ac7c3ee --- /dev/null +++ b/examples/provisioning/console_prov/main/app_prov.h @@ -0,0 +1,96 @@ +/* Console based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#pragma once + +#include + +#include +#include + +/** + * @brief Get state of WiFi Station during provisioning + * + * @note WiFi is initially configured as AP, when + * provisioning starts. After provisioning data + * is provided by user, the WiFi is reconfigured + * to run as both AP and Station. + * + * @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi state + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state); + +/** + * @brief Get reason code in case of WiFi station + * disconnection during provisioning + * +* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi disconnect reason + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason); + +/** + * @brief Event handler for provisioning app + * + * This is called from the main event handler and controls the + * provisioning application, depeding on WiFi events + * + * @param[in] ctx Event context data + * @param[in] event Event info + * + * @return + * - ESP_OK : Event handled successfully + * - ESP_FAIL : Failed to start server on event AP start + */ +esp_err_t app_prov_event_handler(void *ctx, system_event_t *event); + +/** + * @brief Checks if device is provisioned + * * + * @param[out] provisioned True if provisioned, else false + * + * @return + * - ESP_OK : Retrieved provision state successfully + * - ESP_FAIL : Failed to retrieve provision state + */ +esp_err_t app_prov_is_provisioned(bool *provisioned); + +/** + * @brief Runs WiFi as Station + * + * Configures the WiFi station mode to connect to the + * SSID and password specified in config structure, + * and starts WiFi to run as station + * + * @param[in] wifi_cfg Pointer to WiFi cofiguration structure + * + * @return + * - ESP_OK : WiFi configured and started successfully + * - ESP_FAIL : Failed to set configuration + */ +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg); + +/** + * @brief Start provisioning via Console + * + * @param[in] security Security mode + * @param[in] pop Pointer to proof of possession (NULL if not present) + * + * @return + * - ESP_OK : Provisioning started successfully + * - ESP_FAIL : Failed to start + */ +esp_err_t app_prov_start_console_provisioning(int security, const protocomm_security_pop_t *pop); diff --git a/examples/provisioning/console_prov/main/app_prov_handlers.c b/examples/provisioning/console_prov/main/app_prov_handlers.c new file mode 100644 index 000000000..55d2f2c19 --- /dev/null +++ b/examples/provisioning/console_prov/main/app_prov_handlers.c @@ -0,0 +1,106 @@ +/* Console based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* This file is mostly a boiler-plate code that applications can use without much change */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "app_prov.h" + +static const char* TAG = "app_prov_handler"; + +static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data) +{ + /* Initialise to zero */ + memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t)); + + if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) { + ESP_LOGW(TAG, "Prov app not running"); + return ESP_FAIL; + } + + if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) { + ESP_LOGI(TAG, "Connected state"); + + /* IP Addr assigned to STA */ + tcpip_adapter_ip_info_t ip_info; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); + char *ip_addr = ip4addr_ntoa(&ip_info.ip); + strcpy(resp_data->conn_info.ip_addr, ip_addr); + + /* AP information to which STA is connected */ + wifi_ap_record_t ap_info; + esp_wifi_sta_get_ap_info(&ap_info); + memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid)); + memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid)); + resp_data->conn_info.channel = ap_info.primary; + resp_data->conn_info.auth_mode = ap_info.authmode; + } else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Disconnected state"); + + /* If disconnected, convey reason */ + app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason); + } else { + ESP_LOGI(TAG, "Connecting state"); + } + return ESP_OK; +} + +static wifi_config_t *wifi_cfg; + +static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data) +{ + if (wifi_cfg) { + free(wifi_cfg); + wifi_cfg = NULL; + } + + wifi_cfg = (wifi_config_t *) calloc(1, sizeof(wifi_config_t)); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Unable to alloc wifi config"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s", + req_data->ssid, req_data->password); + memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid, + strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid))); + memcpy((char *) wifi_cfg->sta.password, req_data->password, + strnlen(req_data->password, sizeof(wifi_cfg->sta.password))); + return ESP_OK; +} + +static esp_err_t apply_config_handler(void) +{ + if (!wifi_cfg) { + ESP_LOGE(TAG, "WiFi config not set"); + return ESP_FAIL; + } + + app_prov_configure_sta(wifi_cfg); + ESP_LOGI(TAG, "WiFi Credentials Applied"); + + free(wifi_cfg); + wifi_cfg = NULL; + return ESP_OK; +} + +wifi_prov_config_handlers_t wifi_prov_handlers = { + .get_status_handler = get_status_handler, + .set_config_handler = set_config_handler, + .apply_config_handler = apply_config_handler, +}; diff --git a/examples/provisioning/console_prov/main/component.mk b/examples/provisioning/console_prov/main/component.mk new file mode 100644 index 000000000..61f8990c3 --- /dev/null +++ b/examples/provisioning/console_prov/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/provisioning/custom_config/CMakeLists.txt b/examples/provisioning/custom_config/CMakeLists.txt new file mode 100644 index 000000000..ec9b0ce66 --- /dev/null +++ b/examples/provisioning/custom_config/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(custom_config) diff --git a/examples/provisioning/custom_config/Makefile b/examples/provisioning/custom_config/Makefile new file mode 100644 index 000000000..8ded51d75 --- /dev/null +++ b/examples/provisioning/custom_config/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := custom_config + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/provisioning/custom_config/README.md b/examples/provisioning/custom_config/README.md new file mode 100644 index 000000000..55c7e8cc3 --- /dev/null +++ b/examples/provisioning/custom_config/README.md @@ -0,0 +1,144 @@ +# SoftAP + HTTPD based Provisioning Example featuring Custom configuration + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +(Please see the README.md under `softap_prov` example before this.) + +`custom_config` example demonstrates the implementation and integration of various IDF components for building a provisioning application. + +This is same as `softap_prov` example, with added feature for configuration of some custom data (just like Wi-Fi configuration) during provisioning. The custom data provided during provisioning is simply printed on the serial monitor. The rest of the program functions just like `softap_prov`, ie. the device is configured as Wi-Fi station with supplied AP credentials. + + +`custom_config` uses the following components : +* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration +* `protocomm` : for protocol based communication and secure session establishment +* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures + +Also, it uses a component provided with this example `custom_provisioning` which provides data structures and protocomm endpoint handlers for custom data configuration + +## How to use example + +### Hardware Required + +Example should be able to run on any commonly available ESP32 development board. + +### Application Required + +To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). This feature of `esp_prov` should work on all platforms as long long as all Python dependencies are satisfied. + +### Configure the project + +``` +make menuconfig +``` + +* Set serial port under Serial Flasher Options. + +* Under Example Configuration set the following : + * SoftAP SSID (Defaults to PROV_) + * SoftAP Password (Defaults to PROV_PASS) + * Security Version (default 0) + * Proof of Possession (by default not needed for security version 0) + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (1562) app: SoftAP started +I (1572) app_prov: SoftAP Provisioning started with SSID 'PROV_261FCC', Password 'PROV_PASS' +``` + +Make sure to connect the client computer to the SoftAP network, whose SSID and Password are displayed in the serial monitor log. On successful connection the monitor log will show : + +``` +I (519482) tcpip_adapter: softAP assign IP to station,IP is: 192.168.4.2 +``` + +In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace the values corresponding to the parameters `--custom_info` and `--custom_ver` with your desired values for the custom configuration). Assuming default example configuration, the script should be run as follows : + +``` +python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 0 --transport softap --softap_endpoint 192.168.4.1:80 --custom_config --custom_info "some string" --custom_ver 4321 +``` + +Above command will perform the provisioning steps, and the monitor log should display something like this : + +``` +I (92734) app_prov_handler: Custom config received : + Info : some string + Version : 4321 +. +. +. +I (634572) app_prov_handler: WiFi Credentials Received : + ssid : myssid + password : mypassword +. +. +. +I (634652) app_prov_handler: WiFi Credentials Applied +I (634652) app_prov: STA Start +. +. +. +I (688270) app_prov_handler: Connecting state +. +. +. +I (637732) app_prov: STA Got IP +I (637732) app: got ip:192.168.43.220 +. +. +. +I (654562) app_prov_handler: Connected state +``` + +After sometime the provisioning app will exit, SoftAP will be turned off and HTTP server will be stopped + +``` +I (667732) app_prov: Stopping provisioning +I (668732) app_prov: Provisioning stopped +I (668742) app: SoftAP stopped +``` + +## Troubleshooting + +### Provisioning failed + +It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason : + +``` +E (39291) app_prov: STA Disconnected +E (39291) app_prov: Disconnect reason : 201 +I (39291) app_prov: STA AP Not found +I (42021) app_prov_handler: Disconnected state +``` + +### Provisioning does not start + +If the serial monitor log is different, as shown below : + +``` +I (539) app_prov: Found ssid myssid +I (539) app_prov: Found password mypassword +I (549) app: Starting WiFi station +``` + +It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example + +``` +make erase_flash +make -j4 flash monitor +``` + +Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution. diff --git a/examples/provisioning/custom_config/components/custom_provisioning/CMakeLists.txt b/examples/provisioning/custom_config/components/custom_provisioning/CMakeLists.txt new file mode 100644 index 000000000..4e0f61c12 --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/CMakeLists.txt @@ -0,0 +1,8 @@ +set(COMPONENT_ADD_INCLUDEDIRS include) +set(COMPONENT_PRIV_INCLUDEDIRS proto-c) +set(COMPONENT_SRCS "src/custom_config.c" + "proto-c/custom_config.pb-c.c") + +set(COMPONENT_PRIV_REQUIRES protobuf-c) + +register_component() diff --git a/examples/provisioning/custom_config/components/custom_provisioning/component.mk b/examples/provisioning/custom_config/components/custom_provisioning/component.mk new file mode 100644 index 000000000..69af7efdf --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/component.mk @@ -0,0 +1,3 @@ +COMPONENT_SRCDIRS := src proto-c +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_PRIV_INCLUDEDIRS := proto-c diff --git a/examples/provisioning/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h b/examples/provisioning/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h new file mode 100644 index 000000000..a9b6214a8 --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h @@ -0,0 +1,44 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _CUSTOM_PROV_CONFIG_H_ +#define _CUSTOM_PROV_CONFIG_H_ + +/** + * @brief Custom config data received by device + */ +typedef struct { + char info[128]; + int version; +} custom_config_t; + +/** + * @brief Internal handler for receiving and responding to protocomm + * requests from master + * + * This is to be passed as priv_data for protocomm request handler + * (refer to `custom_prov_config_data_handler()`) when calling `protocomm_add_endpoint()`. + */ +typedef esp_err_t (*custom_prov_config_handler_t) (const custom_config_t *config); + +/** + * @brief Handler for receiving and responding to requests from master + * + * This is to be registered as the `wifi_config` endpoint handler + * (protocomm `protocomm_req_handler_t`) using `protocomm_add_endpoint()` + */ +esp_err_t custom_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, void *priv_data); + +#endif diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.c b/examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.c new file mode 100644 index 000000000..62640573d --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.c @@ -0,0 +1,229 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: custom_config.proto */ + +/* Do not generate deprecated warnings for self */ +#ifndef PROTOBUF_C__NO_DEPRECATED +#define PROTOBUF_C__NO_DEPRECATED +#endif + +#include "custom_config.pb-c.h" +void custom_config_request__init + (CustomConfigRequest *message) +{ + static const CustomConfigRequest init_value = CUSTOM_CONFIG_REQUEST__INIT; + *message = init_value; +} +size_t custom_config_request__get_packed_size + (const CustomConfigRequest *message) +{ + assert(message->base.descriptor == &custom_config_request__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t custom_config_request__pack + (const CustomConfigRequest *message, + uint8_t *out) +{ + assert(message->base.descriptor == &custom_config_request__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t custom_config_request__pack_to_buffer + (const CustomConfigRequest *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &custom_config_request__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +CustomConfigRequest * + custom_config_request__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (CustomConfigRequest *) + protobuf_c_message_unpack (&custom_config_request__descriptor, + allocator, len, data); +} +void custom_config_request__free_unpacked + (CustomConfigRequest *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &custom_config_request__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void custom_config_response__init + (CustomConfigResponse *message) +{ + static const CustomConfigResponse init_value = CUSTOM_CONFIG_RESPONSE__INIT; + *message = init_value; +} +size_t custom_config_response__get_packed_size + (const CustomConfigResponse *message) +{ + assert(message->base.descriptor == &custom_config_response__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t custom_config_response__pack + (const CustomConfigResponse *message, + uint8_t *out) +{ + assert(message->base.descriptor == &custom_config_response__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t custom_config_response__pack_to_buffer + (const CustomConfigResponse *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &custom_config_response__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +CustomConfigResponse * + custom_config_response__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (CustomConfigResponse *) + protobuf_c_message_unpack (&custom_config_response__descriptor, + allocator, len, data); +} +void custom_config_response__free_unpacked + (CustomConfigResponse *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &custom_config_response__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +static const ProtobufCFieldDescriptor custom_config_request__field_descriptors[2] = +{ + { + "info", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_STRING, + 0, /* quantifier_offset */ + offsetof(CustomConfigRequest, info), + NULL, + &protobuf_c_empty_string, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "version", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(CustomConfigRequest, version), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned custom_config_request__field_indices_by_name[] = { + 0, /* field[0] = info */ + 1, /* field[1] = version */ +}; +static const ProtobufCIntRange custom_config_request__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor custom_config_request__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "CustomConfigRequest", + "CustomConfigRequest", + "CustomConfigRequest", + "", + sizeof(CustomConfigRequest), + 2, + custom_config_request__field_descriptors, + custom_config_request__field_indices_by_name, + 1, custom_config_request__number_ranges, + (ProtobufCMessageInit) custom_config_request__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor custom_config_response__field_descriptors[2] = +{ + { + "status", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(CustomConfigResponse, status), + &custom_config_status__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "dummy", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(CustomConfigResponse, dummy), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned custom_config_response__field_indices_by_name[] = { + 1, /* field[1] = dummy */ + 0, /* field[0] = status */ +}; +static const ProtobufCIntRange custom_config_response__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor custom_config_response__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "CustomConfigResponse", + "CustomConfigResponse", + "CustomConfigResponse", + "", + sizeof(CustomConfigResponse), + 2, + custom_config_response__field_descriptors, + custom_config_response__field_indices_by_name, + 1, custom_config_response__number_ranges, + (ProtobufCMessageInit) custom_config_response__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCEnumValue custom_config_status__enum_values_by_number[2] = +{ + { "ConfigSuccess", "CUSTOM_CONFIG_STATUS__ConfigSuccess", 0 }, + { "ConfigFail", "CUSTOM_CONFIG_STATUS__ConfigFail", 1 }, +}; +static const ProtobufCIntRange custom_config_status__value_ranges[] = { +{0, 0},{0, 2} +}; +static const ProtobufCEnumValueIndex custom_config_status__enum_values_by_name[2] = +{ + { "ConfigFail", 1 }, + { "ConfigSuccess", 0 }, +}; +const ProtobufCEnumDescriptor custom_config_status__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "CustomConfigStatus", + "CustomConfigStatus", + "CustomConfigStatus", + "", + 2, + custom_config_status__enum_values_by_number, + 2, + custom_config_status__enum_values_by_name, + 1, + custom_config_status__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.h b/examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.h new file mode 100644 index 000000000..fe1e0e453 --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.h @@ -0,0 +1,113 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: custom_config.proto */ + +#ifndef PROTOBUF_C_custom_5fconfig_2eproto__INCLUDED +#define PROTOBUF_C_custom_5fconfig_2eproto__INCLUDED + +#include + +PROTOBUF_C__BEGIN_DECLS + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. +#elif 1003000 < PROTOBUF_C_MIN_COMPILER_VERSION +# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. +#endif + + +typedef struct _CustomConfigRequest CustomConfigRequest; +typedef struct _CustomConfigResponse CustomConfigResponse; + + +/* --- enums --- */ + +typedef enum _CustomConfigStatus { + CUSTOM_CONFIG_STATUS__ConfigSuccess = 0, + CUSTOM_CONFIG_STATUS__ConfigFail = 1 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(CUSTOM_CONFIG_STATUS) +} CustomConfigStatus; + +/* --- messages --- */ + +struct _CustomConfigRequest +{ + ProtobufCMessage base; + char *info; + int32_t version; +}; +#define CUSTOM_CONFIG_REQUEST__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&custom_config_request__descriptor) \ + , (char *)protobuf_c_empty_string, 0 } + + +struct _CustomConfigResponse +{ + ProtobufCMessage base; + CustomConfigStatus status; + int32_t dummy; +}; +#define CUSTOM_CONFIG_RESPONSE__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&custom_config_response__descriptor) \ + , CUSTOM_CONFIG_STATUS__ConfigSuccess, 0 } + + +/* CustomConfigRequest methods */ +void custom_config_request__init + (CustomConfigRequest *message); +size_t custom_config_request__get_packed_size + (const CustomConfigRequest *message); +size_t custom_config_request__pack + (const CustomConfigRequest *message, + uint8_t *out); +size_t custom_config_request__pack_to_buffer + (const CustomConfigRequest *message, + ProtobufCBuffer *buffer); +CustomConfigRequest * + custom_config_request__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void custom_config_request__free_unpacked + (CustomConfigRequest *message, + ProtobufCAllocator *allocator); +/* CustomConfigResponse methods */ +void custom_config_response__init + (CustomConfigResponse *message); +size_t custom_config_response__get_packed_size + (const CustomConfigResponse *message); +size_t custom_config_response__pack + (const CustomConfigResponse *message, + uint8_t *out); +size_t custom_config_response__pack_to_buffer + (const CustomConfigResponse *message, + ProtobufCBuffer *buffer); +CustomConfigResponse * + custom_config_response__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void custom_config_response__free_unpacked + (CustomConfigResponse *message, + ProtobufCAllocator *allocator); +/* --- per-message closures --- */ + +typedef void (*CustomConfigRequest_Closure) + (const CustomConfigRequest *message, + void *closure_data); +typedef void (*CustomConfigResponse_Closure) + (const CustomConfigResponse *message, + void *closure_data); + +/* --- services --- */ + + +/* --- descriptors --- */ + +extern const ProtobufCEnumDescriptor custom_config_status__descriptor; +extern const ProtobufCMessageDescriptor custom_config_request__descriptor; +extern const ProtobufCMessageDescriptor custom_config_response__descriptor; + +PROTOBUF_C__END_DECLS + + +#endif /* PROTOBUF_C_custom_5fconfig_2eproto__INCLUDED */ diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto/README.md b/examples/provisioning/custom_config/components/custom_provisioning/proto/README.md new file mode 100644 index 000000000..16d41538a --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/proto/README.md @@ -0,0 +1,11 @@ +# Protobuf files for defining custom config-data packet structures + +This is an example proto file defining custom configuration related data packet structures, namely - +1. CustomConfigRequest - for sending configuration data consisting of various fields (Info and Version) +2. CustomConfigResponse - for receiving configuration status (fail/success) + +Note : These proto files are not automatically compiled during the build process. + +Run "make" (Optional) to generate the respective C and Python files. The generated C files are used by protocomm itself to create, delete and manipulate transaction packets. The generated Python files can be used by python based applications for implementing client side interface to protocomm layer. + +Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under "protocomm/proto-c" and "protocomm/python" directories, and thus running make (and installing the Protobuf compilers) is optional. diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto/custom_config.proto b/examples/provisioning/custom_config/components/custom_provisioning/proto/custom_config.proto new file mode 100644 index 000000000..c6ae82ead --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/proto/custom_config.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +enum CustomConfigStatus { + ConfigSuccess = 0; + ConfigFail = 1; +} + +message CustomConfigRequest { + string info = 1; + int32 version = 2; +} + +message CustomConfigResponse { + CustomConfigStatus status = 1; + int32 dummy = 2; +} diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto/makefile b/examples/provisioning/custom_config/components/custom_provisioning/proto/makefile new file mode 100644 index 000000000..d0da1dde6 --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/proto/makefile @@ -0,0 +1,7 @@ +all: c_proto python_proto + +c_proto: *.proto + @protoc-c --c_out=../proto-c/ *.proto + +python_proto: *.proto + @protoc --python_out=../python/ *.proto diff --git a/examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py b/examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py new file mode 100644 index 000000000..821bb18f8 --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py @@ -0,0 +1,150 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: custom_config.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='custom_config.proto', + package='', + syntax='proto3', + serialized_options=None, + serialized_pb=_b('\n\x13\x63ustom_config.proto\"4\n\x13\x43ustomConfigRequest\x12\x0c\n\x04info\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x05\"J\n\x14\x43ustomConfigResponse\x12#\n\x06status\x18\x01 \x01(\x0e\x32\x13.CustomConfigStatus\x12\r\n\x05\x64ummy\x18\x02 \x01(\x05*7\n\x12\x43ustomConfigStatus\x12\x11\n\rConfigSuccess\x10\x00\x12\x0e\n\nConfigFail\x10\x01\x62\x06proto3') +) + +_CUSTOMCONFIGSTATUS = _descriptor.EnumDescriptor( + name='CustomConfigStatus', + full_name='CustomConfigStatus', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='ConfigSuccess', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ConfigFail', index=1, number=1, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=153, + serialized_end=208, +) +_sym_db.RegisterEnumDescriptor(_CUSTOMCONFIGSTATUS) + +CustomConfigStatus = enum_type_wrapper.EnumTypeWrapper(_CUSTOMCONFIGSTATUS) +ConfigSuccess = 0 +ConfigFail = 1 + + + +_CUSTOMCONFIGREQUEST = _descriptor.Descriptor( + name='CustomConfigRequest', + full_name='CustomConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='info', full_name='CustomConfigRequest.info', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='version', full_name='CustomConfigRequest.version', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=23, + serialized_end=75, +) + + +_CUSTOMCONFIGRESPONSE = _descriptor.Descriptor( + name='CustomConfigResponse', + full_name='CustomConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='CustomConfigResponse.status', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='dummy', full_name='CustomConfigResponse.dummy', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=77, + serialized_end=151, +) + +_CUSTOMCONFIGRESPONSE.fields_by_name['status'].enum_type = _CUSTOMCONFIGSTATUS +DESCRIPTOR.message_types_by_name['CustomConfigRequest'] = _CUSTOMCONFIGREQUEST +DESCRIPTOR.message_types_by_name['CustomConfigResponse'] = _CUSTOMCONFIGRESPONSE +DESCRIPTOR.enum_types_by_name['CustomConfigStatus'] = _CUSTOMCONFIGSTATUS +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +CustomConfigRequest = _reflection.GeneratedProtocolMessageType('CustomConfigRequest', (_message.Message,), dict( + DESCRIPTOR = _CUSTOMCONFIGREQUEST, + __module__ = 'custom_config_pb2' + # @@protoc_insertion_point(class_scope:CustomConfigRequest) + )) +_sym_db.RegisterMessage(CustomConfigRequest) + +CustomConfigResponse = _reflection.GeneratedProtocolMessageType('CustomConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _CUSTOMCONFIGRESPONSE, + __module__ = 'custom_config_pb2' + # @@protoc_insertion_point(class_scope:CustomConfigResponse) + )) +_sym_db.RegisterMessage(CustomConfigResponse) + + +# @@protoc_insertion_point(module_scope) diff --git a/examples/provisioning/custom_config/components/custom_provisioning/src/custom_config.c b/examples/provisioning/custom_config/components/custom_provisioning/src/custom_config.c new file mode 100644 index 000000000..bf369aaec --- /dev/null +++ b/examples/provisioning/custom_config/components/custom_provisioning/src/custom_config.c @@ -0,0 +1,68 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 + +#include + +#include "custom_config.pb-c.h" + +static const char *TAG = "custom_config"; + +int custom_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data) +{ + CustomConfigRequest *req; + CustomConfigResponse resp; + custom_prov_config_handler_t app_handler_custom_config = (custom_prov_config_handler_t) priv_data; + + req = custom_config_request__unpack(NULL, inlen, inbuf); + if (!req) { + ESP_LOGE(TAG, "Unable to unpack config data"); + return ESP_ERR_INVALID_ARG; + } + + custom_config_response__init(&resp); + resp.status = CUSTOM_CONFIG_STATUS__ConfigFail; + + if (app_handler_custom_config) { + custom_config_t config; + strlcpy(config.info, req->info, sizeof(config.info)); + config.version = req->version; + + esp_err_t err = app_handler_custom_config(&config); + resp.status = (err == ESP_OK) ? CUSTOM_CONFIG_STATUS__ConfigSuccess : + CUSTOM_CONFIG_STATUS__ConfigFail; + } + custom_config_request__free_unpacked(req, NULL); + + resp.dummy = 47; // Set a non zero value of dummy + + *outlen = custom_config_response__get_packed_size(&resp); + if (*outlen <= 0) { + ESP_LOGE(TAG, "Invalid encoding for response"); + return ESP_FAIL; + } + + *outbuf = (uint8_t *) malloc(*outlen); + if (*outbuf == NULL) { + ESP_LOGE(TAG, "System out of memory"); + return ESP_ERR_NO_MEM; + } + + custom_config_response__pack(&resp, *outbuf); + return ESP_OK; +} diff --git a/examples/provisioning/custom_config/main/CMakeLists.txt b/examples/provisioning/custom_config/main/CMakeLists.txt new file mode 100644 index 000000000..3c44a1a36 --- /dev/null +++ b/examples/provisioning/custom_config/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCS "app_main.c" + "app_prov.c" + "app_prov_handlers.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/provisioning/custom_config/main/Kconfig.projbuild b/examples/provisioning/custom_config/main/Kconfig.projbuild new file mode 100644 index 000000000..866227bb9 --- /dev/null +++ b/examples/provisioning/custom_config/main/Kconfig.projbuild @@ -0,0 +1,52 @@ +menu "Example Configuration" + +config SOFTAP_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config SOFTAP_PASS + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config USE_SEC_1 + bool + default n + prompt "Use Security Version 1" + help + Security version 1 used Curve25519 key exchange for establishing + secure session between device and client during provisioning + +config USE_POP + bool + depends on USE_SEC_1 + default n + prompt "Use proof-of-possession" + help + Proof-of-possession can be optionally used to prove that the device is indeed + in possession of the user who is provisioning the device. This proof-of-possession + is internally used to generate the shared secret through key exchange. + +config POP + string "Proof-of-possession" + default "abcd1234" + depends on USE_POP + +config PROTOCOMM_HTTPD_PORT + int "Protocomm HTTP Port" + default 80 + help + Port on which to run Protocomm HTTP based provisioning service + +config RESET_PROVISIONED + bool + default n + prompt "Reset provisioned status of the device" + help + This erases the NVS to reset provisioned status of the device on every reboot. + Provisioned status is determined by the WiFi STA configuration, saved on the NVS. + +endmenu diff --git a/examples/provisioning/custom_config/main/app_main.c b/examples/provisioning/custom_config/main/app_main.c new file mode 100644 index 000000000..17bb29aec --- /dev/null +++ b/examples/provisioning/custom_config/main/app_main.c @@ -0,0 +1,115 @@ +/* SoftAP based Custom Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "app_prov.h" + +static const char *TAG = "app"; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + /* Invoke Provisioning event handler first */ + app_prov_event_handler(ctx, event); + + switch(event->event_id) { + case SYSTEM_EVENT_AP_START: + ESP_LOGI(TAG, "SoftAP started"); + break; + case SYSTEM_EVENT_AP_STOP: + ESP_LOGI(TAG, "SoftAP stopped"); + break; + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:"MACSTR" join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:"MACSTR"leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + break; + default: + break; + } + return ESP_OK; +} + +static void wifi_init_sta() +{ + /* Start wifi in station mode with credentials set during provisioning */ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_start() ); +} + +void app_main() +{ + /* Security version */ + int security = 0; + /* Proof of possession */ + const protocomm_security_pop_t *pop = NULL; + +#ifdef CONFIG_USE_SEC_1 + security = 1; +#endif + + /* Having proof of possession is optional */ +#ifdef CONFIG_USE_POP + const static protocomm_security_pop_t app_pop = { + .data = (uint8_t *) CONFIG_POP, + .len = (sizeof(CONFIG_POP)-1) + }; + pop = &app_pop; +#endif + + /* Initialize networking stack */ + tcpip_adapter_init(); + + /* Set our event handling */ + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + + /* Check if device is provisioned */ + bool provisioned; + if (app_prov_is_provisioned(&provisioned) != ESP_OK) { + ESP_LOGE(TAG, "Error getting device provisioning state"); + return; + } + + if (provisioned == false) { + /* If not provisioned, start provisioning via soft AP */ + ESP_LOGI(TAG, "Starting WiFi SoftAP provisioning"); + app_prov_start_softap_provisioning(CONFIG_SOFTAP_SSID, CONFIG_SOFTAP_PASS, + security, pop); + } else { + /* Start WiFi station with credentials set during provisioning */ + ESP_LOGI(TAG, "Starting WiFi station"); + wifi_init_sta(NULL); + } +} diff --git a/examples/provisioning/custom_config/main/app_prov.c b/examples/provisioning/custom_config/main/app_prov.c new file mode 100644 index 000000000..ce33ac960 --- /dev/null +++ b/examples/provisioning/custom_config/main/app_prov.c @@ -0,0 +1,398 @@ +/* SoftAP based Custom Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "app_prov.h" + +static const char *TAG = "app_prov"; + +/* Handlers for provisioning endpoints */ +extern wifi_prov_config_handlers_t wifi_prov_handlers; +extern custom_prov_config_handler_t custom_prov_handler; + +/** + * @brief Data relevant to provisioning application + */ +struct app_prov_data { + protocomm_t *pc; /*!< Protocomm handler */ + int security; /*!< Type of security to use with protocomm */ + const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */ + esp_timer_handle_t timer; /*!< Handle to timer */ + + /* State of WiFi Station */ + wifi_prov_sta_state_t wifi_state; + + /* Code for WiFi station disconnection (if disconnected) */ + wifi_prov_sta_fail_reason_t wifi_disconnect_reason; +}; + +/* Pointer to provisioning application data */ +static struct app_prov_data *g_prov; + +static esp_err_t app_prov_start_service(void) +{ + /* Create new protocomm instance */ + g_prov->pc = protocomm_new(); + if (g_prov->pc == NULL) { + ESP_LOGE(TAG, "Failed to create new protocomm instance"); + return ESP_FAIL; + } + + /* Config for protocomm_httpd_start() */ + protocomm_httpd_config_t pc_config = PROTOCOMM_HTTPD_DEFAULT_CONFIG(); + + /* Start protocomm server on top of HTTP */ + if (protocomm_httpd_start(g_prov->pc, &pc_config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start protocomm HTTP server"); + return ESP_FAIL; + } + + /* Set protocomm version verification endpoint for protocol */ + protocomm_set_version(g_prov->pc, "proto-ver", "V0.1"); + + /* Set protocomm security type for endpoint */ + if (g_prov->security == 0) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL); + } else if (g_prov->security == 1) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop); + } + + /* Add endpoint for provisioning to set WiFi STA config */ + if (protocomm_add_endpoint(g_prov->pc, "prov-config", + wifi_prov_config_data_handler, + (void *) &wifi_prov_handlers) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi provisioning endpoint"); + protocomm_httpd_stop(g_prov->pc); + return ESP_FAIL; + } + + /* Add endpoint for provisioning to set custom config */ + if (protocomm_add_endpoint(g_prov->pc, "custom-config", + custom_prov_config_data_handler, + (void *) custom_prov_handler) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set custom provisioning endpoint"); + protocomm_httpd_stop(g_prov->pc); + return ESP_FAIL; + } + return ESP_OK; +} + +static void app_prov_stop_service(void) +{ + /* Remove provisioning endpoint for custom config */ + protocomm_remove_endpoint(g_prov->pc, "custom-config"); + /* Remove provisioning endpoint for WiFi STA config */ + protocomm_remove_endpoint(g_prov->pc, "prov-config"); + /* Unset provisioning security */ + protocomm_unset_security(g_prov->pc, "prov-session"); + /* Unset provisioning version endpoint */ + protocomm_unset_version(g_prov->pc, "proto-ver"); + /* Stop protocomm server */ + protocomm_httpd_stop(g_prov->pc); + /* Delete protocomm instance */ + protocomm_delete(g_prov->pc); +} + +/* Callback to be invoked by timer */ +static void _stop_softap_cb(void * arg) +{ + ESP_LOGI(TAG, "Stopping provisioning"); + app_prov_stop_service(); + esp_wifi_set_mode(WIFI_MODE_STA); + + /* Timer not needed anymore */ + esp_timer_handle_t timer = g_prov->timer; + esp_timer_delete(timer); + g_prov->timer = NULL; + + /* Free provisioning process data */ + free(g_prov); + g_prov = NULL; + ESP_LOGI(TAG, "Provisioning stopped"); +} + +/* Event handler for starting/stopping provisioning. + * To be called from within the context of the main + * event handler. + */ +esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) +{ + /* For accessing reason codes in case of disconnection */ + system_event_info_t *info = &event->event_info; + + /* If pointer to provisioning application data is NULL + * then provisioning is not running, therefore return without + * error */ + if (!g_prov) { + return ESP_OK; + } + + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + ESP_LOGI(TAG, "STA Start"); + /* Once configuration is received by protocomm server, + * device is restarted as both AP and Station. + * Once station starts, wait for connection to + * establish with configured host SSID and password */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + break; + + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "STA Got IP"); + /* Station got IP. That means configuraion is successful. + * Schedule timer to stop provisioning app after 30 seconds. */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTED; + if (g_prov && g_prov->timer) { + /* Note that, after restarting the WiFi in Station + AP mode, the + * user gets disconnected from the AP for a while. But at the same + * time, the user app requests for status update from the device + * to verify that the provisioning was successful. Therefore, the + * turning off of the AP must be delayed long enough for the user + * to reconnect and get STA connection status from the device. + * Otherwise, the AP will be turned off before the user can + * reconnect and thus the user app will see connection timed out, + * signalling a failure in provisioning. */ + esp_timer_start_once(g_prov->timer, 30000*1000U); + } + break; + + case SYSTEM_EVENT_STA_DISCONNECTED: + ESP_LOGE(TAG, "STA Disconnected"); + /* Station couldn't connect to configured host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED; + ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason); + + /* Set code corresponding to the reason for disconnection */ + switch (info->disconnected.reason) { + case WIFI_REASON_AUTH_EXPIRE: + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + case WIFI_REASON_BEACON_TIMEOUT: + case WIFI_REASON_AUTH_FAIL: + case WIFI_REASON_ASSOC_FAIL: + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ESP_LOGI(TAG, "STA Auth Error"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR; + break; + case WIFI_REASON_NO_AP_FOUND: + ESP_LOGI(TAG, "STA AP Not found"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND; + break; + default: + /* If none of the expected reasons, + * retry connecting to host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + esp_wifi_connect(); + } + break; + + default: + break; + } + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state) +{ + if (g_prov == NULL || state == NULL) { + return ESP_FAIL; + } + + *state = g_prov->wifi_state; + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason) +{ + if (g_prov == NULL || reason == NULL) { + return ESP_FAIL; + } + + if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) { + return ESP_FAIL; + } + + *reason = g_prov->wifi_disconnect_reason; + return ESP_OK; +} + +esp_err_t app_prov_is_provisioned(bool *provisioned) +{ +#ifdef CONFIG_RESET_PROVISIONED + nvs_flash_erase(); +#endif + + if (nvs_flash_init() != ESP_OK) { + ESP_LOGE(TAG, "Failed to init NVS"); + return ESP_FAIL; + } + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + if (esp_wifi_init(&cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to init wifi"); + return ESP_FAIL; + } + + /* Get WiFi Station configuration */ + wifi_config_t wifi_cfg; + if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) { + *provisioned = false; + return ESP_FAIL; + } + + if (strlen((const char*) wifi_cfg.sta.ssid)) { + *provisioned = true; + ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid); + ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password); + } + return ESP_OK; +} + +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg) +{ + /* Configure WiFi as both AP and Station */ + if (esp_wifi_set_mode(WIFI_MODE_APSTA) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode"); + return ESP_FAIL; + } + /* Configure WiFi station with host credentials + * provided during provisioning */ + if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi configuration"); + return ESP_FAIL; + } + /* Restart WiFi */ + if (esp_wifi_start() != ESP_OK) { + ESP_LOGE(TAG, "Failed to restart WiFi"); + return ESP_FAIL; + } + /* Connect to AP */ + if (esp_wifi_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect WiFi"); + return ESP_FAIL; + } + + if (g_prov) { + /* Reset wifi station state for provisioning app */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + } + return ESP_OK; +} + +static esp_err_t start_wifi_ap(const char *ssid, const char *pass) +{ + /* Initialise WiFi with default configuration */ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t err = esp_wifi_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to init WiFi : %d", err); + return err; + } + + /* Build WiFi configuration for AP mode */ + wifi_config_t wifi_config = { + .ap = { + .max_connection = 5, + }, + }; + + strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); + wifi_config.ap.ssid_len = strlen(ssid); + + if (strlen(pass) == 0) { + memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password)); + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } else { + strncpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); + wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; + } + + /* Start WiFi in AP mode with configuration built above */ + err = esp_wifi_set_mode(WIFI_MODE_AP); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode : %d", err); + return err; + } + err = esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi config : %d", err); + return err; + } + err = esp_wifi_start(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start WiFi : %d", err); + return err; + } + + return ESP_OK; +} + +esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass, + int security, const protocomm_security_pop_t *pop) +{ + /* If provisioning app data present, + * means provisioning app is already running */ + if (g_prov) { + ESP_LOGI(TAG, "Invalid provisioning state"); + return ESP_FAIL; + } + + /* Allocate memory for provisioning app data */ + g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data)); + if (!g_prov) { + ESP_LOGI(TAG, "Unable to allocate prov data"); + return ESP_ERR_NO_MEM; + } + + /* Initialise app data */ + g_prov->pop = pop; + g_prov->security = security; + + /* Create timer object as a member of app data */ + esp_timer_create_args_t timer_conf = { + .callback = _stop_softap_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "stop_softap_tm" + }; + esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to create timer"); + return err; + } + + /* Start WiFi softAP with specified ssid and password */ + err = start_wifi_ap(ssid, pass); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start WiFi AP"); + return err; + } + + /* Start provisioning service through HTTP */ + err = app_prov_start_service(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start provisioning app"); + return err; + } + + ESP_LOGI(TAG, "SoftAP Provisioning started with SSID %s, Password %s", ssid, pass); + return ESP_OK; +} diff --git a/examples/provisioning/custom_config/main/app_prov.h b/examples/provisioning/custom_config/main/app_prov.h new file mode 100644 index 000000000..a36c3aff8 --- /dev/null +++ b/examples/provisioning/custom_config/main/app_prov.h @@ -0,0 +1,103 @@ +/* SoftAP based Custom Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#pragma once + +#include + +#include +#include +#include + +/** + * @brief Get state of WiFi Station during provisioning + * + * @note WiFi is initially configured as AP, when + * provisioning starts. After provisioning data + * is provided by user, the WiFi is reconfigured + * to run as both AP and Station. + * + * @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi state + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state); + +/** + * @brief Get reason code in case of WiFi station + * disconnection during provisioning + * +* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi disconnect reason + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason); + +/** + * @brief Event handler for provisioning app + * + * This is called from the main event handler and controls the + * provisioning application, depeding on WiFi events + * + * @param[in] ctx Event context data + * @param[in] event Event info + * + * @return + * - ESP_OK : Event handled successfully + * - ESP_FAIL : Failed to start server on event AP start + */ +esp_err_t app_prov_event_handler(void *ctx, system_event_t *event); + +/** + * @brief Checks if device is provisioned + * * + * @param[out] provisioned True if provisioned, else false + * + * @return + * - ESP_OK : Retrieved provision state successfully + * - ESP_FAIL : Failed to retrieve provision state + */ +esp_err_t app_prov_is_provisioned(bool *provisioned); + +/** + * @brief Runs WiFi as both AP and Station + * + * Configures the WiFi station mode to connect to the + * SSID and password specified in config structure, + * and restarts WiFi to run as both AP and station + * + * @param[in] wifi_cfg Pointer to WiFi cofiguration structure + * + * @return + * - ESP_OK : WiFi configured and restarted successfully + * - ESP_FAIL : Failed to set configuration + */ +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg); + +/** + * @brief Start provisioning via softAP + * + * Starts the WiFi softAP with specified ssid and pass, provisioning + * security mode and proof of possession (if any). + * + * @param[in] ssid SSID for SoftAP + * @param[in] pass Password for SoftAP + * @param[in] security Security mode + * @param[in] pop Pointer to proof of possession (NULL if not present) + * + * @return + * - ESP_OK : Provisioning started successfully + * - ESP_FAIL : Failed to start + */ +esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass, + int security, const protocomm_security_pop_t *pop); diff --git a/examples/provisioning/custom_config/main/app_prov_handlers.c b/examples/provisioning/custom_config/main/app_prov_handlers.c new file mode 100644 index 000000000..14dec260b --- /dev/null +++ b/examples/provisioning/custom_config/main/app_prov_handlers.c @@ -0,0 +1,118 @@ +/* SoftAP based Custom Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* This file is mostly a boiler-plate code that applications can use without much change */ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "app_prov.h" + +static const char* TAG = "app_prov_handler"; + +/****************** Handler for Custom Configuration *******************/ +static esp_err_t custom_config_handler(const custom_config_t *config) +{ + ESP_LOGI(TAG, "Custom config received :\n\tInfo : %s\n\tVersion : %d", + config->info, config->version); + return ESP_OK; +} + +custom_prov_config_handler_t custom_prov_handler = custom_config_handler; + +/****************** Handlers for Wi-Fi Configuration *******************/ +static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data) +{ + /* Initialise to zero */ + memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t)); + + if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) { + ESP_LOGW(TAG, "Prov app not running"); + return ESP_FAIL; + } + + if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) { + ESP_LOGI(TAG, "Connected state"); + + /* IP Addr assigned to STA */ + tcpip_adapter_ip_info_t ip_info; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); + char *ip_addr = ip4addr_ntoa(&ip_info.ip); + strcpy(resp_data->conn_info.ip_addr, ip_addr); + + /* AP information to which STA is connected */ + wifi_ap_record_t ap_info; + esp_wifi_sta_get_ap_info(&ap_info); + memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid)); + memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid)); + resp_data->conn_info.channel = ap_info.primary; + resp_data->conn_info.auth_mode = ap_info.authmode; + } else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Disconnected state"); + + /* If disconnected, convey reason */ + app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason); + } else { + ESP_LOGI(TAG, "Connecting state"); + } + return ESP_OK; +} + +static wifi_config_t *wifi_cfg; + +static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data) +{ + if (wifi_cfg) { + free(wifi_cfg); + wifi_cfg = NULL; + } + + wifi_cfg = (wifi_config_t *) calloc(1, sizeof(wifi_config_t)); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Unable to alloc wifi config"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s", + req_data->ssid, req_data->password); + memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid, + strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid))); + memcpy((char *) wifi_cfg->sta.password, req_data->password, + strnlen(req_data->password, sizeof(wifi_cfg->sta.password))); + return ESP_OK; +} + +static esp_err_t apply_config_handler(void) +{ + if (!wifi_cfg) { + ESP_LOGE(TAG, "WiFi config not set"); + return ESP_FAIL; + } + + app_prov_configure_sta(wifi_cfg); + ESP_LOGI(TAG, "WiFi Credentials Applied"); + + free(wifi_cfg); + wifi_cfg = NULL; + return ESP_OK; +} + +wifi_prov_config_handlers_t wifi_prov_handlers = { + .get_status_handler = get_status_handler, + .set_config_handler = set_config_handler, + .apply_config_handler = apply_config_handler, +}; diff --git a/examples/provisioning/custom_config/main/component.mk b/examples/provisioning/custom_config/main/component.mk new file mode 100644 index 000000000..61f8990c3 --- /dev/null +++ b/examples/provisioning/custom_config/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/provisioning/softap_prov/CMakeLists.txt b/examples/provisioning/softap_prov/CMakeLists.txt new file mode 100644 index 000000000..fa6ac1433 --- /dev/null +++ b/examples/provisioning/softap_prov/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(softap_prov) diff --git a/examples/provisioning/softap_prov/Makefile b/examples/provisioning/softap_prov/Makefile new file mode 100644 index 000000000..ab167ef2a --- /dev/null +++ b/examples/provisioning/softap_prov/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := softap_prov + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/provisioning/softap_prov/README.md b/examples/provisioning/softap_prov/README.md new file mode 100644 index 000000000..5df3e83b0 --- /dev/null +++ b/examples/provisioning/softap_prov/README.md @@ -0,0 +1,141 @@ +# SoftAP + HTTPD based Provisioning Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +`softap_prov` example demonstrates the implementation and integration of various IDF components for building a provisioning application. + +For this example Wi-Fi SoftAP is chosen as the mode of transport, over which the provisioning related communication is to take place, between the device (to be provisioned) and the client (owner of the device). The provisioning service is hosted by an HTTP server which accepts requests to specific URIs corresponding to the available provisioning endpoints (eg. for session establishment, for Wi-Fi credentials configuration, etc.). + +In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed. + +Right after provisioning is complete, Wi-Fi SoftAP and the HTTP server are deactivated. Though, that is specific to this example, and the user can choose to keep SoftAP / HTTP server active in their own application. + +`softap_prov` uses the following components : +* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration +* `protocomm` : for protocol based communication and secure session establishment +* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures + +This example can be used, as it is, for adding a provisioning service to any application intended for IoT. + +## How to use example + +### Hardware Required + +Example should be able to run on any commonly available ESP32 development board. + +### Application Required + +To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). This feature of `esp_prov` should work on all platforms as long long as all Python dependencies are satisfied. + +For android, a provisioning tool along with source code is available [here](https://github.com/espressif/esp-idf-provisioning-android). This offers a simpler and more close to actual user experience. + +### Configure the project + +``` +make menuconfig +``` + +* Set serial port under Serial Flasher Options. + +* Under Example Configuration set the following : + * SoftAP SSID (Defaults to PROV_) + * SoftAP Password (Defaults to PROV_PASS) + * Security Version (default 1) + * Proof of Possession (default "abcd1234") + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (1562) app: SoftAP started +I (1572) app_prov: SoftAP Provisioning started with SSID 'PROV_261FCC', Password 'PROV_PASS' +``` + +Make sure to connect the client computer to the SoftAP network, whose SSID and Password are displayed in the serial monitor log. On successful connection the monitor log will show : + +``` +I (519482) tcpip_adapter: softAP assign IP to station,IP is: 192.168.4.2 +``` + +In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). The SoftAP endpoint corresponds to the IP and port of the device on the SoftAP network, but this is usually same as the default value and may be left out. Assuming default example configuration, the script should be run as follows : + +``` +python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport softap --softap_endpoint 192.168.4.1:80 +``` + +Above command will perform the provisioning steps, and the monitor log should display something like this : + +``` +I (634572) app_prov_handler: WiFi Credentials Received : + ssid : myssid + password : mypassword +. +. +. +I (634652) app_prov_handler: WiFi Credentials Applied +I (634652) app_prov: STA Start +. +. +. +I (688270) app_prov_handler: Connecting state +. +. +. +I (637732) app_prov: STA Got IP +I (637732) app: got ip:192.168.43.220 +. +. +. +I (654562) app_prov_handler: Connected state +``` + +After sometime the provisioning app will exit, SoftAP will be turned off and HTTP server will be stopped + +``` +I (667732) app_prov: Stopping provisioning +I (668732) app_prov: Provisioning stopped +I (668742) app: SoftAP stopped +``` + +## Troubleshooting + +### Provisioning failed + +It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason : + +``` +E (39291) app_prov: STA Disconnected +E (39291) app_prov: Disconnect reason : 201 +I (39291) app_prov: STA AP Not found +I (42021) app_prov_handler: Disconnected state +``` + +### Provisioning does not start + +If the serial monitor log is different, as shown below : + +``` +I (539) app_prov: Found ssid myssid +I (539) app_prov: Found password mypassword +I (549) app: Starting WiFi station +``` + +It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example + +``` +make erase_flash +make -j4 flash monitor +``` + +Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution. diff --git a/examples/provisioning/softap_prov/main/CMakeLists.txt b/examples/provisioning/softap_prov/main/CMakeLists.txt new file mode 100644 index 000000000..3c44a1a36 --- /dev/null +++ b/examples/provisioning/softap_prov/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCS "app_main.c" + "app_prov.c" + "app_prov_handlers.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/provisioning/softap_prov/main/Kconfig.projbuild b/examples/provisioning/softap_prov/main/Kconfig.projbuild new file mode 100644 index 000000000..08cb2f97a --- /dev/null +++ b/examples/provisioning/softap_prov/main/Kconfig.projbuild @@ -0,0 +1,53 @@ +menu "Example Configuration" + +config SOFTAP_SSID_SET_MAC + bool "Use MAC as SSID" + default y + help + Set SoftAP SSID as PROV_. + +config SOFTAP_SSID + string "WiFi SSID" + default "PROV_SSID" + depends on !SOFTAP_SSID_SET_MAC + help + SSID (network name) for the example to connect to. + +config SOFTAP_PASS + string "WiFi Password" + default "PROV_PASS" + help + WiFi password (WPA or WPA2) for the example to use. + +config USE_SEC_1 + bool + default y + prompt "Use Security Version 1" + help + Security version 1 used Curve25519 key exchange for establishing + secure session between device and client during provisioning + +config USE_POP + bool + depends on USE_SEC_1 + default y + prompt "Use proof-of-possession" + help + Proof-of-possession can be optionally used to prove that the device is indeed + in possession of the user who is provisioning the device. This proof-of-possession + is internally used to generate the shared secret through key exchange. + +config POP + string "Proof-of-possession" + default "abcd1234" + depends on USE_POP + +config RESET_PROVISIONED + bool + default n + prompt "Reset provisioned status of the device" + help + This erases the NVS to reset provisioned status of the device on every reboot. + Provisioned status is determined by the WiFi STA configuration, saved on the NVS. + +endmenu diff --git a/examples/provisioning/softap_prov/main/app_main.c b/examples/provisioning/softap_prov/main/app_main.c new file mode 100644 index 000000000..65a100e4d --- /dev/null +++ b/examples/provisioning/softap_prov/main/app_main.c @@ -0,0 +1,131 @@ +/* SoftAP based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "app_prov.h" + +static const char *TAG = "app"; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + /* Invoke Provisioning event handler first */ + app_prov_event_handler(ctx, event); + + switch(event->event_id) { + case SYSTEM_EVENT_AP_START: + ESP_LOGI(TAG, "SoftAP started"); + break; + case SYSTEM_EVENT_AP_STOP: + ESP_LOGI(TAG, "SoftAP stopped"); + break; + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:"MACSTR" join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:"MACSTR"leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + break; + default: + break; + } + return ESP_OK; +} + +static void wifi_init_sta() +{ + /* Start wifi in station mode with credentials set during provisioning */ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_start() ); +} + +void app_main() +{ + /* Security version */ + int security = 0; + /* Proof of possession */ + const protocomm_security_pop_t *pop = NULL; + +#ifdef CONFIG_USE_SEC_1 + security = 1; +#endif + + /* Having proof of possession is optional */ +#ifdef CONFIG_USE_POP + const static protocomm_security_pop_t app_pop = { + .data = (uint8_t *) CONFIG_POP, + .len = (sizeof(CONFIG_POP)-1) + }; + pop = &app_pop; +#endif + + /* Initialize networking stack */ + tcpip_adapter_init(); + + /* Set our event handling */ + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + + /* Check if device is provisioned */ + bool provisioned; + if (app_prov_is_provisioned(&provisioned) != ESP_OK) { + ESP_LOGE(TAG, "Error getting device provisioning state"); + return; + } + + if (provisioned == false) { + /* If not provisioned, start provisioning via soft AP */ + ESP_LOGI(TAG, "Starting WiFi SoftAP provisioning"); + + const char *ssid = NULL; + +#ifdef CONFIG_SOFTAP_SSID + ssid = CONFIG_SOFTAP_SSID; +#else + uint8_t eth_mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, eth_mac); + + char ssid_with_mac[33]; + snprintf(ssid_with_mac, sizeof(ssid_with_mac), "PROV_%02X%02X%02X", + eth_mac[3], eth_mac[4], eth_mac[5]); + + ssid = ssid_with_mac; +#endif + + app_prov_start_softap_provisioning(ssid, CONFIG_SOFTAP_PASS, + security, pop); + } else { + /* Start WiFi station with credentials set during provisioning */ + ESP_LOGI(TAG, "Starting WiFi station"); + wifi_init_sta(NULL); + } +} diff --git a/examples/provisioning/softap_prov/main/app_prov.c b/examples/provisioning/softap_prov/main/app_prov.c new file mode 100644 index 000000000..b110d2e52 --- /dev/null +++ b/examples/provisioning/softap_prov/main/app_prov.c @@ -0,0 +1,386 @@ +/* SoftAP based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "app_prov.h" + +static const char *TAG = "app_prov"; + +/* Handlers for wifi_config provisioning endpoint */ +extern wifi_prov_config_handlers_t wifi_prov_handlers; + +/** + * @brief Data relevant to provisioning application + */ +struct app_prov_data { + protocomm_t *pc; /*!< Protocomm handler */ + int security; /*!< Type of security to use with protocomm */ + const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */ + esp_timer_handle_t timer; /*!< Handle to timer */ + + /* State of WiFi Station */ + wifi_prov_sta_state_t wifi_state; + + /* Code for WiFi station disconnection (if disconnected) */ + wifi_prov_sta_fail_reason_t wifi_disconnect_reason; +}; + +/* Pointer to provisioning application data */ +static struct app_prov_data *g_prov; + +static esp_err_t app_prov_start_service(void) +{ + /* Create new protocomm instance */ + g_prov->pc = protocomm_new(); + if (g_prov->pc == NULL) { + ESP_LOGE(TAG, "Failed to create new protocomm instance"); + return ESP_FAIL; + } + + /* Config for protocomm_httpd_start() */ + protocomm_httpd_config_t pc_config = PROTOCOMM_HTTPD_DEFAULT_CONFIG(); + + /* Start protocomm server on top of HTTP */ + if (protocomm_httpd_start(g_prov->pc, &pc_config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start protocomm HTTP server"); + return ESP_FAIL; + } + + /* Set protocomm version verification endpoint for protocol */ + protocomm_set_version(g_prov->pc, "proto-ver", "V0.1"); + + /* Set protocomm security type for endpoint */ + if (g_prov->security == 0) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL); + } else if (g_prov->security == 1) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop); + } + + /* Add endpoint for provisioning to set wifi station config */ + if (protocomm_add_endpoint(g_prov->pc, "prov-config", + wifi_prov_config_data_handler, + (void *) &wifi_prov_handlers) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set provisioning endpoint"); + protocomm_httpd_stop(g_prov->pc); + return ESP_FAIL; + } + return ESP_OK; +} + +static void app_prov_stop_service(void) +{ + /* Remove provisioning endpoint */ + protocomm_remove_endpoint(g_prov->pc, "prov-config"); + /* Unset provisioning security */ + protocomm_unset_security(g_prov->pc, "prov-session"); + /* Unset provisioning version endpoint */ + protocomm_unset_version(g_prov->pc, "proto-ver"); + /* Stop protocomm server */ + protocomm_httpd_stop(g_prov->pc); + /* Delete protocomm instance */ + protocomm_delete(g_prov->pc); +} + +/* Callback to be invoked by timer */ +static void _stop_softap_cb(void * arg) +{ + ESP_LOGI(TAG, "Stopping provisioning"); + app_prov_stop_service(); + esp_wifi_set_mode(WIFI_MODE_STA); + + /* Timer not needed anymore */ + esp_timer_handle_t timer = g_prov->timer; + esp_timer_delete(timer); + g_prov->timer = NULL; + + /* Free provisioning process data */ + free(g_prov); + g_prov = NULL; + ESP_LOGI(TAG, "Provisioning stopped"); +} + +/* Event handler for starting/stopping provisioning. + * To be called from within the context of the main + * event handler. + */ +esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) +{ + /* For accessing reason codes in case of disconnection */ + system_event_info_t *info = &event->event_info; + + /* If pointer to provisioning application data is NULL + * then provisioning is not running, therefore return without + * error */ + if (!g_prov) { + return ESP_OK; + } + + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + ESP_LOGI(TAG, "STA Start"); + /* Once configuration is received by protocomm server, + * device is restarted as both AP and Station. + * Once station starts, wait for connection to + * establish with configured host SSID and password */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + break; + + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "STA Got IP"); + /* Station got IP. That means configuraion is successful. + * Schedule timer to stop provisioning app after 30 seconds. */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTED; + if (g_prov && g_prov->timer) { + /* Note that, after restarting the WiFi in Station + AP mode, the + * user gets disconnected from the AP for a while. But at the same + * time, the user app requests for status update from the device + * to verify that the provisioning was successful. Therefore, the + * turning off of the AP must be delayed long enough for the user + * to reconnect and get STA connection status from the device. + * Otherwise, the AP will be turned off before the user can + * reconnect and thus the user app will see connection timed out, + * signalling a failure in provisioning. */ + esp_timer_start_once(g_prov->timer, 30000*1000U); + } + break; + + case SYSTEM_EVENT_STA_DISCONNECTED: + ESP_LOGE(TAG, "STA Disconnected"); + /* Station couldn't connect to configured host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED; + ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason); + + /* Set code corresponding to the reason for disconnection */ + switch (info->disconnected.reason) { + case WIFI_REASON_AUTH_EXPIRE: + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + case WIFI_REASON_BEACON_TIMEOUT: + case WIFI_REASON_AUTH_FAIL: + case WIFI_REASON_ASSOC_FAIL: + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ESP_LOGI(TAG, "STA Auth Error"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR; + break; + case WIFI_REASON_NO_AP_FOUND: + ESP_LOGI(TAG, "STA AP Not found"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND; + break; + default: + /* If none of the expected reasons, + * retry connecting to host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + esp_wifi_connect(); + } + break; + + default: + break; + } + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state) +{ + if (g_prov == NULL || state == NULL) { + return ESP_FAIL; + } + + *state = g_prov->wifi_state; + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason) +{ + if (g_prov == NULL || reason == NULL) { + return ESP_FAIL; + } + + if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) { + return ESP_FAIL; + } + + *reason = g_prov->wifi_disconnect_reason; + return ESP_OK; +} + +esp_err_t app_prov_is_provisioned(bool *provisioned) +{ + *provisioned = false; + +#ifdef CONFIG_RESET_PROVISIONED + nvs_flash_erase(); +#endif + + if (nvs_flash_init() != ESP_OK) { + ESP_LOGE(TAG, "Failed to init NVS"); + return ESP_FAIL; + } + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + if (esp_wifi_init(&cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to init wifi"); + return ESP_FAIL; + } + + /* Get WiFi Station configuration */ + wifi_config_t wifi_cfg; + if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) { + return ESP_FAIL; + } + + if (strlen((const char*) wifi_cfg.sta.ssid)) { + *provisioned = true; + ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid); + ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password); + } + return ESP_OK; +} + +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg) +{ + /* Configure WiFi as both AP and Station */ + if (esp_wifi_set_mode(WIFI_MODE_APSTA) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode"); + return ESP_FAIL; + } + /* Configure WiFi station with host credentials + * provided during provisioning */ + if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi configuration"); + return ESP_FAIL; + } + /* Restart WiFi */ + if (esp_wifi_start() != ESP_OK) { + ESP_LOGE(TAG, "Failed to restart WiFi"); + return ESP_FAIL; + } + /* Connect to AP */ + if (esp_wifi_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect WiFi"); + return ESP_FAIL; + } + + if (g_prov) { + /* Reset wifi station state for provisioning app */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + } + return ESP_OK; +} + +static esp_err_t start_wifi_ap(const char *ssid, const char *pass) +{ + /* Initialise WiFi with default configuration */ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t err = esp_wifi_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to init WiFi : %d", err); + return err; + } + + /* Build WiFi configuration for AP mode */ + wifi_config_t wifi_config = { + .ap = { + .max_connection = 5, + }, + }; + + strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); + wifi_config.ap.ssid_len = strlen(ssid); + + if (strlen(pass) == 0) { + memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password)); + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } else { + strncpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); + wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; + } + + /* Start WiFi in AP mode with configuration built above */ + err = esp_wifi_set_mode(WIFI_MODE_AP); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode : %d", err); + return err; + } + err = esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi config : %d", err); + return err; + } + err = esp_wifi_start(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start WiFi : %d", err); + return err; + } + + return ESP_OK; +} + +esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass, + int security, const protocomm_security_pop_t *pop) +{ + /* If provisioning app data present, + * means provisioning app is already running */ + if (g_prov) { + ESP_LOGI(TAG, "Invalid provisioning state"); + return ESP_FAIL; + } + + /* Allocate memory for provisioning app data */ + g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data)); + if (!g_prov) { + ESP_LOGI(TAG, "Unable to allocate prov data"); + return ESP_ERR_NO_MEM; + } + + /* Initialise app data */ + g_prov->pop = pop; + g_prov->security = security; + + /* Create timer object as a member of app data */ + esp_timer_create_args_t timer_conf = { + .callback = _stop_softap_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "stop_softap_tm" + }; + esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to create timer"); + return err; + } + + /* Start WiFi softAP with specified ssid and password */ + err = start_wifi_ap(ssid, pass); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start WiFi AP"); + return err; + } + + /* Start provisioning service through HTTP */ + err = app_prov_start_service(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start provisioning app"); + return err; + } + + ESP_LOGI(TAG, "SoftAP Provisioning started with SSID '%s', Password '%s'", ssid, pass); + return ESP_OK; +} diff --git a/examples/provisioning/softap_prov/main/app_prov.h b/examples/provisioning/softap_prov/main/app_prov.h new file mode 100644 index 000000000..ec196585a --- /dev/null +++ b/examples/provisioning/softap_prov/main/app_prov.h @@ -0,0 +1,102 @@ +/* SoftAP based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#pragma once + +#include + +#include +#include + +/** + * @brief Get state of WiFi Station during provisioning + * + * @note WiFi is initially configured as AP, when + * provisioning starts. After provisioning data + * is provided by user, the WiFi is reconfigured + * to run as both AP and Station. + * + * @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi state + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state); + +/** + * @brief Get reason code in case of WiFi station + * disconnection during provisioning + * +* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi disconnect reason + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason); + +/** + * @brief Event handler for provisioning app + * + * This is called from the main event handler and controls the + * provisioning application, depeding on WiFi events + * + * @param[in] ctx Event context data + * @param[in] event Event info + * + * @return + * - ESP_OK : Event handled successfully + * - ESP_FAIL : Failed to start server on event AP start + */ +esp_err_t app_prov_event_handler(void *ctx, system_event_t *event); + +/** + * @brief Checks if device is provisioned + * * + * @param[out] provisioned True if provisioned, else false + * + * @return + * - ESP_OK : Retrieved provision state successfully + * - ESP_FAIL : Failed to retrieve provision state + */ +esp_err_t app_prov_is_provisioned(bool *provisioned); + +/** + * @brief Runs WiFi as both AP and Station + * + * Configures the WiFi station mode to connect to the + * SSID and password specified in config structure, + * and restarts WiFi to run as both AP and station + * + * @param[in] wifi_cfg Pointer to WiFi cofiguration structure + * + * @return + * - ESP_OK : WiFi configured and restarted successfully + * - ESP_FAIL : Failed to set configuration + */ +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg); + +/** + * @brief Start provisioning via softAP + * + * Starts the WiFi softAP with specified ssid and pass, provisioning + * security mode and proof of possession (if any). + * + * @param[in] ssid SSID for SoftAP + * @param[in] pass Password for SoftAP + * @param[in] security Security mode + * @param[in] pop Pointer to proof of possession (NULL if not present) + * + * @return + * - ESP_OK : Provisioning started successfully + * - ESP_FAIL : Failed to start + */ +esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass, + int security, const protocomm_security_pop_t *pop); diff --git a/examples/provisioning/softap_prov/main/app_prov_handlers.c b/examples/provisioning/softap_prov/main/app_prov_handlers.c new file mode 100644 index 000000000..c9704576c --- /dev/null +++ b/examples/provisioning/softap_prov/main/app_prov_handlers.c @@ -0,0 +1,106 @@ +/* SoftAP based Provisioning Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* This file is mostly a boiler-plate code that applications can use without much change */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "app_prov.h" + +static const char* TAG = "app_prov_handler"; + +static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data) +{ + /* Initialise to zero */ + memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t)); + + if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) { + ESP_LOGW(TAG, "Prov app not running"); + return ESP_FAIL; + } + + if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) { + ESP_LOGI(TAG, "Connected state"); + + /* IP Addr assigned to STA */ + tcpip_adapter_ip_info_t ip_info; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); + char *ip_addr = ip4addr_ntoa(&ip_info.ip); + strcpy(resp_data->conn_info.ip_addr, ip_addr); + + /* AP information to which STA is connected */ + wifi_ap_record_t ap_info; + esp_wifi_sta_get_ap_info(&ap_info); + memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid)); + memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid)); + resp_data->conn_info.channel = ap_info.primary; + resp_data->conn_info.auth_mode = ap_info.authmode; + } else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Disconnected state"); + + /* If disconnected, convey reason */ + app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason); + } else { + ESP_LOGI(TAG, "Connecting state"); + } + return ESP_OK; +} + +static wifi_config_t *wifi_cfg; + +static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data) +{ + if (wifi_cfg) { + free(wifi_cfg); + wifi_cfg = NULL; + } + + wifi_cfg = (wifi_config_t *) calloc(1, sizeof(wifi_config_t)); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Unable to alloc wifi config"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s", + req_data->ssid, req_data->password); + memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid, + strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid))); + memcpy((char *) wifi_cfg->sta.password, req_data->password, + strnlen(req_data->password, sizeof(wifi_cfg->sta.password))); + return ESP_OK; +} + +static esp_err_t apply_config_handler(void) +{ + if (!wifi_cfg) { + ESP_LOGE(TAG, "WiFi config not set"); + return ESP_FAIL; + } + + app_prov_configure_sta(wifi_cfg); + ESP_LOGI(TAG, "WiFi Credentials Applied"); + + free(wifi_cfg); + wifi_cfg = NULL; + return ESP_OK; +} + +wifi_prov_config_handlers_t wifi_prov_handlers = { + .get_status_handler = get_status_handler, + .set_config_handler = set_config_handler, + .apply_config_handler = apply_config_handler, +}; diff --git a/examples/provisioning/softap_prov/main/component.mk b/examples/provisioning/softap_prov/main/component.mk new file mode 100644 index 000000000..61f8990c3 --- /dev/null +++ b/examples/provisioning/softap_prov/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/provisioning/softap_prov/softap_prov_test.py b/examples/provisioning/softap_prov/softap_prov_test.py new file mode 100644 index 000000000..d986847d8 --- /dev/null +++ b/examples/provisioning/softap_prov/softap_prov_test.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. + +from __future__ import print_function +import imp +import re +import os +import sys +import time + +# This environment variable is expected on the host machine +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +# When running on local machine execute the following before running this script +# > export TEST_FW_PATH='~/esp/esp-idf/tools/tiny-test-fw' +# > make print_flash_cmd | tail -n 1 > build/download.config +# > make app bootloader + +import TinyFW +import IDF + +# Import esp_prov tool +idf_path = os.environ['IDF_PATH'] +esp_prov = imp.load_source("esp_prov", idf_path + "/tools/esp_prov/esp_prov.py") +wifi_tools = imp.load_source("wifi_tools", idf_path + "/examples/provisioning/softap_prov/utils/wifi_tools.py") + +@IDF.idf_example_test(env_tag="Example_WIFI_BT") +def test_examples_provisioning_softap(env, extra_data): + # Acquire DUT + dut1 = env.get_dut("softap_prov", "examples/provisioning/softap_prov") + + # Get binary file + binary_file = os.path.join(dut1.app.binary_path, "softap_prov.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("softap_prov_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("softap_prov_bin_size", bin_size//1024) + + # Upload binary and start testing + dut1.start_app() + + # Parse IP address of STA + dut1.expect("Starting WiFi SoftAP provisioning") + dut1.expect("SoftAP started") + [ssid, password] = dut1.expect(re.compile(r"(?:[\s\S]*)SoftAP Provisioning started with SSID '(\S+)', Password '(\S+)'")) + + iface = wifi_tools.get_wiface_name() + if iface == None: + raise RuntimeError("Failed to get Wi-Fi interface on host") + print("Interface name : " + iface) + print("SoftAP SSID : " + ssid) + print("SoftAP Password : " + password) + + ctrl = wifi_tools.wpa_cli(iface, reset_on_exit = True) + print("Connecting to DUT SoftAP...") + ip = ctrl.connect(ssid, password) + print("Connected to DUT SoftAP") + + print("Starting Provisioning") + verbose = False + protover = "V0.1" + secver = 1 + pop = "abcd1234" + provmode = "softap" + ap_ssid = "myssid" + ap_password = "mypassword" + softap_endpoint = ip.split('.')[0] + "." + ip.split('.')[1]+ "." + ip.split('.')[2] + ".1:80" + + print("Getting security") + security = esp_prov.get_security(secver, pop, verbose) + if security == None: + raise RuntimeError("Failed to get security") + + print("Getting transport") + transport = esp_prov.get_transport(provmode, softap_endpoint, None) + if transport == None: + raise RuntimeError("Failed to get transport") + + print("Verifying protocol version") + if not esp_prov.version_match(transport, protover): + raise RuntimeError("Mismatch in protocol version") + + print("Starting Session") + if not esp_prov.establish_session(transport, security): + raise RuntimeError("Failed to start session") + + print("Sending Wifi credential to DUT") + if not esp_prov.send_wifi_config(transport, security, ap_ssid, ap_password): + raise RuntimeError("Failed to send Wi-Fi config") + + print("Applying config") + if not esp_prov.apply_wifi_config(transport, security): + raise RuntimeError("Failed to send apply config") + + success = False + while True: + time.sleep(5) + print("Wi-Fi connection state") + ret = esp_prov.get_wifi_config(transport, security) + if (ret == 1): + continue + elif (ret == 0): + print("Provisioning was successful") + success = True + break + + if not success: + raise RuntimeError("Provisioning failed") + +if __name__ == '__main__': + test_examples_provisioning_softap() diff --git a/examples/provisioning/softap_prov/utils/wifi_tools.py b/examples/provisioning/softap_prov/utils/wifi_tools.py new file mode 100644 index 000000000..1d40111d5 --- /dev/null +++ b/examples/provisioning/softap_prov/utils/wifi_tools.py @@ -0,0 +1,98 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +import dbus +import dbus.mainloop.glib +import netifaces +import time + +def get_wiface_name(): + for iface in netifaces.interfaces(): + if iface.startswith('w'): + return iface + return None + +def get_wiface_IPv4(iface): + try: + [info] = netifaces.ifaddresses(iface)[netifaces.AF_INET] + return info['addr'] + except KeyError: + return None + +class wpa_cli: + def __init__(self, iface, reset_on_exit = False): + self.iface_name = iface + self.iface_obj = None + self.iface_ifc = None + self.old_network = None + self.new_network = None + self.connected = False + self.reset_on_exit = reset_on_exit + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + bus = dbus.SystemBus() + + service = dbus.Interface(bus.get_object("fi.w1.wpa_supplicant1", "/fi/w1/wpa_supplicant1"), "fi.w1.wpa_supplicant1") + paths = service.Get("fi.w1.wpa_supplicant1", "Interfaces", dbus_interface='org.freedesktop.DBus.Properties') + iface_path = service.GetInterface(self.iface_name) + self.iface_obj = bus.get_object("fi.w1.wpa_supplicant1", iface_path) + self.iface_ifc = dbus.Interface(self.iface_obj, "fi.w1.wpa_supplicant1.Interface") + if self.iface_ifc == None: + raise RuntimeError('supplicant : Failed to fetch interface') + + self.old_network = self.iface_obj.Get("fi.w1.wpa_supplicant1.Interface", "CurrentNetwork", dbus_interface='org.freedesktop.DBus.Properties') + if self.old_network == '/': + self.old_network = None + else: + self.connected = True + + def connect(self, ssid, password): + if self.connected == True: + self.iface_ifc.Disconnect() + self.connected = False + + if self.new_network != None: + self.iface_ifc.RemoveNetwork(self.new_network) + + self.new_network = self.iface_ifc.AddNetwork({"ssid": ssid, "psk": password}) + self.iface_ifc.SelectNetwork(self.new_network) + + ip = None + retry = 10 + while retry > 0: + time.sleep(5) + ip = get_wiface_IPv4(self.iface_name) + if ip != None: + self.connected = True + return ip + retry -= 1 + + self.reset() + raise RuntimeError('wpa_cli : Connection failed') + + def reset(self): + if self.iface_ifc != None: + if self.connected == True: + self.iface_ifc.Disconnect() + self.connected = False + if self.new_network != None: + self.iface_ifc.RemoveNetwork(self.new_network) + self.new_network = None + if self.old_network != None: + self.iface_ifc.SelectNetwork(self.old_network) + self.old_network = None + + def __del__(self): + if self.reset_on_exit == True: + self.reset() diff --git a/tools/esp_prov/README.md b/tools/esp_prov/README.md new file mode 100644 index 000000000..9d7a0e919 --- /dev/null +++ b/tools/esp_prov/README.md @@ -0,0 +1,109 @@ +# ESP Provisioning Tool + +# NAME +`esp_prov` - A python based utility for testing the provisioning examples over a host + +# SYNOPSIS + +``` +python esp_prov.py --transport < mode of provisioning : softap \ ble \ console > --ssid < AP SSID > --passphrase < AP Password > --sec_ver < Security version 0 / 1 > [ Optional parameters... ] +``` + +# DESCRIPTION + +Usage of `esp-prov` assumes that the provisioning app has specific protocomm endpoints active. These endpoints are active in the provisioning examples and accept specific protobuf data structures: + +| Endpoint Name | URI (HTTP server on ip:port) | UUID (BLE) | Description | +|---------------|------------------------------|--------------------------------------|-----------------------------------------------------------| +| prov-session | http://ip:port/prov-session | 0000ff51-0000-1000-8000-00805f9b34fb | Security endpoint used for session establishment | +| prov-config | http://ip:port/prov-config | 0000ff52-0000-1000-8000-00805f9b34fb | Endpoint used for configuring Wi-Fi credentials on device | +| proto-ver | http://ip:port/proto-ver | 0000ff53-0000-1000-8000-00805f9b34fb | Version endpoint for checking protocol compatibility | +| custom-config | http://ip:port/custom-config | NA | Optional endpoint for configuring custom credentials | + +# PARAMETERS + +* `--help` + Print the list of options along with brief descriptions + +* `--verbose`, `-v` + Sets the verbosity level of output log + +* `--transport ` + Three options are available: + * `softap` + For SoftAP + HTTPD based provisioning. This assumes that the device is running in Wi-Fi SoftAP mode and hosts an HTTP server supporting specific endpoint URIs. Also client needs to connect to the device softAP network before running `esp_prov` + * `ble` + For BLE based provisioning (Linux support only. In Windows/macOS it redirects to console). This assumes that the provisioning endpoints are active on the device with specific BLE service UUIDs + * `console` + For debugging via console based provisioning. The client->device commands are printed to STDOUT and device->client messages are accepted via STDIN. This is to be used when device is accepting provisioning commands on UART console. + +* `--ssid ` + For specifying the SSID of the Wi-Fi AP to which the device is to connect after provisioning + +* `--passphrase ` + For specifying the password of the Wi-Fi AP to which the device is to connect after provisioning + +* `--sec_ver ` + For specifying version of protocomm endpoint security to use. For now two versions are supported: + * `0` for `protocomm_security0` + * `1` for `protocomm_security1` + +* `--pop ` (Optional) + For specifying optional Proof of Possession string to use for protocomm endpoint security version 1. This option is ignored when security version 0 is in use + +* `--proto_ver ` (Optional) (Default `V0.1`) + For specifying version string for checking compatibility with provisioning app prior to starting provisioning process + +* `--softap_endpoint ` (Optional) (Default `192.168.4.1:80`) + For specifying the IP and port of the HTTP server on which provisioning app is running. The client must connect to the device SoftAP prior to running `esp_prov` + +* `--ble_devname ` (Optional) + For specifying name of the BLE device to which connection is to be established prior to starting provisioning process. This is only used when `--transport ble` is specified, else it is ignored. Since connection with BLE is supported only on Linux, so this option is again ignored for other platforms + +* `--custom_config` (Optional) + This flag assumes the provisioning app has an endpoint called `custom-config`. Use `--custom_info` and `--custom_ver` options to specify the fields accepted by this endpoint + +* `--custom_info ` (Optional) (Only use along with `--custom_config`) + For specifying an information string to be sent to the `custom-config` endpoint during provisioning + +* `--custom_ver ` (Optional) (Only use along with `--custom_config`) + For specifying a version number (int) to be sent to the `custom-config` endpoint during provisioning + +# AVAILABILITY + +`esp_prov` is intended as a cross-platform tool, but currently BLE communication functionality is only available on Linux (via BlueZ and DBus) + +For android, a provisioning tool along with source code is available [here](https://github.com/espressif/esp-idf-provisioning-android) + +On macOS and Windows, running with `--transport ble` option falls back to console mode, ie. write data and target UUID are printed to STDOUT and read data is input through STDIN. Users are free to use their app of choice to connect to the BLE device, send the write data to the target characteristic and read from it. + +## Dependencies + +This requires the following python libraries to run (included in requirements.txt): +* `future` +* `protobuf` +* `cryptography` + +Run `pip install -r $IDF_PATH/tools/esp_prov/requirements.txt` + +Note : +* The packages listed in requirements.txt are limited only to the ones needed AFTER fully satisfying the requirements of ESP-IDF +* BLE communication is only supported on Linux (via Bluez and DBus), therefore, the dependencies for this have been made optional + +## Optional Dependencies (Linux Only) + +These dependencies are for enabling communication with BLE devices using Bluez and DBus on Linux: +* `dbus-python` + +Run `pip install -r $IDF_PATH/tools/esp_prov/requirements_linux_extra.txt` + +# EXAMPLE USAGE + +Please refer to the README.md files with the examples present under `$IDF_PATH/examples/provisioning/`, namely: + +* `ble_prov` +* `softap_prov` +* `custom_config` +* `console_prov` + +Each of these examples use specific options of the `esp_prov` tool and give an overview to simple as well as advanced usage scenarios. diff --git a/tools/esp_prov/esp_prov.py b/tools/esp_prov/esp_prov.py new file mode 100644 index 000000000..10ad67134 --- /dev/null +++ b/tools/esp_prov/esp_prov.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +# +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +from __future__ import print_function +import argparse +import time +import os +import sys + +idf_path = os.environ['IDF_PATH'] +sys.path.insert(0, idf_path + "/components/protocomm/python") +sys.path.insert(1, idf_path + "/tools/esp_prov") + +import security +import transport +import prov + +def get_security(secver, pop=None, verbose=False): + if secver == 1: + return security.Security1(pop, verbose) + elif secver == 0: + return security.Security0(verbose) + return None + +def get_transport(sel_transport, softap_endpoint=None, ble_devname=None): + try: + tp = None + if (sel_transport == 'softap'): + tp = transport.Transport_Softap(softap_endpoint) + elif (sel_transport == 'ble'): + tp = transport.Transport_BLE(devname = ble_devname, + service_uuid = '0000ffff-0000-1000-8000-00805f9b34fb', + nu_lookup = { + 'prov-session': 'ff51', + 'prov-config' : 'ff52', + 'proto-ver' : 'ff53' + }) + elif (sel_transport == 'console'): + tp = transport.Transport_Console() + return tp + except RuntimeError as e: + print(e) + return None + +def version_match(tp, protover): + try: + response = tp.send_data('proto-ver', protover) + if response != "SUCCESS": + return False + return True + except RuntimeError as e: + print(e) + return None + +def establish_session(tp, sec): + try: + response = None + while True: + request = sec.security_session(response) + if request == None: + break + response = tp.send_data('prov-session', request) + if (response == None): + return False + return True + except RuntimeError as e: + print(e) + return None + +def custom_config(tp, sec, custom_info, custom_ver): + try: + message = prov.custom_config_request(sec, custom_info, custom_ver) + response = tp.send_data('custom-config', message) + return (prov.custom_config_response(sec, response) == 0) + except RuntimeError as e: + print(e) + return None + +def send_wifi_config(tp, sec, ssid, passphrase): + try: + message = prov.config_set_config_request(sec, ssid, passphrase) + response = tp.send_data('prov-config', message) + return (prov.config_set_config_response(sec, response) == 0) + except RuntimeError as e: + print(e) + return None + +def apply_wifi_config(tp, sec): + try: + message = prov.config_apply_config_request(sec) + response = tp.send_data('prov-config', message) + return (prov.config_set_config_response(sec, response) == 0) + except RuntimeError as e: + print(e) + return None + +def get_wifi_config(tp, sec): + try: + message = prov.config_get_status_request(sec) + response = tp.send_data('prov-config', message) + return prov.config_get_status_response(sec, response) + except RuntimeError as e: + print(e) + return None + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Generate ESP prov payload") + + parser.add_argument("--ssid", dest = 'ssid', type = str, + help = "SSID of Wi-Fi Network", required = True) + parser.add_argument("--passphrase", dest = 'passphrase', type = str, + help = "Passphrase of Wi-Fi network", default = '') + + parser.add_argument("--sec_ver", dest = 'secver', type = int, + help = "Security scheme version", default = 1) + parser.add_argument("--proto_ver", dest = 'protover', type = str, + help = "Protocol version", default = 'V0.1') + parser.add_argument("--pop", dest = 'pop', type = str, + help = "Proof of possession", default = '') + + parser.add_argument("--softap_endpoint", dest = 'softap_endpoint', type = str, + help = ", http(s):// shouldn't be included", default = '192.168.4.1:80') + + parser.add_argument("--ble_devname", dest = 'ble_devname', type = str, + help = "BLE Device Name", default = '') + + parser.add_argument("--transport", dest = 'provmode', type = str, + help = "provisioning mode i.e console or softap or ble", default = 'softap') + + parser.add_argument("--custom_config", help="Provision Custom Configuration", + action = "store_true") + parser.add_argument("--custom_info", dest = 'custom_info', type = str, + help = "Custom Config Info String", default = '') + parser.add_argument("--custom_ver", dest = 'custom_ver', type = int, + help = "Custom Config Version Number", default = 2) + + parser.add_argument("-v","--verbose", help = "increase output verbosity", + action = "store_true") + args = parser.parse_args() + + print("==== Esp_Prov Version: " + args.protover + " ====") + + security = get_security(args.secver, args.pop, args.verbose) + if security == None: + print("---- Invalid Security Version ----") + exit(1) + + transport = get_transport(args.provmode, args.softap_endpoint, args.ble_devname) + if transport == None: + print("---- Invalid provisioning mode ----") + exit(2) + + print("\n==== Verifying protocol version ====") + if not version_match(transport, args.protover): + print("---- Error in protocol version matching ----") + exit(3) + print("==== Verified protocol version successfully ====") + + print("\n==== Starting Session ====") + if not establish_session(transport, security): + print("---- Error in establishing session ----") + exit(4) + print("==== Session Established ====") + + if args.custom_config: + print("\n==== Sending Custom config to esp32 ====") + if not custom_config(transport, security, args.custom_info, args.custom_ver): + print("---- Error in custom config ----") + exit(5) + print("==== Custom config sent successfully ====") + + print("\n==== Sending Wi-Fi credential to esp32 ====") + if not send_wifi_config(transport, security, args.ssid, args.passphrase): + print("---- Error in send Wi-Fi config ----") + exit(6) + print("==== Wi-Fi Credentials sent successfully ====") + + print("\n==== Applying config to esp32 ====") + if not apply_wifi_config(transport, security): + print("---- Error in apply Wi-Fi config ----") + exit(7) + print("==== Apply config sent successfully ====") + + while True: + time.sleep(5) + print("\n==== Wi-Fi connection state ====") + ret = get_wifi_config(transport, security) + if (ret == 1): + continue + elif (ret == 0): + print("==== Provisioning was successful ====") + else: + print("---- Provisioning failed ----") + break diff --git a/tools/esp_prov/proto/__init__.py b/tools/esp_prov/proto/__init__.py new file mode 100644 index 000000000..52141d0d2 --- /dev/null +++ b/tools/esp_prov/proto/__init__.py @@ -0,0 +1,32 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +import imp +import os + +idf_path = os.environ['IDF_PATH'] + +# protocomm component related python files generated from .proto files +constants_pb2 = imp.load_source("constants_pb2", idf_path + "/components/protocomm/python/constants_pb2.py") +sec0_pb2 = imp.load_source("sec0_pb2", idf_path + "/components/protocomm/python/sec0_pb2.py") +sec1_pb2 = imp.load_source("sec1_pb2", idf_path + "/components/protocomm/python/sec1_pb2.py") +session_pb2 = imp.load_source("session_pb2", idf_path + "/components/protocomm/python/session_pb2.py") + +# wifi_provisioning component related python files generated from .proto files +wifi_constants_pb2 = imp.load_source("wifi_constants_pb2", idf_path + "/components/wifi_provisioning/python/wifi_constants_pb2.py") +wifi_config_pb2 = imp.load_source("wifi_config_pb2", idf_path + "/components/wifi_provisioning/python/wifi_config_pb2.py") + +# custom_provisioning component related python files generated from .proto files +custom_config_pb2 = imp.load_source("custom_config_pb2", idf_path + "/examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py") diff --git a/tools/esp_prov/prov/__init__.py b/tools/esp_prov/prov/__init__.py new file mode 100644 index 000000000..797084536 --- /dev/null +++ b/tools/esp_prov/prov/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +from .wifi_prov import * +from .custom_prov import * diff --git a/tools/esp_prov/prov/custom_prov.py b/tools/esp_prov/prov/custom_prov.py new file mode 100644 index 000000000..d5d291326 --- /dev/null +++ b/tools/esp_prov/prov/custom_prov.py @@ -0,0 +1,43 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +# APIs for interpreting and creating protobuf packets for `custom-config` protocomm endpoint + +from __future__ import print_function +from future.utils import tobytes + +import utils +import proto + +def print_verbose(security_ctx, data): + if (security_ctx.verbose): + print("++++ " + data + " ++++") + +def custom_config_request(security_ctx, info, version): + # Form protobuf request packet from custom-config data + cmd = proto.custom_config_pb2.CustomConfigRequest() + cmd.info = tobytes(info) + cmd.version = version + enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1') + print_verbose(security_ctx, "Client -> Device (CustomConfig cmd) " + utils.str_to_hexstr(enc_cmd)) + return enc_cmd + +def custom_config_response(security_ctx, response_data): + # Interpret protobuf response packet + decrypt = security_ctx.decrypt_data(tobytes(response_data)) + cmd_resp = proto.custom_config_pb2.CustomConfigResponse() + cmd_resp.ParseFromString(decrypt) + print_verbose(security_ctx, "CustomConfig status " + str(cmd_resp.status)) + return cmd_resp.status diff --git a/tools/esp_prov/prov/wifi_prov.py b/tools/esp_prov/prov/wifi_prov.py new file mode 100644 index 000000000..ff6319b78 --- /dev/null +++ b/tools/esp_prov/prov/wifi_prov.py @@ -0,0 +1,91 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +# APIs for interpreting and creating protobuf packets for Wi-Fi provisioning + +from __future__ import print_function +from future.utils import tobytes + +import utils +import proto + +def print_verbose(security_ctx, data): + if (security_ctx.verbose): + print("++++ " + data + " ++++") + +def config_get_status_request(security_ctx): + # Form protobuf request packet for GetStatus command + cfg1 = proto.wifi_config_pb2.WiFiConfigPayload() + cfg1.msg = proto.wifi_config_pb2.TypeCmdGetStatus + cmd_get_status = proto.wifi_config_pb2.CmdGetStatus() + cfg1.cmd_get_status.MergeFrom(cmd_get_status) + encrypted_cfg = security_ctx.encrypt_data(cfg1.SerializeToString()).decode('latin-1') + print_verbose(security_ctx, "Client -> Device (Encrypted CmdGetStatus) " + utils.str_to_hexstr(encrypted_cfg)) + return encrypted_cfg + +def config_get_status_response(security_ctx, response_data): + # Interpret protobuf response packet from GetStatus command + decrypted_message = security_ctx.decrypt_data(tobytes(response_data)) + cmd_resp1 = proto.wifi_config_pb2.WiFiConfigPayload() + cmd_resp1.ParseFromString(decrypted_message) + print_verbose(security_ctx, "Response type " + str(cmd_resp1.msg)) + print_verbose(security_ctx, "Response status " + str(cmd_resp1.resp_get_status.status)) + if cmd_resp1.resp_get_status.sta_state == 0: + print("++++ WiFi state: " + "connected ++++") + elif cmd_resp1.resp_get_status.sta_state == 1: + print("++++ WiFi state: " + "connecting... ++++") + elif cmd_resp1.resp_get_status.sta_state == 2: + print("++++ WiFi state: " + "disconnected ++++") + elif cmd_resp1.resp_get_status.sta_state == 3: + print("++++ WiFi state: " + "connection failed ++++") + if cmd_resp1.resp_get_status.fail_reason == 0: + print("++++ Failure reason: " + "Incorrect Password ++++") + elif cmd_resp1.resp_get_status.fail_reason == 1: + print("++++ Failure reason: " + "Incorrect SSID ++++") + return cmd_resp1.resp_get_status.sta_state + +def config_set_config_request(security_ctx, ssid, passphrase): + # Form protobuf request packet for SetConfig command + cmd = proto.wifi_config_pb2.WiFiConfigPayload() + cmd.msg = proto.wifi_config_pb2.TypeCmdSetConfig + cmd.cmd_set_config.ssid = tobytes(ssid) + cmd.cmd_set_config.passphrase = tobytes(passphrase) + enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1') + print_verbose(security_ctx, "Client -> Device (SetConfig cmd) " + utils.str_to_hexstr(enc_cmd)) + return enc_cmd + +def config_set_config_response(security_ctx, response_data): + # Interpret protobuf response packet from SetConfig command + decrypt = security_ctx.decrypt_data(tobytes(response_data)) + cmd_resp4 = proto.wifi_config_pb2.WiFiConfigPayload() + cmd_resp4.ParseFromString(decrypt) + print_verbose(security_ctx, "SetConfig status " + str(cmd_resp4.resp_set_config.status)) + return cmd_resp4.resp_set_config.status + +def config_apply_config_request(security_ctx): + # Form protobuf request packet for ApplyConfig command + cmd = proto.wifi_config_pb2.WiFiConfigPayload() + cmd.msg = proto.wifi_config_pb2.TypeCmdApplyConfig + enc_cmd = security_ctx.encrypt_data(cmd.SerializeToString()).decode('latin-1') + print_verbose(security_ctx, "Client -> Device (ApplyConfig cmd) " + utils.str_to_hexstr(enc_cmd)) + return enc_cmd + +def config_apply_config_response(security_ctx, response_data): + # Interpret protobuf response packet from ApplyConfig command + decrypt = security_ctx.decrypt_data(tobytes(response_data)) + cmd_resp5 = proto.wifi_config_pb2.WiFiConfigPayload() + cmd_resp5.ParseFromString(decrypt) + print_verbose(security_ctx, "ApplyConfig status " + str(cmd_resp5.resp_apply_config.status)) + return cmd_resp5.resp_apply_config.status diff --git a/tools/esp_prov/requirements.txt b/tools/esp_prov/requirements.txt new file mode 100644 index 000000000..f21e32fb5 --- /dev/null +++ b/tools/esp_prov/requirements.txt @@ -0,0 +1,3 @@ +future +protobuf +cryptography diff --git a/tools/esp_prov/requirements_linux_extra.txt b/tools/esp_prov/requirements_linux_extra.txt new file mode 100644 index 000000000..555438cc5 --- /dev/null +++ b/tools/esp_prov/requirements_linux_extra.txt @@ -0,0 +1 @@ +dbus-python diff --git a/tools/esp_prov/security/__init__.py b/tools/esp_prov/security/__init__.py new file mode 100644 index 000000000..c0bb9ffd3 --- /dev/null +++ b/tools/esp_prov/security/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +from .security0 import * +from .security1 import * diff --git a/tools/esp_prov/security/security.py b/tools/esp_prov/security/security.py new file mode 100644 index 000000000..89f011adc --- /dev/null +++ b/tools/esp_prov/security/security.py @@ -0,0 +1,21 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +# Base class for protocomm security + +class Security: + def __init__(self, security_session): + self.security_session = security_session + diff --git a/tools/esp_prov/security/security0.py b/tools/esp_prov/security/security0.py new file mode 100644 index 000000000..8e105f6ab --- /dev/null +++ b/tools/esp_prov/security/security0.py @@ -0,0 +1,65 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +# APIs for interpreting and creating protobuf packets for +# protocomm endpoint with security type protocomm_security0 + +from __future__ import print_function +from future.utils import tobytes + +import utils +import proto +from .security import * + +class Security0(Security): + def __init__(self, verbose): + # Initialize state of the security1 FSM + self.session_state = 0 + self.verbose = verbose + Security.__init__(self, self.security0_session) + + def security0_session(self, response_data): + # protocomm security0 FSM which interprets/forms + # protobuf packets according to present state of session + if (self.session_state == 0): + self.session_state = 1 + return self.setup0_request() + if (self.session_state == 1): + self.setup0_response(response_data) + return None + + def setup0_request(self): + # Form protocomm security0 request packet + setup_req = proto.session_pb2.SessionData() + setup_req.sec_ver = 0 + session_cmd = proto.sec0_pb2.S0SessionCmd() + setup_req.sec0.sc.MergeFrom(session_cmd) + return setup_req.SerializeToString().decode('latin-1') + + def setup0_response(self, response_data): + # Interpret protocomm security0 response packet + setup_resp = proto.session_pb2.SessionData() + setup_resp.ParseFromString(tobytes(response_data)) + # Check if security scheme matches + if setup_resp.sec_ver != proto.session_pb2.SecScheme0: + print("Incorrect sec scheme") + + def encrypt_data(self, data): + # Passive. No encryption when security0 used + return data + + def decrypt_data(self, data): + # Passive. No encryption when security0 used + return data diff --git a/tools/esp_prov/security/security1.py b/tools/esp_prov/security/security1.py new file mode 100644 index 000000000..7e7b96011 --- /dev/null +++ b/tools/esp_prov/security/security1.py @@ -0,0 +1,164 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +# APIs for interpreting and creating protobuf packets for +# protocomm endpoint with security type protocomm_security1 + +from __future__ import print_function +from future.utils import tobytes + +import utils +import proto +from .security import * + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + +import session_pb2 + +# Enum for state of protocomm_security1 FSM +class security_state: + REQUEST1 = 0 + RESPONSE1_REQUEST2 = 1 + RESPONSE2 = 2 + FINISHED = 3 + +def xor(a, b): + # XOR two inputs of type `bytes` + ret = bytearray() + # Decode the input bytes to strings + a = a.decode('latin-1') + b = b.decode('latin-1') + for i in range(max(len(a), len(b))): + # Convert the characters to corresponding 8-bit ASCII codes + # then XOR them and store in bytearray + ret.append(([0, ord(a[i])][i < len(a)]) ^ ([0, ord(b[i])][i < len(b)])) + # Convert bytearray to bytes + return bytes(ret) + +class Security1(Security): + def __init__(self, pop, verbose): + # Initialize state of the security1 FSM + self.session_state = security_state.REQUEST1 + self.pop = tobytes(pop) + self.verbose = verbose + Security.__init__(self, self.security1_session) + + def security1_session(self, response_data): + # protocomm security1 FSM which interprets/forms + # protobuf packets according to present state of session + if (self.session_state == security_state.REQUEST1): + self.session_state = security_state.RESPONSE1_REQUEST2 + return self.setup0_request() + if (self.session_state == security_state.RESPONSE1_REQUEST2): + self.session_state = security_state.RESPONSE2 + self.setup0_response(response_data) + return self.setup1_request() + if (self.session_state == security_state.RESPONSE2): + self.session_state = security_state.FINISHED + self.setup1_response(response_data) + return None + else: + print("Unexpected state") + return None + + def __generate_key(self): + # Generate private and public key pair for client + self.client_private_key = X25519PrivateKey.generate() + self.client_public_key = self.client_private_key.public_key().public_bytes() + + def _print_verbose(self, data): + if (self.verbose): + print("++++ " + data + " ++++") + + def setup0_request(self): + # Form SessionCmd0 request packet using client public key + setup_req = session_pb2.SessionData() + setup_req.sec_ver = session_pb2.SecScheme1 + self.__generate_key() + setup_req.sec1.sc0.client_pubkey = self.client_public_key + self._print_verbose("Client Public Key:\t" + utils.str_to_hexstr(self.client_public_key.decode('latin-1'))) + return setup_req.SerializeToString().decode('latin-1') + + def setup0_response(self, response_data): + # Interpret SessionResp0 response packet + setup_resp = proto.session_pb2.SessionData() + setup_resp.ParseFromString(tobytes(response_data)) + self._print_verbose("Security version:\t" + str(setup_resp.sec_ver)) + if setup_resp.sec_ver != session_pb2.SecScheme1: + print("Incorrect sec scheme") + exit(1) + self.device_public_key = setup_resp.sec1.sr0.device_pubkey + # Device random is the initialization vector + device_random = setup_resp.sec1.sr0.device_random + self._print_verbose("Device Public Key:\t" + utils.str_to_hexstr(self.device_public_key.decode('latin-1'))) + self._print_verbose("Device Random:\t" + utils.str_to_hexstr(device_random.decode('latin-1'))) + + # Calculate Curve25519 shared key using Client private key and Device public key + sharedK = self.client_private_key.exchange(X25519PublicKey.from_public_bytes(self.device_public_key)) + self._print_verbose("Shared Key:\t" + utils.str_to_hexstr(sharedK.decode('latin-1'))) + + # If PoP is provided, XOR SHA256 of PoP with the previously + # calculated Shared Key to form the actual Shared Key + if len(self.pop) > 0: + # Calculate SHA256 of PoP + h = hashes.Hash(hashes.SHA256(), backend=default_backend()) + h.update(self.pop) + digest = h.finalize() + # XOR with and update Shared Key + sharedK = xor(sharedK, digest) + self._print_verbose("New Shared Key XORed with PoP:\t" + utils.str_to_hexstr(sharedK.decode('latin-1'))) + # Initialize the encryption engine with Shared Key and initialization vector + cipher = Cipher(algorithms.AES(sharedK), modes.CTR(device_random), backend=default_backend()) + self.cipher = cipher.encryptor() + + def setup1_request(self): + # Form SessionCmd1 request packet using encrypted device public key + setup_req = proto.session_pb2.SessionData() + setup_req.sec_ver = session_pb2.SecScheme1 + setup_req.sec1.msg = proto.sec1_pb2.Session_Command1 + # Encrypt device public key and attach to the request packet + client_verify = self.cipher.update(self.device_public_key) + self._print_verbose("Client Verify:\t" + utils.str_to_hexstr(client_verify.decode('latin-1'))) + setup_req.sec1.sc1.client_verify_data = client_verify + return setup_req.SerializeToString().decode('latin-1') + + def setup1_response(self, response_data): + # Interpret SessionResp1 response packet + setup_resp = proto.session_pb2.SessionData() + setup_resp.ParseFromString(tobytes(response_data)) + # Ensure security scheme matches + if setup_resp.sec_ver == session_pb2.SecScheme1: + # Read encrypyed device verify string + device_verify = setup_resp.sec1.sr1.device_verify_data + self._print_verbose("Device verify:\t" + utils.str_to_hexstr(device_verify.decode('latin-1'))) + # Decrypt the device verify string + enc_client_pubkey = self.cipher.update(setup_resp.sec1.sr1.device_verify_data) + self._print_verbose("Enc client pubkey:\t " + utils.str_to_hexstr(enc_client_pubkey.decode('latin-1'))) + # Match decryped string with client public key + if enc_client_pubkey != self.client_public_key: + print("Mismatch in device verify") + return -2 + else: + print("Unsupported security protocol") + return -1 + + def encrypt_data(self, data): + return self.cipher.update(data) + + def decrypt_data(self, data): + return self.cipher.update(data) diff --git a/tools/esp_prov/transport/__init__.py b/tools/esp_prov/transport/__init__.py new file mode 100644 index 000000000..0b519d8f8 --- /dev/null +++ b/tools/esp_prov/transport/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +from .transport_console import * +from .transport_softap import * +from .transport_ble import * diff --git a/tools/esp_prov/transport/ble_cli.py b/tools/esp_prov/transport/ble_cli.py new file mode 100644 index 000000000..d8b2dff88 --- /dev/null +++ b/tools/esp_prov/transport/ble_cli.py @@ -0,0 +1,202 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +from __future__ import print_function +from builtins import input + +import platform + +import utils + +fallback = True + +# Check if platform is Linux and required packages are installed +# else fallback to console mode +if platform.system() == 'Linux': + try: + import dbus + import dbus.mainloop.glib + import time + fallback = False + except: + pass + +#-------------------------------------------------------------------- + +# BLE client (Linux Only) using Bluez and DBus +class BLE_Bluez_Client: + def connect(self, devname, iface, srv_uuid): + self.devname = devname + self.srv_uuid = srv_uuid + self.device = None + self.adapter = None + self.adapter_props = None + self.services = None + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + bus = dbus.SystemBus() + manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager") + objects = manager.GetManagedObjects() + + for path, interfaces in objects.items(): + adapter = interfaces.get("org.bluez.Adapter1") + if adapter != None: + if path.endswith(iface): + self.adapter = dbus.Interface(bus.get_object("org.bluez", path), "org.bluez.Adapter1") + self.adapter_props = dbus.Interface(bus.get_object("org.bluez", path), "org.freedesktop.DBus.Properties") + break + + if self.adapter == None: + raise RuntimeError("Bluetooth adapter not found") + + self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(1)) + self.adapter.StartDiscovery() + + retry = 10 + while (retry > 0): + try: + if self.device == None: + print("Connecting...") + # Wait for device to be discovered + time.sleep(5) + self._connect_() + print("Connected") + print("Getting Services...") + # Wait for services to be discovered + time.sleep(5) + self._get_services_() + return True + except Exception as e: + print(e) + retry -= 1 + print("Retries left", retry) + continue + self.adapter.StopDiscovery() + return False + + def _connect_(self): + bus = dbus.SystemBus() + manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager") + objects = manager.GetManagedObjects() + dev_path = None + for path, interfaces in objects.items(): + if "org.bluez.Device1" not in interfaces.keys(): + continue + if interfaces["org.bluez.Device1"].get("Name") == self.devname: + dev_path = path + break + + if dev_path == None: + raise RuntimeError("BLE device not found") + + try: + self.device = bus.get_object("org.bluez", dev_path) + self.device.Connect(dbus_interface='org.bluez.Device1') + except Exception as e: + print(e) + self.device = None + raise RuntimeError("BLE device could not connect") + + def _get_services_(self): + bus = dbus.SystemBus() + manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager") + objects = manager.GetManagedObjects() + srv_path = None + for path, interfaces in objects.items(): + if "org.bluez.GattService1" not in interfaces.keys(): + continue + if path.startswith(self.device.object_path): + service = bus.get_object("org.bluez", path) + uuid = service.Get('org.bluez.GattService1', 'UUID', + dbus_interface='org.freedesktop.DBus.Properties') + if uuid == self.srv_uuid: + srv_path = path + break + + if srv_path == None: + raise RuntimeError("Provisioning service not found") + + self.characteristics = dict() + for path, interfaces in objects.items(): + if "org.bluez.GattCharacteristic1" not in interfaces.keys(): + continue + if path.startswith(srv_path): + chrc = bus.get_object("org.bluez", path) + uuid = chrc.Get('org.bluez.GattCharacteristic1', 'UUID', + dbus_interface='org.freedesktop.DBus.Properties') + self.characteristics[uuid] = chrc + + def has_characteristic(self, uuid): + if uuid in self.characteristics.keys(): + return True + return False + + def disconnect(self): + if self.device: + self.device.Disconnect(dbus_interface='org.bluez.Device1') + if self.adapter: + self.adapter.RemoveDevice(self.device) + if self.adapter_props: + self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(0)) + + def send_data(self, characteristic_uuid, data): + try: + path = self.characteristics[characteristic_uuid] + except KeyError: + raise RuntimeError("Invalid characteristic : " + characteristic_uuid) + path.WriteValue([ord(c) for c in data], {}, dbus_interface='org.bluez.GattCharacteristic1') + return ''.join(chr(b) for b in path.ReadValue({}, dbus_interface='org.bluez.GattCharacteristic1')) + +#-------------------------------------------------------------------- + +# Console based BLE client for Cross Platform support +class BLE_Console_Client: + def connect(self, devname, iface, srv_uuid): + print("BLE client is running in console mode") + print("\tThis could be due to your platform not being supported or dependencies not being met") + print("\tPlease ensure all pre-requisites are met to run the full fledged client") + print("BLECLI >> Please connect to BLE device `" + devname + "` manually using your tool of choice") + resp = input("BLECLI >> Was the device connected successfully? [y/n] ") + if resp != 'Y' and resp != 'y': + return False + print("BLECLI >> List available attributes of the connected device") + resp = input("BLECLI >> Is the service UUID '" + srv_uuid + "' listed among available attributes? [y/n] ") + if resp != 'Y' and resp != 'y': + return False + return True + + def has_characteristic(self, uuid): + resp = input("BLECLI >> Is the characteristic UUID '" + uuid + "' listed among available attributes? [y/n] ") + if resp != 'Y' and resp != 'y': + return False + return True + + def disconnect(self): + pass + + def send_data(self, characteristic_uuid, data): + print("BLECLI >> Write following data to characteristic with UUID '" + characteristic_uuid + "' :") + print("\t>> " + utils.str_to_hexstr(data)) + print("BLECLI >> Enter data read from characteristic (in hex) :") + resp = input("\t<< ") + return utils.hexstr_to_str(resp) + +#-------------------------------------------------------------------- + +# Function to get client instance depending upon platform +def get_client(): + if fallback: + return BLE_Console_Client() + return BLE_Bluez_Client() diff --git a/tools/esp_prov/transport/transport.py b/tools/esp_prov/transport/transport.py new file mode 100644 index 000000000..d6851375a --- /dev/null +++ b/tools/esp_prov/transport/transport.py @@ -0,0 +1,28 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +# Base class for protocomm transport + +import abc + +class Transport(): + + @abc.abstractmethod + def send_session_data(self, data): + pass + + @abc.abstractmethod + def send_config_data(self, data): + pass diff --git a/tools/esp_prov/transport/transport_ble.py b/tools/esp_prov/transport/transport_ble.py new file mode 100644 index 000000000..20cb144f1 --- /dev/null +++ b/tools/esp_prov/transport/transport_ble.py @@ -0,0 +1,57 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +from __future__ import print_function + +from .transport import * + +from . import ble_cli + +class Transport_BLE(Transport): + def __init__(self, devname, service_uuid, nu_lookup): + # Expect service UUID like '0000ffff-0000-1000-8000-00805f9b34fb' + for name in nu_lookup.keys(): + # Calculate characteristic UUID for each endpoint + nu_lookup[name] = service_uuid[:4] + '{:02x}'.format( + int(nu_lookup[name], 16) & int(service_uuid[4:8], 16)) + service_uuid[8:] + self.name_uuid_lookup = nu_lookup + + # Get BLE client module + self.cli = ble_cli.get_client() + + # Use client to connect to BLE device and bind to service + if not self.cli.connect(devname = devname, iface = 'hci0', srv_uuid = service_uuid): + raise RuntimeError("Failed to initialize transport") + + # Check if expected characteristics are provided by the service + for name in self.name_uuid_lookup.keys(): + if not self.cli.has_characteristic(self.name_uuid_lookup[name]): + raise RuntimeError("'" + name + "' endpoint not found") + + def __del__(self): + # Make sure device is disconnected before application gets closed + try: + self.disconnect() + except: + pass + + def disconnect(self): + self.cli.disconnect() + + def send_data(self, ep_name, data): + # Write (and read) data to characteristic corresponding to the endpoint + if ep_name not in self.name_uuid_lookup.keys(): + raise RuntimeError("Invalid endpoint : " + ep_name) + return self.cli.send_data(self.name_uuid_lookup[ep_name], data) diff --git a/tools/esp_prov/transport/transport_console.py b/tools/esp_prov/transport/transport_console.py new file mode 100644 index 000000000..823083069 --- /dev/null +++ b/tools/esp_prov/transport/transport_console.py @@ -0,0 +1,32 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +from __future__ import print_function +from builtins import input + +import utils + +from .transport import * + +class Transport_Console(Transport): + + def send_data(self, path, data, session_id = 0): + print("Client->Device msg :", path, session_id, utils.str_to_hexstr(data)) + try: + resp = input("Enter device->client msg : ") + except Exception as err: + print("error:", err) + return None + return utils.hexstr_to_str(resp) diff --git a/tools/esp_prov/transport/transport_softap.py b/tools/esp_prov/transport/transport_softap.py new file mode 100644 index 000000000..83f1818cb --- /dev/null +++ b/tools/esp_prov/transport/transport_softap.py @@ -0,0 +1,39 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +from __future__ import print_function +from future.utils import tobytes + +import http.client + +from .transport import * + +class Transport_Softap(Transport): + def __init__(self, url): + self.conn = http.client.HTTPConnection(url, timeout=30) + self.headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"} + + def _send_post_request(self, path, data): + try: + self.conn.request("POST", path, tobytes(data), self.headers) + response = self.conn.getresponse() + if response.status == 200: + return response.read().decode('latin-1') + except Exception as err: + raise RuntimeError("Connection Failure : " + str(err)) + raise RuntimeError("Server responded with error code " + str(response.status)) + + def send_data(self, ep_name, data): + return self._send_post_request('/'+ ep_name, data) diff --git a/tools/esp_prov/utils/__init__.py b/tools/esp_prov/utils/__init__.py new file mode 100644 index 000000000..e46d42d08 --- /dev/null +++ b/tools/esp_prov/utils/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +from .convenience import * diff --git a/tools/esp_prov/utils/convenience.py b/tools/esp_prov/utils/convenience.py new file mode 100644 index 000000000..b894ad14c --- /dev/null +++ b/tools/esp_prov/utils/convenience.py @@ -0,0 +1,29 @@ +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +# Convenience functions for commonly used data type conversions + +def str_to_hexstr(string): + # Form hexstr by appending ASCII codes (in hex) corresponding to + # each character in the input string + return ''.join('{:02x}'.format(ord(c)) for c in string) + +def hexstr_to_str(hexstr): + # Prepend 0 (if needed) to make the hexstr length an even number + if len(hexstr)%2 == 1: + hexstr = '0' + hexstr + # Interpret consecutive pairs of hex characters as 8 bit ASCII codes + # and append characters corresponding to each code to form the string + return ''.join(chr(int(hexstr[2*i:2*i+2], 16)) for i in range(len(hexstr)//2)) From 2d199dc521ec75b4ac996049e2b1acda6685bfaf Mon Sep 17 00:00:00 2001 From: Anurag Kar Date: Sun, 23 Sep 2018 20:14:46 +0530 Subject: [PATCH 7/7] Docs : Added Provisioning and Protocomm related docs Co-Authored-By: Amey Inamdar Co-Authored-By: Anurag Kar --- docs/Doxyfile | 13 ++ docs/_static/unified_provisioning.png | Bin 0 -> 22567 bytes docs/_static/unified_provisioning.xml | 1 + docs/conf_common.py | 3 + docs/en/api-reference/index.rst | 1 + docs/en/api-reference/provisioning/index.rst | 11 ++ .../api-reference/provisioning/protocomm.rst | 169 ++++++++++++++++++ .../provisioning/provisioning.rst | 154 ++++++++++++++++ .../provisioning/wifi_provisioning.rst | 60 +++++++ .../api-reference/provisioning/index.rst | 1 + .../api-reference/provisioning/protocomm.rst | 1 + .../provisioning/provisioning.rst | 1 + .../provisioning/wifi_provisioning.rst | 1 + 13 files changed, 416 insertions(+) create mode 100644 docs/_static/unified_provisioning.png create mode 100644 docs/_static/unified_provisioning.xml create mode 100644 docs/en/api-reference/provisioning/index.rst create mode 100644 docs/en/api-reference/provisioning/protocomm.rst create mode 100644 docs/en/api-reference/provisioning/provisioning.rst create mode 100644 docs/en/api-reference/provisioning/wifi_provisioning.rst create mode 100644 docs/zh_CN/api-reference/provisioning/index.rst create mode 100644 docs/zh_CN/api-reference/provisioning/protocomm.rst create mode 100644 docs/zh_CN/api-reference/provisioning/provisioning.rst create mode 100644 docs/zh_CN/api-reference/provisioning/wifi_provisioning.rst diff --git a/docs/Doxyfile b/docs/Doxyfile index fd241f019..4765c675c 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -99,6 +99,19 @@ INPUT = \ ../../components/esp_http_client/include/esp_http_client.h \ ../../components/http_server/include/http_server.h \ ## + ## Provisioning - API Reference + ## + ## Protocol Communication + ../../components/protocomm/include/common/protocomm.h \ + ../../components/protocomm/include/security/protocomm_security.h \ + ../../components/protocomm/include/security/protocomm_security0.h \ + ../../components/protocomm/include/security/protocomm_security1.h \ + ../../components/protocomm/include/transports/protocomm_ble.h \ + ../../components/protocomm/include/transports/protocomm_console.h \ + ../../components/protocomm/include/transports/protocomm_httpd.h \ + ## WiFi Provisioning + ../../components/wifi_provisioning/include/wifi_provisioning/wifi_config.h \ + ## ## Storage - API Reference ## ## SPI Flash and Partition APIs diff --git a/docs/_static/unified_provisioning.png b/docs/_static/unified_provisioning.png new file mode 100644 index 0000000000000000000000000000000000000000..247ce0c1f39213f5ad91ea9fc42e8185c3c5fe12 GIT binary patch literal 22567 zcmafbbyQVP^sgdHDcv0s(wC6#?(ULHhe(KYcT0x|(k0TJ(#RF0mF{loe1rP^y|>b;XaMvU5U6!m=FA1S+MXFtL{4woBN{VYAK`bjv88#`plCs*1J zN?q^ako)EhN^^{#9wW}+D$o~Q8D1P_O8)1&K;b}IawzVi0Ug($uh8Kr&DoxSHt2Ao zrHPPfi(7yWricO=HI%ad)1KH?6>#Gt=##_}*+C!R*(N41q&OL98}WSLZF zBv!QO&V=m17D$y!iSX;~ARt@ReY1(fMNKVFcKiSF~s8`4QH%N3s zIMA1>AC|L}l7lp?SfeC3L%<{3dc4@(R*vMq&ax=W%?5B`DG;%bLqF7rF{=_@s1>Wx zI$Eos=@wDlPbWHD7InK?G=HVf{+xgT4QOIHoV~c*8`Jnyj{3iDj42jE*FsZ*f~xfy z{@#?2fevARR*H}NcQ_W{(h1kfO6spi%2Sa;%?WiOjDH&sQj$a07)M5;@4ZeKF-20i z3&v3m)&AQ!T>@O{~S^1qF_Bfw>qv>e}G$9zQyyma(Oxbfei+2evs zi`b-he?7XL12|xQ7Eb5i#tV$#a*&c~SKJbAAc;wAeek2h&QFLb_*?#LBu5Gjn^9ZY zi`{PGvyQ{o*o)_~34O*N>fX!;x3vkenZrUKqu?$Ke&oYvGY##0w*p6jPJjqj#S@lq z0x7cOnJ2lybl{Rn9Ljvz4<%x9+a5rox{v`5^ztdJ6lq*Gag{<-g80xr}Rdvv(}FwOylvDy1Z7wPBe?z9HHB*oFu zk!rR$#y}ziUaj41@#*lnSv3xWrufa}i3SPs=+3XIMD9bqN${NFzySZwzDTI#hseql z$It3Tag{@rru~@ItkrkNn`$+^YsNvX)V z&vI!TR_e&HMUvs|!#Ps1Q`ufh1cmV9 zhe1Y0z8!ClCZ<@yP1L@XaMA)ya! zgczw{PZmGTIG$C9i>!8Cdat4d{O<7f#>2TU+qg5>S}B533NqT|_i-@;kBXIxi!0G? zOcBqp!BJw@0a7yw_AlYsU$-|`8dLT&^$v7Kt<8-EBz*74-|hUA%}rTaA;o{}I98}~ zaD1#z+23a6UxY+7`aaX^l;DeDOL(qK{F88)_sxmc_?OVsqE5-k5G)Wr?BQ6xHwyZB zob6F)6sy;}9zs7YpY!{E9Sy(^p_B+OyeVJo3@Ld_k;5`!v2wbaY_xQ+*eRVU*dlw^ z9fpO_c6}5fORWkIf`BE4*lmWD@I-;I*$LY@3rS6DWHZxiK`Dg^CqqXl5GVwG; zmQiq$#!EwyGb!dP#&IKgattaf-uP$3_|AvuNTVqeOAN88+c6@WS>9lqxzv&QPABQR z$%EX{A5V+HZZ1DC%U-HoWv-CU?I5}vd#L_Nla3KPfzxK9@DaJLOcEmjzTaUBTR!Cr zeQI!Nab0G)kqUH2bUob^Ekr4nQK-VUIZ~NaYMB}#nF^L zzNjYzjf`H8pSbo#sA&z!xP?fOlmL zK;Wzw>gLFDHZG3Y50$xOPOx8PqgWsZO%VKK{-r7Q0>Exx*0DBY|84csAP|VWQZ@J+ z7xZJm&cA&j?*9C56hXxVF$O(F9q&GdAU>P~F=owOFqRbQ;h$I#0QMqcc$)FQ!M6Pb zY}E}JO1V=1Lc#y<(d`_NBU#cxs89vg!u3V{@hvN`U#@9Q(y+>E>&Bp zvPF?$OXDTnR}gDAn?;NNJs;gZ{XrYA)fz1IXSG%Qmk1I}c5gIwkd}azie}K07x%A2 zzaQueXNX67jOQ(0?%`t49;K)2E_{c4nYsU^608vq+J<7%u&5-T8Ypl-H?Qt{Q@&bv z^2MM@Q|pnc%wOvS`clDLPkt$F_4_zK_N9)S(VU~%a+s3Ze0#ENayohp>#vn4UL^~E z||15q)M`mK_wq~dwb*ay%Dy1Vr{jQr0|v+(EN@B*^3B_f~)3tt=0 z`utyl3~h8)uZRvlo7K;_ZMJ&mCe(a0hZU*e-Bd zZ{)M(p5V|_2gYt-E|4NFpaYKyu@BPL>4I@5L1n==m2RC<8gPWJ(O*0|O=6~V+bl&2LqmB%!bmNa@c+LMat(*?Je(M%sK%2SPU-Ce`Z#!(H z0TcNv-F^DQ9TJg3(5uRb^pKVJL;WpT^Y6QT8@L)D$U~lH9ryI6lP3&l{FOh#Nf-z( z#}ybaj}Xb4_KR2bvc z+n4U+OtT!p9I~`3sdl=tCE38Zx7ZN&=sayqI9#}wIqXE9&*>wk0wh7lcmItE#qX1c z#SB-(>0OcJ?m_ELV!tow*zRfQ-!c0M!+*}Hx{D5Ykd`8&PV)%dTMr?JniI=aJcozF zD0i+@DIr%$eToZKEV~`?e|IS%!q>TI3YO7+>+>*E@$LejbAd(s#|7GH;qgCuwQVI) z*YqJ{CiV*tgQ+Iq!u{VKGJ%E5APpWS(FHq)yb4X(J!y^7L-RTq>eLF|#YN%T6Xa?w zfT|#hE5jMSU|U+2BG={cMG(mls7La=M$d2RfBy|@|N1)iWh%l5wTo2hwN;0y5;H0p z9A+v(uYf%C-Q|`EY}WP3tAEA7wZfl_aF}C zSO~fvk0A^hwFaMcdu%8(a5-AY5+j95eAEMPJUE$!TgB<|W#M$0tMF1nQ9R~$bJ7rh zQUX|qQt;v38Ck`}!J{#{G~x;<{#|pk&)vgm)O4|6?7c!gdZ#V~T#3DV50MIyg=t

YPa%~Q7*)#tRSv10(23u@ z0_Tk~GBQkr>yN-NpHibA+&=Qw@j_Q~Pk@Yl!Dk1Dv{VOqu&HI}Hov=jCX1VZ9qho2 zE+^XV3g<)G7XJCga*q1#SPv0iMDYFD;Ax(TRZsWyAeBf*U|;Hs1_uGqRu~aqabait z>cH&;nW@B-Yo->TBl^B$Mv`z?52Ijsr9@@_p~v!h1U1DgWWNw4uk!t$W>2j(eqvui z$MC^w?y=9s3F)#OaeOnzoIF`SalcQ%j(cY)-EEjF_za%ZoCR9RVCkvjx>eHI6qwoz|^XHMtC2|WqiL{TX)MiDzN zGpImE+nW_hHVk+@WkN=u*cTn;<4TofK^Zdrbo(TlgATfeSb9*z02Y`F#@}&y&fiYA zetgg2CVNcCDmr_2{YwbCQ|^84Jz~pYH*9!gH}>9e>#hYMx@$GULgkaa!%Y1CDFmR=SUkv?!(au9N1^_GaMRj&MXzc%v%beq}TrN=2CWnI@V(&D6kn6%(y+ zIP~4d^5HC`BP~n5g*}fx1;-a!Q%QEYP=+xNR93@ zi1iX9$i*fdZ%Xin63>4dUb?)A=#q&u?w>NirI@|GlJykI#S>&PB$NnGRu!Gk)sPZj z?%m&}qcrCw4|2i*vq*fuUk=b&g<;^TMKz%s__wu@5ssgWD)cdJYqdA-N+qNu(!Vzh zo0#S*6Is-$TQ0=JlQjRG^hUxzFys9EFd!|L-K_u1h^KDLCo9QXPRHw~*mSCJ`;Xuf z=xVK%G2+dcRYb)y~6#IVp{YS1D zf+fcM4~W{~##aIUW*@wh5492%`Y*~YzbighNP=tO1%ya+tNi< zfp&!{YW{bFuy;`3_l(^8#Z@oV5D$uXl)~NSL}?kE39= zyQz>Z0=^S1YEkT_k0H$TB6?Mxe&O$FZkn|@#Z(pqXR|r^>{plE#beD0%mWMxvyh+HxS*4|8OkiHx8r*{Es+7+(R zmpsOzO6TXzqC>ZxaTtTU{9R*hZE4=dCy0SwEAZtl=OgHRw>&=wZi9=_anJjF|c`qst#T>fwiF9j9 zc6>YAW}l)joihZp3g^SAN!O>nuTvIAmg@t|USlWNRc0vWOUcwkkHS)W0?vv~GBI99 zxUWLI=IB@Y5|s?vEcZC5(}h^H8KAgf863dN(_j4YavQ6m!P4_$GbZP|X|xQfV0j&f zlMy#{hL2J=(Zk}0ajQ=;*!4Wj30-XQH)68v>7E(%H$V4N+{C6Zg_VgN-T7Wx5hjh=$;r+*k^rMHX2ala|`QN zj`)@2RWO_*W^zo*f&4fkLAg#U)}ZECD-1^YON|C6 zQ%;x_&~CV(9OLmN`otcS%NZ9oO17Mo8*YzO%&bq;g()OF^>`n3C%dFUg@AWL9WMfk3J2tgH zt+Ug{v4)u3(Is5no|Bog+>$7f3MeBOnGP`?2|(`yk@|hUr!o--5Gh@*!a`Kuw|hK& z9mdyve)aEG;qr&{N(23&?Da!hTfPDB(jfI5IhsS7X$~W>?xf=noNpCi?}!@_U0l!( z#{G2EdZFLy_tiP~a+tJFggld$=R;#XES>3bB{M~GlymH`PMQ?9lS*3ZCP+M9owwU* zH<*6OLn3|hLb))-Y$&n_wyk^rB`Os?Va;OJG%;q!|^M| zb2Iv-CXYh3_a}sPl6U((o1_+w55&2O?Mvu@C%E5-uW)KS>Vu^V_VZS+fG(MM1{LSr z1Lc7xmsS7?YUulY4)}$bwhA_CoB9xy{S| z#`{c<(s}9_^%ow*2~n$F9T&om+H{|VfNaA41xO#5S5ZHFyicX2{(Df~{c$pZPaG!y zE+jOy-?zIN9gzjJT$C{MX~In1v@k2n4^0&iJ#wKy7XjFLL+dg~U4O^^U%kq(r|qDs zbw#oaA7SBA${`Kh1g=i?btvt71tw}}gM*ur#W>3LO=t?wfLhiYDaxuJRVeOx#?YP^ zmZ^GoNbPI=C@mJFg)x~o%kBh&Nk?l8p9K3`tV@Fn$IEd5hCv36p=3v4H&~>zIHhK* zLVbp2a~GPD%A*@q!2~sbDy0h%hjC}UwNrWY7-dBwM>0ye)>dcPeS}{CgVA}isdXzaLN^|pQbrV}~Lg*Z3h3HjS;_l6IhY+xv<4>~V z)r{5HXywA*V=3sm)c)!$?COxm`?Uw>{BtRq4GY{E@QWB!^*qiZ9! zlhRBa7n_qRgknCb-Cu+6wL1lU1*;vtw{<_Mg&{_w_VywL)O;&{w2xv%6mC=Q{(&+|7Fos)AX*svbd>ey-NW~NFxaqVG#KD=eJ~GOybN_Yd-fE( z&5ZK(SX@g#UvWik@BK^s*CsqA$UjfqJj)k1Ty95Uj?$b$ERn;6?#ah^7T`6g^_$SO z^>qX?GP15vj7LaVG*8urG6g*HM^Yar%wjQDIB*g{j+@Eig1I1<6o zqlV8q()dU>)qFaRbJu$dwrqAF$zMsZk;kgH^i7taAWbhy#k#&))G+;MiL zKYqBy<4jp8oyDkCygMAn;rE)t_08FQp>h@+DhaQt6?-l!;S2Ns8#d3_9nL{O#$^nx zeLWp)Nn2>tmuBmfKR+*uMAKYWn>XN#Q*}=^Pj4=QLkQOiMNrAR@VgBb!u*@)wxb+9^vpNYj%N{l(Dv&tqjN5xV}QR-Bh z*4r=or=?$>?U&gG=L3jaLg5-P0AgEXMHPT3!e-LNwzIQ)_nm;+{NclgTh76c5Kw)W zW|~~yG`$W{&k_qk?Mr6K#JpJe7Dp}jqgaE%cB&f5>|26L0o*z)HDJH(&QM_x;DpaK z$N<0|%uoCGtIshu7T%yV3d4BM4y7skL1TK^P z@rDXEvp#p%t#%Bb$8vJSCxH4c!&2F0-v%(478hiYDxbQBrfgiSS7+H($on?9aS;jqFa74$@uq`nSBMP2~ZmU@E2)#+<$Ol&d9ch&u8&TMAia;|wg#A!|bcxxQxo=T{I237X>A9PU7q zPUH`KB!@VYsR{s=Th6z(Tnf>WD1HIFp;kPr*4S5xSFNqB-xLA)F}@-uvYhL-IYQm+ zej=N*=8Z+1RC>9 zpIaV$vjfA_Ba^h(eeWp%NF60xVBJ>78Tm5=(4usbkt6sT)i#r;^WImbm|37bLEFvg zhpD?LEM?HvBZ)vPo~wZ%c-l8}Xunh+Y{*Gwxo1E3NWXBgfh_xnQVL^G%Hi(0 zSb2P&YS|)jnQ_Wlm?CV`w`DYH)i*<~KQK1Uq}QPD&T~Cnl6_zAn2`a~vo%p#LW!@a80mj|xy|x+b3{U+Pj^MM;_775NJG49yt+k3 zb03NCOBc_k-F&M-&G)Mnk*DF95{z13^ywXQ@qcI3w`sL`dufDxuIK9R|2{pvjIDF` zUo8L;2(KDvU36ivdb&@cth{tuU(%PpA|{+EK76VO`-!@((w>qw>M1=8efyPz?h2+( ziE<^sm>U{fx5mzl&7Jz2H@q0v;V6UP!oj~T`KYRwr!eI(17BveHxiw*6&oIxSsK58 zw9UQeW6R+{pEi+@K8MQl2Cd#>`d)6ekH@2>dUcE^Ke?DEHURoJkj$dzz2+bDt|yWp z0Fc5eWjS*?qsz|EEE;!7()2=(iY#Tesio&+Z;yV^IFZs1-}d0} zKMwNmMv5J#^f5P{ijNJh@uH$+m-H18dY}4&KOen6YM0$#<~_OJR>(Ug?5YB#Ox@FN zZVKzO+%STmyAU$(cCfi?Mz1aX7vC3134^f0OtUK&QK1HUpncWreG@0^fQD;srjUfv7Bzt*~%>_K}R*_=8! z7EY64S~^~J%hgNY9ThZyGgC)&%$q1YsSYn`efGQq;d8SV1h|Rt|nz$n4 zu(s0kHiu>}9i70-K}kh!T~!~y=ztaeaWr&@&M%&s>Sg{NzJ^s$$KYGsvp71* z#R-?}RA#jUeUX!v{&`X`*Mkt14{Jp1RP1v^IReo*`KF~PXE?un!s)3L1(pZr&H-ap z5$@Z7Aa}S<85Kd14@;F@6{NV6d^oywIG^^);j`*ks-T5HI6-%&3eU}8VbX9$t^EQ> zS_!xg8^Wo9|L_~9rC8ApxLQYlSV-@y-|v!+z+#dJzj`FU>u?t|jIY)CxwD0BZ(4aQ z#eH$>4wPW1g>c}T{h%2&PS1Uztsgr|s@0i}&)wkU`0DZp24wkphX&uMhi$;{^HDF! z{nSHc_?%A>cq~tNqlb{*`zuzgKkw1tJG<-)9md;M!*9Jlk|P%M#NQk((3-)&aTNM- z$&qs+BNO#3B>JTqlqD8kDW)Lf;M%N5S*0S}Afq)nx5iC`M@sZANZSc03r zs}<9s?#DUeQ(RPRCO+qBkGjLpdc4)}>+!Q(*_~kmOpYV$dvx;pPvN<|8#2FniCzX1 zz%fsy5U#kc?!S<4e#88-+THXcl@w~tekzZXxifx<;7W=G$z{M$?+30o2@8%G)KK#o z)AjJA`v4yf0{ljRhuK7l>7Loa!}VLVpy2qYXl&*7I749p42F_=k?7ni2WROEFYI)Y zgHL!K+9wWlY;(I=1-(8%N66uMt?_wRMH&0t13B@Oi|M;GWVuNA%S!5+u}|vJvN5*e znl6$}S}Y|j0h?B68!wWl-zg^S%GI>XQj{Sw3E%Gs9RbbPGN?$WRkNKgrAS)QTX(Em z?IX5yc*48)zTR2dZ2rT@yNBYmuN~snL`6IbVy-XVbTI`qMbIi9sac4c2vzG#VrdkD;(4 zePjgM@lQd`8$_4g6STgT2()zeSxk}D1dH}rB1VowjIXDv>8&RWL%CF89C}kNDs=uf zQv3BNR@Wn3k-lzkuDM=2ULRWINA)K~;B33^P&Jgl5fSPRHpysR<_sh1J8?Lb7J1}gDRFt^>?e5YmaZv&`__*I z&`lIV;wihM&(Jv%H;KdVYO_Dmt=tl+MbEUE&_}71ou&m4K-XX%Z_V{UDNOEedM)s^ zWk#rZ0QG1T|I-c}K_RAqE5Zrt&sLHid&}0DSFG{|kE0YD<_lY7(bGu`ht76W+{c{Y z=L~&1j$B&rx?N7`iZ6bzYi%0sjuiN))-6YhZ zr6D1l-Ty-t;2f>1q-}~-yv_v7f|sPb4WLjX?pz)s@$=Fi zSTt1s_LOHh*V*Dj9buw&%BGD+`mc9?x~vfzG;Bf3)GGT?Df^Cn_IYh5;HMcQACxk? zNW)Iu=X+`p-l$Zzw`F=2Byznm%yEh!q|+uZ;|(W1j%SM8wma8Z`sTX&Es=ZA3%M%E zPO{mxwoQqNzMORw#hs@LO~tQ2aEBhu71_8a};}L;PSA0Uw%oex@6wn z00ew~OnineY6L;lmuVNAAtN8*et*BwD0jjHUcmLFNG%Mtl~Gnm$&bCD9?Ex1h^hjP zM?9b$M)1L?X`kM1?o4Dnjt7HX`rB~*Uf6xL*)jSnYlrDur@nOFhg%8Q6r-ImoGlPo z)`OHI2%79qFR|_^I=9MvUoBA=XH%h6tqmdBZ}roda-!r%SPO?&QbV2xJKZ+vt&U_Q zHmj1+PYq-5dZ>m;!a*M}*09uxC@GfzDj-kxF#T@*8LFXc*u-ia z0z>fHss3jazdnU~zVHsbWA8{F(=Oi?T$7@JRApd=^2-=n$D6jo$6hJ_FD|#z`M_u z8(LhY;%19`0-M6(G~}rm>#j;&FrIXSY#b(Dl(E#9_FhY6f81(m^)*bpctt>;#t}f~ z!zj(y*x#k%){7FiY$fg8KDA0v%x0Ud&do+g%Xjk-wJtHBUVb^rAU~ zj%q)#p?W{p3FE{4jfa_`sXY1ybH$|_^4V<3E+5{qVxj|M@hAe?FvKRXoREVzzK~J0 zUV)e3&q!RsNC<{FI_cb2m#pnkFt{oJVR0k zV*i!$lqdtwr+FwA5^O9kA%t=MbQPqXpO<`%wKnL5S7!9;-Dg-9RBqqKP-iCq?H5aFM)>?#e&vU= zO6idiZ5dUN1*7_{{@u1{WA_fg)bRPQ$aMi2^Ajv-LcIo~`xm71drdU?>RWwQN{yvJ zwp0wN(R`gYhXr9Y$y|(rha#osXYIy*&+*EajVC1G&5y%3T50GtJ+B7I7-~s+&qyMu z(%dm7yoZ_0m5eT-N62rgi*6dcN>s)Ej8+EGcD9@lRP}ZN7K;gMyOc;vr2uNY!|O>v zG{GpbRZUQCy;(+QkVQ|(^(Ll4NU1AE=&6{e3z~dt4YnDBX74|;SUUYV0q*BZGB)#X z=2fH>0-pA0Ic>X9pt~IPhHv=%j%;ie-=n3__Lr|HTE_N&udw)62n~wV?ld^q&ok>c z+CLF2Gh8Y_qGGk|5?|^2NG5ja8C0M|YP?seKzDqB*SOu_zr9u_6!|6%Pm~hPD@5dN z#IK@~n2PBu=HldUtH@_By#CY!a|b=3s*-3^?DfZ)R`+YlQqw_IIV(oPtuGBkC9$( z<1p$IM-?Y4Fa0)4p3}bGe!4SX^{AxoHYmSFwrJjE=k(b+@H3Jl7O#}(E#t&SS{ZB<>ozwQ|+s5=UsOA(k-p7XfZqy6}Q7{ zf_w$yFx`_6Sso!hKfM?Dm%5@*>~7v#8b0FOekn2IdZ-~@>rlr9?F#jq`>6K`IzCmX zQio)B=kzG5II+F<#^P`?(`Tc29ofU3mU%`J8nZ!X#9GR{zlmE_8e+E)HYA1pJzg={ zxac{2y28R#Vnhm`7xC%toL>mq9$(W1-Lzrl$_wRMQJl9sf{2^!=3#G%s|DEe2@VhE z_swJWV}+5Pp>#7!Wn5eS+OyGb@qEa%o!A@Q#@s$j<#*YH`%7iv@|{%}OVhE+X79ol zC#X}9FaTs>$>~OQrSW$*+Y2wVPxELu`6!iULqXz(G6#Un-x#qeFII%uOco_ieQ0vn zWlI}p>42n!Ov<7{ck=IoSMg5Kcxi92#!qDss0B?dW!G3Tna^w&A4w?g z@V-Sdl!*gXs3}L=qYXirRoF!%@Oot;qqX>)xdpPu05(zE4JZYr8&;zh9f2wVjKg?T zRdXNHO1v6VASR24ombyp%H3Cf_#og^wKUak6@$!v&J}Y&lw2Dwvai+7fC!-87_y7< z+Zcrnvfl2}FD>R3^C$k~3jcVafazy)4H znf_?pZHaD4RyfRde88v>KYUd-OHt`)q0b+bf^^3bFV(*2@gfHDxkrqf3B$vE$Q?2hGa;0pMV3<0mtBR&~n&2Y|L`?GxY z-M44;Zqg@nt!@D>`<~+{6BGNohi5ECf?6Jqhs#oPO3?5)5A(`HGC{P`s+YIjZHvoB zdwiOux;SFNPeqa#FVWuFFSWcCBKPEFDwQOJQ&EJOE(=}vd}067cz+(i(62!0b6NVX3y)O@lu;O_q(Ww|Q^Br$DIQ?ahS2Qg_LAK$R*)wX$v|vCYc1YhXUSUC;jMwphaAL4OA`eHJg%^jt&4~+_#0r&%0*;5> z`6q>lKm1nwK%THyZXEH0*w{aEizo4|_KR?iD}BbJ)}^{N<=!{$AB<3p?YTr>GXu#4 z!1{$-JulE1G(Rg(4?7u~qE`XzOXR8xWjEu(P6Lo5kGupkNJ*n7bjn{n1ZY@}px_pu zA*2D%f=MmAQb;hm6_Jt}tod{r6!DuMl)C~6klk!!5Vyn9tId%-PyH4(q!Cd7h#lz9a$`Ex+|Z)eyiolv7>2GY*Jogh4q4W~?MXnpS&il!gWpM( zJH2E+>i#cX_Vm?DG@k*qwFhV|4m;6=+B-WR0WF94J?jIg7WoW*g2%|%%FcjR3I;p` zNVmsJw9faM_J#GXZ~ieRvi+qVo`*g>U=_X z*X*ET9Yqla&YGRy5*buJYeP;h+%qyV@-f0UpljRPC5BIFw(r?D?%@z%BQ?*4dIC;s zY*21qaIHYL@-0zrTmbNC_be7rD`WwpNEY3w)w5`)g0$^ttbg7OOi5ueSy#~W9Np*c zhRgkIuN0T$*dwx?=JE=eTvfW_$D^*{x3t?e&3Z}d06D$N|$U<5cMKu@Ef5SqT?eX)wi8YCmK zEbelB?r?GkgfqoRu~;*6Egr*#DpY_ZL!g#R5)XLtZ0rPNU|JnLx3+W5Itt#vj#@Xe zA6}MPhhR}$b#vg1npc^B7X^kWPX7!=i;Q z_e9B>(n$*n3KlctaY{tsYM`V5`!Hm3T8)TbB+pw&WZDQh4U67YDh_1`Xgu-FELSN| zB%xM6ChCRT9xF24{XC%;0xZ zm4Lfn99sKT7DJj>oO5$?TMRt);RQ^$OcsOb#j^mCF}XV3#fL!Qk@1*6@2LGqF^7HR zOp?INAxn68G28DdR{!%;Alp&O;8$_RZ()ba zDXXt9G-#^Dq`HO812-T5tdhD=6|uQfQWtBdc!D4}ceQzJ?D?@!JRSq|*fsG>2y=e~+)N|mMQ%%`5`D_BR?(wPG> zg4ArIvxaPJkX0VDL1SUxHo$D@&AL0n-cPcgbRk6v*<8H>Vv-&up_^RFR=`ctHAQ}_ zk6T|vQ-qn5(S$yYC2;d_ySXw8FEeV>6kPNVvXaVw{lZ)Xg~|dMZ~HBfd;Q2P?T+wX zrwoY2@3#G(#_31tWdkueo+vXCs-2phgT z?UI*!=&&E~LTc}IBC~;RSiAsa8IR(~Jsednc*bY3OG;&B^Cj*rOn_C=6v)M@#oZ=i z3Di<-*$Y!-OwD~>y=PGiiYz1;w+_O;fU9emHIKrO$>e}d4+9ZE0 zc1gj=3}B3}5ugsE@GUv5$d`_@-D!mI#L~W<>x zkC|N_Zw@y&SvdR0T^V6BDt?w%bI1%Tk}&LKQ68fdl!WSL)(dsErs`0-n@U2Kz7mmc zqzZa9@-lWlB9g~)B#Vf^J~D%c^@VL3I$B)D?B^W{BYJgXLzJl4y5;AO-lJ`+9&d16 z#=VdUCqN5O{z(@pG8$O#Gp(_Z=;)X0PR?Hn4BEJ(H%C&-hgEg0s=0xexc!`1@U4W zNtCOzeXZd~TDS7FN1qT;l*EpNBmrY0Nid_f2>%_M=5gYZrFk(VJ0cbLMpe9?HZKuG zjH1y9{$ihn_J`eC4A;>tq!X`>#pl^M`$T({K3=`P~ZL9L6P9(4-eIGRgP(V4WJv}{RY`}*$+h$xRO%oh+_2o zuI(-Cr0@&yeI1FsKD{^G;kaiC+*y`E`(h44K`df6SNvaVM6N(-$jd}UQ*`!c=x)Cc zo_cNU?U9Cl05Y5-6t$?xgi;iVkdFKd!z-d*cnR1XcX_SZF1w);+3yD!IaLRsuyj#G zV5{~8D7^E0a}$P+u~jX*n>v^82bKAjMw}6z$yhGWT044)N-&TC zB{5J`G0#9o$ z&%_qduEdyH=g2o=0VDL=S8d#*2fChL9XMbs)-V>kQsu5oZyMoB*wq*GNCUQ;=G1uWwc zg{mFNz63;fOY;XOMb6vi=abHRitbC{_qo(8sLUiu)98%azVkxOX5gi!KkomNe{Dw+ zvx9ujg;u~p0!~F1w&6>p#q!DIH{q7r@`Z}n^_bg>L5hVxO`SZgN^mUBxvuvVb>BJP z=Zzch^|-!32dgW7R&D#Z+TUfAT+xjj59$0F0|DFM(VO#`kJVdS@Y%U34UKBKKL3XGG%fP>t6TgHUH$sh}_Z(T|v{*OBjz505>+}jO2CqqH3K5C(X3!|C2c(GWlI>qb+|}3wqy2Rm+Uk z*d6&0tma$MD(fKhX{LPU<^L^F%7LG|yfj%oUB(~Js^5E~Imu+c1FP-iIy?vx3SS@C zu$<3r{ZBt+0FRnpA3SDQp;9ExYjC2|4+PSWmy?La`u%m@^H+rINdlbkuP*$d0Q{bS zcbFJ$S(eXlQTJlKEg*kl@tj*M7^hWfgL>C@hc7Cd`~`P_QHAZ{=PnRl?MBCM%sv<5 zUuxii|7HxyaYml%y9iOKmBuw4N}0)el2`e!d%sWcgGNm;UOq;hF1z8BBA&g0jOtdB z+o+5)?xdl(k1A%M6h#GmaqbON-uk_K6m~d&v{rtF5(_7KLj3Mq0RV+w*x!%Y7#94b zk+_o;j9%OaYmDN2vi{%!-o*X?1*oZ-%v_k7#j8JQN2nuf^&ir^Q+APL#{SolNV|X? zW3J4a-*Lad`xhE;E3g!9c`j0O2#VVFUo`F>5PJkx7Qq7*`M*;rd=At$C`Pv<|CDR^ z!oYDJghECCIu2M3p*rVB$rJzdaetWrY^=Xdh~9di(k8c6GvE6lI@}YWzG8ono$}Y- zeh2p=<<{%Gf5JUhU*Lof(Ypix8Z3|nC@BtBSxQkJ{_)|5ps%OCbTof@5+(P%XEa!R ztp3URp5Eu}55yr9f1k7{2S^pq3%TvT19|lTtcLsZ#^2R|XarWn;((FvKhfX+@0H2b zp{M6ty3NfV4|`dftW+)Grc%fx{;!p<#D`t}MujOH%Q zwB+xI(ds?&#fZ;Vp3L~C{E%y(Tz}}9G26k{Rww#*1?U z>i_7o=UrjzeqBY#K@|gzZrX_a4NlmgcnCZrx%;Ws)g3WO5MvNuV0X2?%>zFMDIbwW zDAfWki+v!CAlrj4A~gQLV_^)pYyq67JK(nEn!-YjP;%I9!pne$x{1upODjn0I}yQIFb+u2@7uW?Tv zqIqDxjjo#Hn+4~=3B_|kp5O!>`d?Kqd|crCJhW=56qhKa6TW0y?~Z&y?MrA&%*FlK zK4*w^>n&LC07a_QOu;HFKJO#tS-+W0iZP9LBJ~kqdi-&D#~?=!Mrx!J6|=ubY1PQG z^DCi^&#!%158+^#i8{{wb*?U7TMd$9!p?qRQXY~3{NO%W4Dq-od@;S?h}fW;v;QR+ z-g?F4sOk@%!8uM!^Haozjd=-}{utzLo(Y3}@-#;G!4n;O5c`sbK=2tL%Rd1UHje6G zd{7AtpHn0W%6z+t%$Grpr6fKCsNNcCGeNs)AMSUl_t-$b>ou7_!8;Ky{2>R}9%?mW`;|1t{Aq_5q5^r{7nD^_SPz z(YWfg8(&M{LOiY}@En$VklxNuxq|AKRyEI~wJ4UQZj>2>*=WT4?2-tA-VZTDMB^S( znFHmO4vE_mupJNo<>&6lmWpnu0jE8AU9hW_#~ii-4<^6NtK7&ZjiE5$MN6c{QMA3i zIm$z$pV$mXNw!KSmiA2|V+EW&>@h7tmTd-B(NtO(DOr{U&z55@|mS7DG_(uvJ zS5!}Zb)YY8VYZRPc<0h?cZQ`9+XXG_j=R_6clKwuRL8q>Y|27jUh`R&7qyu)WDGYo zHiF?e{lELY1iX)kAIsLZ=PlbLe44Fy!*fnMd1h+ztzK|rxZwpr-R&AOBGQKjYD6yB zf(}Q|-=H(;a=hJmiQL9~IZM+NE5^IrG+bqS`+5t_3*vFhyYq{UNGAUINVlO9wH9tw z4biP|K+@euPlb41p4u;|;WIo{8x5RI&ga*LE#5?rkId@yxcC_YlT&zjr5^Xv{Q)-& zE=}#8>fRlV>sih_ClD@A48;nZZgcI5w5;8*I=n4Y%5d?RBs_FU#$Z>p3zDr9&9r0s zQEw;#fIbX4Oa@%h_r-|8(nlsSTwslaIOKB&W<>M3)Zuj8#yv8PJijfT9UHvMj^*B?euzeQGAZ{ub22!Z35vw8F;t154k-S3AbKuN0i!l ziw0F@Qr+Qwudw?SD7-{W{Jg3w@_OTg;_lY+&DH;^;=IGzeA_o(T2;GtX;E!$N`h96 zs{L(i6U3}gQN-RMY8SD-YR{rEW3N)9)Tou(qli&^3!)f2{#3YS9y#sU-px&h_M{zsfb@R{f9S`MUU{SUvgJgXg-pm-y`B zhV{P8%LTG~I@f_xm6TE!Yz})-VESJLGpV780#G*EJ^V+Gv)x%w4nV=(*J8Tj39fFC z?O8nK@$MuO7+BPO$jtt|c&I(`w!pw)fq#brLcX4Jx_RX;=Op@V8UXU@SH`ZxYs_=( z*x1z~jHS{^gaW|nl?F0fo(D>gt|}~*(UnQ}b|PRS4=YR?nY(&_hZqDABS`(f$zAvG z+(uP{atojm+F1(Fg78<#t1~Fdu}P5Au=CuX`|2(hl=|2>-~B* zvb6`9W%f4ZV0rFeID}nw7nwoRGi^S479;IaB-1XdWVfhbyHZz?h;>1bGLz&2Hexj? zQAEQIK5wk&lkQ-()F0&~OpOYX;hwyF1o*y{sM+A8nD;fR>;)<`v*9)pL$lf$T`wn* zXY43dZ1822o5%Gbb;>Xo>$_T`FDcl%;(u4}dZ47d2^B>lBGKuO7JB1%7~gTQN5W(o zdf!^Y7Lx_Z5IHTTvAf(vf#rdedc7J|#CW;si}}-F6@e;$BG1NrJqIGAp1j0>Pc2u; zK=&iWZSc2Sm8pa)z-_Nn`uM#034iS7$Nb1Vj2}c?N8y$hnmGr;Hh0I%6Qdj;=fE$V zEE4UEMfOJjvPtCk0x|AL?Df#^%KG1X-fk z63snR1v4;)mrcktH+>;<6l(?cbIgcJdk=f}5wAhS_zR?o%o4QUjMAfsK`k7pCUHyv z%N*gsSx-LSX-6-_F|HuKEL*8}VicdDA3%vRHn{I)EwNO&M)iRK_=?Rs4a05E_ys#QoWey}Wjid$ zr_1W#rPNr-X~qxMu44I48k}5fK3yR8{*#Or<~E!3u zTT!8rI{7oHDhCI%^eXp$Epp3{&Ij*q7uriE4} z*I5$#*(>fjzWKa;vFg7WRVq zJVszQ+Gu4#_{Hn$20+la>*YV1G}K&?8yrL{xf3~{-9PB92%|xJ{AO)x#4cS^ei}up zI)kuXmL>-vNe&N?Tq}u%r#6%-OkOiCPxdnx5N7i6N|5D!V&DE^S5?9 zDg;6v+rDN*CLS==CaejnH~++Fs&0x?o7U*gwgGcv9q9Z8tKTdXm%euX{0kG9jIII2 z$3V}wI;*7}+$x4X98Yu)JiZFMZK8-ZUp(2LG2IQU!LW1#x|&O2?~;ndDOv(wy~miV z5Be%2lR%x9SjpvAmo`{EDJSVGWo)rs8FWE;0h{+yJoDH-iKac#RC6g`Zw)Gmt~g4z zk5g$`E2FafAzppGht5gsiXdw6!hI@nock`@q@?5*$lS0 zQx)AB69nsw13Yg|WK2K8{00E{6#hHIltTZeg8VZ0^aUC`n0_jjrc4!+1_;OD=0GXc zHzz4aS-X*NFvW}ds!xx51RuXdNo^&(KWPI5g4*n$_3k?x-a7azo%>s&UzQ#ok_?gAk_V-d8Gp(y5! zcTV8Zk_j^0_UxtttiGcUS=N>Z>FGlzR} zCxX_aV=@u^59PCRgXj0~cN?ZfvuCqD=-F zaZlfx1{$+S!dwz&7_}~Dta604GOF`sz50uVHzYf#PVi#zM4%tj$<7EAc|;OLkzF?ZpS;srPRAWJRdpY(eYpa(cS{+)z zsWF=?I3G?Qv5s|Uf-GCyegGM84`hhnU>SLklzV;<*1LCP{Y}+^HN3?Qm7zv%9Do)N zJnyJ&W!!Tc|ALo<>su^z!5(8l>k+vVmj9sn16|MJDf$k<>Y6!iERt*Gh94x112fWE z0}0w(qb7}>`TZF8_Mgj#(!tsnhi}O%t^5Bi>s~yq2~l3Z`X*br+9zY=27ty{JTkDB zjc$AFeZ%#bbcp(gjk&g9Q&H0W6;2C6Y;O$v(-#s(=pW(4NbO>x*i&E8V=;jzIg_@Y zpbMP5aQ-Cq{S}sPS92{t`bxN9ile7;+kLs7#7K3%U4R)ngfe#KQnrF#sJ)Z~25KtR zKD}Fz9b)?;nKjR5UiFu$7v!8dt_Q46k+!he3{x$?x|w&E zB#_2Aj#?J6ujq?>4=yY9AgeD;?#WTSRR6@8?TXvsuRr*{)?-U@qnhn!=jP5zn$_CM zI~a?{h7UZl87%ZmQK;ST8SGqAWGjX7J`I(hAb?uV*#IjGlDR5z>}~vA+c~GLXy+x|JiEW<` zdfQ~dzqv=@p5?bZ+Y{SYtixor2-l`E~>l__PD zKH7xfI9>_TR---DuWz_OwL10@uk0Dz(lZADR{|5+@zrwPmp+vl;e>``Mn6Xy*=^|C zsk%)mT8!1%tKHGVt(mdYR5Y0AGVf_vp-Y?fmILtM`A$g>ur~?4K6hi-x|-1v(z9de z>hT`!2N`QnoZzl8s-w6zhMS%9#SN-^UFkJy&1-{1#CUMyyFU>PJ|cWv=~wrY_zzWc zE5ycYzbZ)n&K#LqvVrQB2bjI&Im=*bWFZlLE;jZsU-W>govHXIWhiH;5O163o#XD; zodLK>_HRjk;l9nA^Ky!r;tG+;KgZMCOMH5rT0_~4e#DtqYV~93HeTUBy3b0B06hvZ z)+J@T-3PC|w^KL#LiFLO@s#ka!yG0a+dT>nzs_M$wU>-F|x64-D2N^I|Z=1QU9mMI88d)6mRRz=(zt|Kp-2v-jlF3 zA~0BWP?df{vJNjS2<%mHod$!Yp`T5c+#zh7y-#qQu*$eMr3sclAGPRY>Hcze|L(IC z;`lf}hGRcQNU%aQmW5G+Sg18_6X{d?ONcLSc5JJqIc>95+>XOnHFIAA+)nB_0MV0j zmM0DCgZ_-q2eZC)gJzikG~ohg2zJHNZ|Anwf`eh3Od5on9AA+gX>E%%%}R>708I(5 zrnHg_n}ziWDueq49ewxg21}i$YuH%_*g)`TQ|m&nIRwGc(3HSwCvwa`ZIPW(4O;h_GvdMee#h542mE;lB2 zrF>7;2`;PI39|H~MrF;$AjOYXFU}Yb@VGCr%r6t_l@}{V8F|8UbwV1P1~h=ZpW=e1 zlkz;WS{Kv^eJkd&{h83zgPmF7!eJwj2CM|~DEJ3^<2|=E)Vs_L?0#M|{7Ar;Upvy5 z>XM-Oyq0kH6vpgJyx_N#Z;3JVyV`Ce^wd3@(dPfZd6P|&vw1~^7mv%T}g_WzQZm05N3<9?*dD23C)_#-xhsdL1Tm`wy2e zXZ^uw^g(d+bOs5dtt6zC+~|OlFMsvYkjEUqV^;j6KsfTbN_BQV*PFTicsw;2)qjZ< zAUCt8UE{4M58l;);*+rpWf|R4=*JuLvG4FJ7Zxtc6I6FMc/jTN7X+wOSXjQl/jU7dz21lm67b6NkGpmkTARa7uf/gYBFU/streAK72+UQ8kyO+cnJx/ADtksHi6kDSeX4uAhR1sBE8dMuxgjExiqLfU8pxZHKebGWaSB5nJ2Bk8/ovlLQvrigdsmdsyUyJEmPC4bPRFFDE/KdmolGJd3u1BhEHJENMZAwbPpyG03vMgmWfWLrZ39q+Mz+bFkZHdy7ZMqf9zJsUqyo/XweRh88o2L2jRV36iyzkNxHrPREYdMpBCJNmnxdOAhSnbMrbxka3b3y1ZlLymAc4aPNJwlZ/6yJt8vhyO85+XPBdINifF0maoQ/rrOU+YF1M/3bpWQaBs82QR5psfeBgORCjkpi1xewNz1Ff2/GhMJuzp6C9GWw4qvphYsEQ+q13yBmZOLo8sqyC53vnJsXPbfM9HRTuah8Zs2/MOj/qQE9LTIjpayuCuAi7U+5A9slDEi/RUsDGWdMHWQv5Unz+5w/FfHWyH6tf0p1J9miUbHh/HUlU8RSJSO/WXiRQ/2V6QGZuX2kKln2cUo5aQwz3rdSGHkfX+mDNBzF2zgFNl+upeVTpKA4t1A1OHtounxLargWmVYW6nij2YCPUgTKeC8WsBlsXI9RLJ6GIJ4/bTJfY6eJAOZ64gqz2flwlbZKavt7eTdFTTRUo2mi7jmmO/MS+hYsJ+0U1OPW7S5FmAMJ1k46MnmtcFdFrsbrw5TIuuCgAW+gKHPdYN+92e74GA4MC/ubh4U3AZbw4uVahg39cFV2BPbevVwZV58CjczwiXswCCaLs2JFtFPkWw5HG/396cJUZsOJCbZlBWwg0mz5vJd+8suSFN0VhbvPUAt/GVOzhLbsRsLty6cJim1FqR/gqOL2S/mrBCqvfuXaUMq6pPXp37mofoAIjXE9IKiJoqr7YBDos8bzIa/TjLAX1Yz+g41jW/wGh0r7+dJcWDtNhgMNqQYS4MByJgvkYXns+6SMUZA5UzhjLodFFXo4sc8n5HvSDhJzxmIVcMzk9tH6yJkC6ssyxCIFFiV0AU5g8vZsyfK9s35otZlHARaRZG7txhtgryN3teCxls9+dpA+1iZ4WOqSonvc0xxcrE/ioI0SxWWbgCx8BazWPykfvsP6ejs+OPegf1CYYjAxuambUSB0Dl1r9V3108nJyJEHmZ7naNqaBrEk0ur6twKab3PbzDq3/cVpAlhnFKsghOklzGIX1uBVwQtla3SbgYwL1XsyVNWsEWBG6zbI9dYJ0wyeM5kzSstRT/04vz3xQrvcMyEnqupxNQqIIyksDLFbdilVaRtY6KhurzXjnjmAQ3OSqg5umvkkRXlLeBreM0yRYqf2+Yyn6aCpo24jUbxQvF5T3/PObtRGs3mhWgPIRUy7cnXUYJkw8pQ6jmPb6IQ/7AFXHVYHL5ypuVKlSUzTjNROXr+jpFiRwMfVaFoiRQUU5UVc5kS2GDikQr31E9sE2oLwehWKXx/d51lHOpLmu8Ja48ipDTA47V3hKHUAX3B5lQgk2kSIQv/pcHx11mH66lNSkPTKjsWrPY4xikXAc4doN1gAmFV5/yYAXRDldM3njtmFjsEvHtBbxS+tFMK6gC3lCNDRmL+5LyqFKSDa2wW+h0KDWXXRf0l07YuiF7oq24pIFPCVxzr8AR4FfqBNuAmxSrJ6fADSXahRCzUKO/7gTXyTJ3ueTLhEZvW1z+U31hEud0voDSq/CFMeQ0FLNxKNZnCPXwRvUmoVpQYrkhn9IpPUOQ9ilBwit2tyvJo9kZcnRQ93QcYTEMCLIocNNHRDtb2bVHDJBxRq49MrZbiqdByZYVC8CjpL8ltX9fj+4BktwmWUgT/ljuXocnP8JE8M2Dh9vHcA8CuosPH0xZipX0Wd5uh/n3XRn2YVcJlTOWgK42Htue+uucCCvsj+tEpZu+WFW5UdNZrY6E9f3HdaSJzeocqemsVkdC3fBxHWkbFY5ITWe1OlJ30eijOtIxetU5UtNZhY5UX3f/CZHtvvvjDTL6Fw== \ No newline at end of file diff --git a/docs/conf_common.py b/docs/conf_common.py index 8c4612560..0eafe710d 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -132,6 +132,9 @@ nwdiag_fontpath = '../_static/DejaVuSans.ttf' rackdiag_fontpath = '../_static/DejaVuSans.ttf' packetdiag_fontpath = '../_static/DejaVuSans.ttf' +# Enabling this fixes cropping of blockdiag edge labels +seqdiag_antialias = True + # Breathe extension variables # Doxygen regenerates files in 'xml/' directory every time, diff --git a/docs/en/api-reference/index.rst b/docs/en/api-reference/index.rst index e35c4cc39..0de5c5ae6 100644 --- a/docs/en/api-reference/index.rst +++ b/docs/en/api-reference/index.rst @@ -11,6 +11,7 @@ API Reference Ethernet Peripherals Protocols + Provisioning Storage System Configuration Options diff --git a/docs/en/api-reference/provisioning/index.rst b/docs/en/api-reference/provisioning/index.rst new file mode 100644 index 000000000..cb064e30b --- /dev/null +++ b/docs/en/api-reference/provisioning/index.rst @@ -0,0 +1,11 @@ +Provisioning API +**************** + +.. toctree:: + :maxdepth: 1 + + Unified Provisioning + Protocol Communication + Wi-Fi Provisioning + +Example code for this API section is provided in :example:`provisioning` directory of ESP-IDF examples. diff --git a/docs/en/api-reference/provisioning/protocomm.rst b/docs/en/api-reference/provisioning/protocomm.rst new file mode 100644 index 000000000..8dc72c50a --- /dev/null +++ b/docs/en/api-reference/provisioning/protocomm.rst @@ -0,0 +1,169 @@ +Protocol Communication +====================== + +Overview +-------- +Protocol Communication (protocomm) component manages secure sessions and provides framework for multiple transports. The application can also use protocomm layer directly to have application specific extensions for the provisioning (or non-provisioning) use cases. + +Following features are available for provisioning : + * Communication security at application level - + * protocomm_security0 (no security) + * protocomm_security1 (curve25519 key exchange + AES-CTR encryption) + * Proof-of-possession (support with protocomm_security1 only) + +Protocomm internally uses protobuf (protocol buffers) for secure session establishment. Though users can implement their own security (even without using protobuf). One can even use protocomm without any security layer. + +Protocomm provides framework for various transports - WiFi (SoftAP+HTTPD), BLE, console - in which case the handler invocation is automatically taken care of on the device side (see Transport Examples below for code snippets). + +Note that the client still needs to establish session (only for protocomm_security1) by performing the two way handshake. See :doc:`provisioning` for more details about the secure handshake logic. + +Transport Example (SoftAP + HTTP) with Security 1 +------------------------------------------------- +For complete example see :example:`provisioning/softap_prov` + + .. highlight:: c + + :: + + /* Endpoint handler to be registered with protocomm. + * This simply echoes back the received data. */ + esp_err_t echo_req_handler (uint32_t session_id, + const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, + void *priv_data) + { + /* Session ID may be used for persistence */ + printf("Session ID : %d", session_id); + + /* Echo back the received data */ + *outlen = inlen; /* Output data length updated */ + *outbuf = malloc(inlen); /* This will be deallocated outside */ + memcpy(*outbuf, inbuf, inlen); + + /* Private data that was passed at the time of endpoint creation */ + uint32_t *priv = (uint32_t *) priv_data; + if (priv) { + printf("Private data : %d", *priv); + } + + return ESP_OK; + } + + /* Example function for launching a protocomm instance over HTTP */ + protocomm_t *start_pc(const char *pop_string) + { + protocomm_t *pc = protocomm_new(); + + /* Config for protocomm_httpd_start() */ + protocomm_httpd_config_t pc_config = PROTOCOMM_HTTPD_DEFAULT_CONFIG(); + + /* Start protocomm server on top of HTTP */ + protocomm_httpd_start(pc, &pc_config); + + /* Create Proof of Possession object from pop_string. It must be valid + * throughout the scope of protocomm endpoint. This need not be static, + * ie. could be dynamically allocated and freed at the time of endpoint + * removal */ + const static protocomm_security_pop_t pop_obj = { + .data = (const uint8_t *) strdup(pop_string), + .len = strlen(pop_string) + }; + + /* Set security for communication at application level. Just like for + * request handlers, setting security creates an endpoint and registers + * the handler provided by protocomm_security1. One can similarly use + * protocomm_security0. Only one type of security can be set for a + * protocomm instance at a time. */ + protocomm_set_security(pc, "security_endpoint", &protocomm_security1, &pop_obj); + + /* Private data passed to the endpoint must be valid throughout the scope + * of protocomm endpoint. This need not be static, ie. could be dynamically + * allocated and freed at the time of endpoint removal */ + static uint32_t priv_data = 1234; + + /* Add a new endpoint for the protocomm instance, identified by a unique name + * and register a handler function along with private data to be passed at the + * time of handler execution. Multiple endpoints can be added as long as they + * are identified by unique names */ + protocomm_add_endpoint(pc, "echo_req_endpoint", + echo_req_handler, (void *) &priv_data); + return pc; + } + + /* Example function for stopping a protocomm instance */ + void stop_pc(protocomm_t *pc) + { + /* Remove endpoint identified by it's unique name */ + protocomm_remove_endpoint(pc, "echo_req_endpoint"); + + /* Remove security endpoint identified by it's name */ + protocomm_unset_security(pc, "security_endpoint"); + + /* Stop HTTP server */ + protocomm_httpd_stop(pc); + + /* Delete (deallocate) the protocomm instance */ + protocomm_delete(pc); + } + +Transport Example (BLE) with Security 0 +--------------------------------------- +For complete example see :example:`provisioning/ble_prov` + + .. highlight:: c + + :: + + /* Example function for launching a secure protocomm instance over BLE */ + protocomm_t *start_pc() + { + protocomm_t *pc = protocomm_new(); + + /* Endpoint UUIDs */ + protocomm_ble_name_uuid_t nu_lookup_table[] = { + {"security_endpoint", 0xFF51}, + {"echo_req_endpoint", 0xFF52} + }; + + /* Config for protocomm_ble_start() */ + protocomm_ble_config_t config = { + .service_uuid = { + /* LSB <--------------------------------------- + * ---------------------------------------> MSB */ + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, + }, + .nu_lookup_count = sizeof(nu_lookup_table)/sizeof(nu_lookup_table[0]), + .nu_lookup = nu_lookup_table + }; + + /* Start protocomm layer on top of BLE */ + protocomm_ble_start(pc, &config); + + /* For protocomm_security0, Proof of Possession is not used, and can be kept NULL */ + protocomm_set_security(pc, "security_endpoint", &protocomm_security0, NULL); + protocomm_add_endpoint(pc, "echo_req_endpoint", echo_req_handler, NULL); + return pc; + } + + /* Example function for stopping a protocomm instance */ + void stop_pc(protocomm_t *pc) + { + protocomm_remove_endpoint(pc, "echo_req_endpoint"); + protocomm_unset_security(pc, "security_endpoint"); + + /* Stop BLE protocomm service */ + protocomm_ble_stop(pc); + + protocomm_delete(pc); + } + +API Reference +------------- + +.. include:: /_build/inc/protocomm.inc +.. include:: /_build/inc/protocomm_security.inc +.. include:: /_build/inc/protocomm_security0.inc +.. include:: /_build/inc/protocomm_security1.inc +.. include:: /_build/inc/protocomm_httpd.inc +.. include:: /_build/inc/protocomm_ble.inc diff --git a/docs/en/api-reference/provisioning/provisioning.rst b/docs/en/api-reference/provisioning/provisioning.rst new file mode 100644 index 000000000..7990e31ae --- /dev/null +++ b/docs/en/api-reference/provisioning/provisioning.rst @@ -0,0 +1,154 @@ +Unified Provisioning +^^^^^^^^^^^^^^^^^^^^ + +Overview +>>>>>>>> +Unified provisioning support in the ESP-IDF provides an extensible mechanism to the developers to configure the device with the Wi-Fi credentials and/or other custom configuration using various transports and different security schemes. Depending on the use-case it provides a complete and ready solution for Wi-Fi network provisioning along with example iOS and Android applications. Or developers can extend the device-side and phone-app side implementations to accommodate their requirements for sending additional configuration data. Following are the important features of this implementation. + +1. *Extensible Protocol:* The protocol is completely flexible and it offers the ability for the developers to send custom configuration in the provisioning process. The data representation too is left to the application to decide. +2. *Transport Flexibility:* The protocol can work on Wi-Fi (SoftAP + HTTP server) or on BLE as a transport protocol. The framework provides an ability to add support for any other transport easily as long as command-response behaviour can be supported on the transport. +3. *Security Scheme Flexibility:* It's understood that each use-case may require different security scheme to secure the data that is exchanged in the provisioning process. Some applications may work with SoftAP that's WPA2 protected or BLE with "just-works" security. Or the applications may consider the transport to be insecure and may want application level security. The unified provisioning framework allows application to choose the security as deemed suitable. +4. *Compact Data Representation:* The protocol uses `Google Protobufs `_ as a data representation for session setup and Wi-Fi provisioning. They provide a compact data representation and ability to parse the data in multiple programming languages in native format. Please note that this data representation is not forced on application specific data and the developers may choose the representation of their choice. + +Typical Provisioning Process +>>>>>>>>>>>>>>>>>>>>>>>>>>>> +.. seqdiag:: + :caption: Typical Provisioning Process + :align: center + + seqdiag typical-prov-process { + activation = none; + node_width = 80; + node_height = 60; + edge_length = 360; + span_height = 5; + default_shape = roundedbox; + default_fontsize = 12; + + CLIENT [label = "Client"]; + DEVICE [label = "Device"]; + + === 1. Transport specific discovery and connection === + DEVICE -> CLIENT [label="Some form of beaconing"]; + CLIENT -> DEVICE [label="Client connects"]; + === 2. Session Establishment ==== + CLIENT -> DEVICE [label="Get Version Request"]; + DEVICE -> CLIENT [label="Get Version Response"]; + CLIENT -> DEVICE [label="Session Setup Request"]; + DEVICE -> CLIENT [label="Session Setup Response"]; + CLIENT --> DEVICE; + ... One or multiple steps as per protocol ... + DEVICE --> CLIENT + === 3. Configuration === + CLIENT --> DEVICE [label="App specific Set Config (optional)"]; + DEVICE --> CLIENT [label="Set Config Response (optional)"]; + CLIENT -> DEVICE [label="Wi-Fi SetConfig(SSID, Passphrase...)"]; + DEVICE -> CLIENT [label="Wi-Fi SetConfig response"]; + CLIENT -> DEVICE [label="Wi-Fi ApplyConfig cmd"]; + DEVICE -> CLIENT [label="Wi-Fi ApplyConfig resp"]; + CLIENT -> DEVICE [label="Wi-Fi GetStatus cmd (repeated)"]; + DEVICE -> CLIENT [label="Wi-Fi GetStatus resp (repeated)"]; + === 4. Close connection === + DEVICE -> CLIENT [label="Close Connection"]; + } + +Deciding on Transport +>>>>>>>>>>>>>>>>>>>>> +Unified provisioning subsystem supports Wi-Fi (SoftAP+HTTP server) and BLE (GATT based) transport schemes. Following points need to be considered while selecting the best possible transport for provisioning. + +1. BLE based transport has an advantage that in the provisioning process, the BLE communication channel stays intact between the device and the client. That provides reliable provisioning feedback. +2. BLE based provisioning implementation makes the user-experience better from the phone apps as on Android and iOS both, the phone app can discover and connect to the device without requiring user to go out of the phone app +3. BLE transport however consumes ~110KB memory at runtime. If the product does not use the BLE or BT functionality after provisioning is done, almost all the memory can be reclaimed back and can be added into the heap. +4. SoftAP based transport is highly interoperable; however as the same radio is shared between SoftAP and Station interface, the transport is not reliable in the phase when the Wi-Fi connection to external AP is attempted. Also, the client may roam back to different network when the SoftAP changes the channel at the time of Station connection. +5. SoftAP transport does not require much additional memory for the Wi-Fi use-cases +6. SoftAP based provisioning requires the phone app user to go to "System Settings" to connect to Wi-Fi network hosted by the device in case of iOS. The discovery (scanning) as well as connection API is not available for the iOS applications. + +Deciding on Security +>>>>>>>>>>>>>>>>>>>> +Depending on the transport and other constraints the security scheme needs to be selected by the application developers. Following considerations need to be given from the provisioning security perspective: +1. The configuration data sent from the client to the device and the response has to be secured. +2. The client should authenticate the device it is connected to. +3. The device manufacturer may choose proof-of-possession - a unique per device secret to be entered on the provisioning client as a security measure to make sure that the user can provisions the device in the possession. + +There are two levels of security schemes. The developer may select one or combination depending on requirements. + +1. *Transport Security:* SoftAP provisioning may choose WPA2 protected security with unique per-device passphrase. Per-device unique passphrase can also act as a proof-of-possession. For BLE, "just-works" security can be used as a transport level security after understanding the level of security it provides. +2. *Application Security:* The unified provisioning subsystem provides application level security (*security1*) that provides data protection and authentication (through proof-of-possession) if the application does not use the transport level security or if the transport level security is not sufficient for the use-case. + +Device Discovery +>>>>>>>>>>>>>>>> +The advertisement and device discovery is left to the application and depending on the protocol chosen, the phone apps and device firmware application can choose appropriate method to advertise and discovery. + +For the SoftAP+HTTP transport, typically the SSID (network name) of the AP hosted by the device can be used for discovery. + +For the BLE transport device name or primary service included in the advertisement or combination of both can be used for discovery. + +Architecture +>>>>>>>>>>>> +The below diagram shows architecture of unified provisioning. + +.. figure:: ../../../_static/unified_provisioning.png + :align: center + :alt: Unified Provisioning Architecture + + Unified Provisioning Architecture + +It relies on the base layer called :doc:`protocomm` (Protocol Communication) which provides a framework for security schemes and transport mechanisms. Wi-Fi Provisioning layer uses Protocomm to provide simple callbacks to the application for setting the configuration and getting the Wi-Fi status. The application has control over implementation of these callbacks. In addition application can directly use protocomm to register custom handlers. + +Application creates a protocomm instance which is mapped to a specific transport and specific security scheme. Each transport in the protocomm has a concept of an "end-point" which corresponds to logical channel for communication for specific type of information. For example security handshake happens on a different endpoint than the Wi-Fi configuration endpoint. Each end-point is identified using a string and depending on the transport internal representation of the end-point changes. In case of SoftAP+HTTP transport the end-point corresponds to URI whereas in case of BLE the end-point corresponds to GATT characteristic with specific UUID. Developers can create custom end-points and implement handler for the data that is received or sent over the same end-point. + +Security Schemes +>>>>>>>>>>>>>>>> +At present unified provisioning supports two security schemes: +1. Security0 - No security (No encryption) +2. Security1 - Curve25519 based key exchange, shared key derivation and AES256-CTR mode encryption of the data. It supports two modes : + + a. Authorized - Proof of Possession (PoP) string used to authorize session and derive shared key + b. No Auth (Null PoP) - Shared key derived through key exchange only + +Security1 scheme details are shown in the below sequence diagram + +.. seqdiag:: + :caption: Security1 + :align: center + + seqdiag security1 { + activation = none; + node_width = 80; + node_height = 60; + edge_length = 480; + span_height = 5; + default_shape = roundedbox; + default_fontsize = 12; + + CLIENT [label = "Client"]; + DEVICE [label = "Device"]; + + === Security 1 === + CLIENT -> CLIENT [label = "Generate\nKey Pair", rightnote = "{cli_privkey, cli_pubkey} = curve25519_keygen()"]; + CLIENT -> DEVICE [label = "SessionCmd0(cli_pubkey)"]; + DEVICE -> DEVICE [label = "Generate\nKey Pair", leftnote = "{dev_privkey, dev_pubkey} = curve25519_keygen()"]; + DEVICE -> DEVICE [label = "Initialization\nVector", leftnote = "dev_rand = gen_16byte_random()"]; + DEVICE -> DEVICE [label = "Shared Key", leftnote = "shared_key(No PoP) = curve25519(dev_privkey, cli_pubkey) \nshared_key(with PoP) = curve25519(dev_privkey, cli_pubkey) ^ SHA256(pop)"]; + DEVICE -> CLIENT [label = "SessionResp0(dev_pubkey, dev_rand)"]; + CLIENT -> CLIENT [label = "Shared Key", rightnote = "shared_key(No PoP) = curve25519(cli_privkey, dev_pubkey)\nshared_key(with PoP) = curve25519(cli_privkey, dev_pubkey) ^ SHA256(pop)"]; + CLIENT -> CLIENT [label = "Verification\nToken", rightnote = "cli_verify = aes_ctr_enc(key=shared_key, data=dev_pubkey, nonce=dev_rand)"]; + CLIENT -> DEVICE [label = "SessionCmd1(cli_verify)"]; + DEVICE -> DEVICE [label = "Verify Client", leftnote = "check (dev_pubkey == aes_ctr_dec(cli_verify...)"]; + DEVICE -> DEVICE [label = "Verification\nToken", leftnote = "dev_verify = aes_ctr_enc(key=shared_key, data=cli_pubkey, nonce=(prev-context))"]; + DEVICE -> CLIENT [label = "SessionResp1(dev_verify)"]; + CLIENT -> CLIENT [label = "Verify Device", rightnote = "check (cli_pubkey == aes_ctr_dec(dev_verify...)"]; + } + +Sample Code +>>>>>>>>>>> +Please refer to :doc:`protocomm` and :doc:`wifi_provisioning` for API guides and code snippets on example usage. + +Various use case implementations can be found as examples under :example:`provisioning`. + +Provisioning Tools +>>>>>>>>>>>>>>>>>> + +A python based provisioning tool is provided under `$IDF_PATH/tools/esp_prov`, intended for use along with the examples under :example:`provisioning`. + +For android, a provisioning tool along with source code is available on Github as `esp-idf-provisioning-android `_ diff --git a/docs/en/api-reference/provisioning/wifi_provisioning.rst b/docs/en/api-reference/provisioning/wifi_provisioning.rst new file mode 100644 index 000000000..a822bf62f --- /dev/null +++ b/docs/en/api-reference/provisioning/wifi_provisioning.rst @@ -0,0 +1,60 @@ +Wi-Fi Provisioning +================== + +Overview +-------- + +This component provides protocomm endpoint handler - `wifi_prov_config_data_handler` - and related protobuf framework which can be used for Wi-Fi configuration in the context of device provisioning, though it may be used in non-provisioning cases as well. The configuration consists of three commands : + * `get_status` - For querying the Wi-Fi connection status + * `set_config` - For setting the Wi-Fi connection credentials + * `apply_config` - For applying the credentials saved during `set_config` and (re)start the Wi-Fi station + +The way this is supposed to work is that the desired Wi-Fi configuration for the ESP32, which is to run as a station and thus connect to an AP with certain credentials, is to be sent during `set_config`. Then `apply_config` is supposed to start (or restart) the Wi-Fi in station mode with the previously set AP credentials. Afterwords, `get_config` command is used to probe the device continuously for Wi-Fi connection status, to ensure that the connection was indeed successful. If the connection failed, then appropriate status code along with disconnection reason, is to be conveyed through `get_config`. + +Application Example +------------------- + + .. highlight:: c + + :: + + esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data) + { + /* Fill the wifi_prov_config_get_data_t structure + * with Wi-Fi station connection status information. */ + + return ESP_OK; + } + + esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data) + { + /* Copy contents of req_data->ssid and req_data->password + * which are Wi-Fi AP credentials to which the device will connect */ + + return ESP_OK; + } + + esp_err_t apply_config_handler(void) + { + /* Apply the Wi-Fi STA credentials saved during set_config */ + + return ESP_OK; + } + + /* Structure with various config command handlers to be passed + * as private data during endpoint registration with protocomm */ + wifi_prov_config_handlers_t wifi_prov_handlers = { + .get_status_handler = get_status_handler, + .set_config_handler = set_config_handler, + .apply_config_handler = apply_config_handler, + }; + + /* Set the endpoint handler */ + protocomm_add_endpoint(pc, "wifi_config_endpoint", + wifi_prov_config_data_handler, + (void *) &wifi_prov_handlers); + +API Reference +------------- + +.. include:: /_build/inc/wifi_config.inc diff --git a/docs/zh_CN/api-reference/provisioning/index.rst b/docs/zh_CN/api-reference/provisioning/index.rst new file mode 100644 index 000000000..d7931ed5c --- /dev/null +++ b/docs/zh_CN/api-reference/provisioning/index.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/provisioning/index.rst diff --git a/docs/zh_CN/api-reference/provisioning/protocomm.rst b/docs/zh_CN/api-reference/provisioning/protocomm.rst new file mode 100644 index 000000000..c50654e0d --- /dev/null +++ b/docs/zh_CN/api-reference/provisioning/protocomm.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/provisioning/protocomm.rst diff --git a/docs/zh_CN/api-reference/provisioning/provisioning.rst b/docs/zh_CN/api-reference/provisioning/provisioning.rst new file mode 100644 index 000000000..375f2ac82 --- /dev/null +++ b/docs/zh_CN/api-reference/provisioning/provisioning.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/provisioning/provisioning.rst diff --git a/docs/zh_CN/api-reference/provisioning/wifi_provisioning.rst b/docs/zh_CN/api-reference/provisioning/wifi_provisioning.rst new file mode 100644 index 000000000..2a73a2611 --- /dev/null +++ b/docs/zh_CN/api-reference/provisioning/wifi_provisioning.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/provisioning/wifi_provisioning.rst