diff --git a/components/esp_common/src/esp_err_to_name.c b/components/esp_common/src/esp_err_to_name.c index 45e5d11f6e..59963fa78c 100644 --- a/components/esp_common/src/esp_err_to_name.c +++ b/components/esp_common/src/esp_err_to_name.c @@ -44,6 +44,9 @@ #if __has_include("esp_spi_flash.h") #include "esp_spi_flash.h" #endif +#if __has_include("esp_supplicant/esp_dpp.h") +#include "esp_supplicant/esp_dpp.h" +#endif #if __has_include("esp_supplicant/esp_wps.h") #include "esp_supplicant/esp_wps.h" #endif @@ -407,6 +410,16 @@ static const esp_err_msg_t esp_err_msg_table[] = { # endif # ifdef ESP_ERR_ESPNOW_IF ERR_TBL_IT(ESP_ERR_ESPNOW_IF), /* 12396 0x306c Interface error */ +# endif + // components/wpa_supplicant/include/esp_supplicant/esp_dpp.h +# ifdef ESP_ERR_DPP_FAILURE + ERR_TBL_IT(ESP_ERR_DPP_FAILURE), /* 12439 0x3097 Generic failure during DPP Operation */ +# endif +# ifdef ESP_ERR_DPP_TX_FAILURE + ERR_TBL_IT(ESP_ERR_DPP_TX_FAILURE), /* 12440 0x3098 DPP Frame Tx failed OR not Acked */ +# endif +# ifdef ESP_ERR_DPP_INVALID_ATTR + ERR_TBL_IT(ESP_ERR_DPP_INVALID_ATTR), /* 12441 0x3099 Encountered invalid DPP Attribute */ # endif // components/esp_common/include/esp_err.h # ifdef ESP_ERR_MESH_BASE diff --git a/components/esp_event/event_send.c b/components/esp_event/event_send.c index 27e5731ca1..334be196a3 100644 --- a/components/esp_event/event_send.c +++ b/components/esp_event/event_send.c @@ -85,6 +85,12 @@ static system_event_id_t esp_event_legacy_wifi_event_id(int32_t event_id) case WIFI_EVENT_AP_PROBEREQRECVED: return SYSTEM_EVENT_AP_PROBEREQRECVED; + case WIFI_EVENT_ACTION_TX_STATUS: + return SYSTEM_EVENT_ACTION_TX_STATUS; + + case WIFI_EVENT_ROC_DONE: + return SYSTEM_EVENT_ROC_DONE; + default: ESP_LOGE(TAG, "invalid wifi event id %d", event_id); return SYSTEM_EVENT_MAX; diff --git a/components/esp_event/include/esp_event_legacy.h b/components/esp_event/include/esp_event_legacy.h index e4f579a0a1..e307d8e60e 100644 --- a/components/esp_event/include/esp_event_legacy.h +++ b/components/esp_event/include/esp_event_legacy.h @@ -48,6 +48,8 @@ typedef enum { SYSTEM_EVENT_AP_STADISCONNECTED, /*!< a station disconnected from ESP32 soft-AP */ SYSTEM_EVENT_AP_STAIPASSIGNED, /*!< ESP32 soft-AP assign an IP to a connected station */ SYSTEM_EVENT_AP_PROBEREQRECVED, /*!< Receive probe request packet in soft-AP interface */ + SYSTEM_EVENT_ACTION_TX_STATUS, /*!< Receive status of Action frame transmitted */ + SYSTEM_EVENT_ROC_DONE, /*!< Indicates the completion of Remain-on-Channel operation status */ SYSTEM_EVENT_GOT_IP6, /*!< ESP32 station or ap or ethernet interface v6IP addr is preferred */ SYSTEM_EVENT_ETH_START, /*!< ESP32 ethernet start */ SYSTEM_EVENT_ETH_STOP, /*!< ESP32 ethernet stop */ diff --git a/components/esp_wifi/include/esp_wifi_types.h b/components/esp_wifi/include/esp_wifi_types.h index ca343d3890..238e90a8aa 100644 --- a/components/esp_wifi/include/esp_wifi_types.h +++ b/components/esp_wifi/include/esp_wifi_types.h @@ -35,6 +35,12 @@ typedef enum { WIFI_IF_AP = ESP_IF_WIFI_AP, } wifi_interface_t; +#define WIFI_OFFCHAN_TX_REQ 1 +#define WIFI_OFFCHAN_TX_CANCEL 0 + +#define WIFI_ROC_REQ 1 +#define WIFI_ROC_CANCEL 0 + typedef enum { WIFI_COUNTRY_POLICY_AUTO, /**< Country policy is auto, use the country info of AP to which the station is connected */ WIFI_COUNTRY_POLICY_MANUAL, /**< Country policy is manual, always use the configured country info */ @@ -483,6 +489,32 @@ typedef struct { enabled_ant1: 4; /**< Index (in antenna GPIO configuration) of enabled WIFI_ANT_MODE_ANT1 */ } wifi_ant_config_t; +/** + * @brief The Rx callback function of Action Tx operations + * + * @param hdr pointer to the IEEE 802.11 Header structure + * @param payload pointer to the Payload following 802.11 Header + * @param len length of the Payload + * @param channel channel number the frame is received on + * + */ +typedef int (* wifi_action_rx_cb_t)(uint8_t *hdr, uint8_t *payload, + size_t len, uint8_t channel); + +/** + * @brief Action Frame Tx Request + * + * + */ +typedef struct { + wifi_interface_t ifx; /**< WiFi interface to send request to */ + uint8_t dest_mac[6]; /**< Destination MAC address */ + bool no_ack; /**< Indicates no ack required */ + wifi_action_rx_cb_t rx_cb; /**< Rx Callback to receive any response */ + uint32_t data_len; /**< Length of the appended Data */ + uint8_t data[0]; /**< Appended Data payload */ +} wifi_action_tx_req_t; + /** * @brief WiFi PHY rate encodings * @@ -551,6 +583,8 @@ typedef enum { /* Add next events after this only */ WIFI_EVENT_STA_BSS_RSSI_LOW, /**< AP's RSSI crossed configured threshold */ + WIFI_EVENT_ACTION_TX_STATUS, /**< Status indication of Action Tx operation */ + WIFI_EVENT_ROC_DONE, /**< Remain-on-Channel operation complete */ WIFI_EVENT_MAX, /**< Invalid WiFi event ID */ } wifi_event_t; @@ -645,6 +679,19 @@ typedef struct { #define WIFI_STATIS_PS (1<<4) #define WIFI_STATIS_ALL (-1) +/** Argument structure for WIFI_EVENT_ACTION_TX_STATUS event */ +typedef struct { + wifi_interface_t ifx; /**< WiFi interface to send request to */ + uint32_t context; /**< Context to identify the request */ + uint8_t da[6]; /**< Destination MAC address */ + uint8_t status; /**< Status of the operation */ +} wifi_event_action_tx_status_t; + +/** Argument structure for WIFI_EVENT_ROC_DONE event */ +typedef struct { + uint32_t context; /**< Context to identify the request */ +} wifi_event_roc_done_t; + #ifdef __cplusplus } #endif diff --git a/components/esp_wifi/lib b/components/esp_wifi/lib index 21001cb8ac..bad7d9df47 160000 --- a/components/esp_wifi/lib +++ b/components/esp_wifi/lib @@ -1 +1 @@ -Subproject commit 21001cb8acd9f193aed4ec17e632ea861233e7e0 +Subproject commit bad7d9df47543691d8be80b449f416bf5d581746 diff --git a/components/wpa_supplicant/CMakeLists.txt b/components/wpa_supplicant/CMakeLists.txt index 377b8a8e5a..799e3e7153 100644 --- a/components/wpa_supplicant/CMakeLists.txt +++ b/components/wpa_supplicant/CMakeLists.txt @@ -18,7 +18,7 @@ set(srcs "port/os_xtensa.c" "src/crypto/aes-omac1.c" "src/crypto/aes-unwrap.c" "src/crypto/aes-wrap.c" - "src/crypto/aes-omac1.c" + "src/crypto/sha256-tlsprf.c" "src/crypto/bignum.c" "src/crypto/ccmp.c" "src/crypto/crypto_mbedtls.c" @@ -63,6 +63,7 @@ set(srcs "port/os_xtensa.c" "src/esp_supplicant/esp_wpas_glue.c" "src/esp_supplicant/esp_wps.c" "src/esp_supplicant/esp_wpa3.c" + "src/esp_supplicant/esp_dpp.c" "src/rsn_supp/pmksa_cache.c" "src/rsn_supp/wpa.c" "src/rsn_supp/wpa_ie.c" diff --git a/components/wpa_supplicant/include/esp_supplicant/esp_dpp.h b/components/wpa_supplicant/include/esp_supplicant/esp_dpp.h new file mode 100644 index 0000000000..c6c86bc536 --- /dev/null +++ b/components/wpa_supplicant/include/esp_supplicant/esp_dpp.h @@ -0,0 +1,116 @@ +// Copyright 2020 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 ESP_DPP_H +#define ESP_DPP_H + +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_ERR_DPP_FAILURE (ESP_ERR_WIFI_BASE + 151) /*!< Generic failure during DPP Operation */ +#define ESP_ERR_DPP_TX_FAILURE (ESP_ERR_WIFI_BASE + 152) /*!< DPP Frame Tx failed OR not Acked */ +#define ESP_ERR_DPP_INVALID_ATTR (ESP_ERR_WIFI_BASE + 153) /*!< Encountered invalid DPP Attribute */ + +/** @brief Types of Bootstrap Methods for DPP. */ +typedef enum dpp_bootstrap_type { + DPP_BOOTSTRAP_QR_CODE, /**< QR Code Method */ + DPP_BOOTSTRAP_PKEX, /**< Proof of Knowledge Method */ + DPP_BOOTSTRAP_NFC_URI, /**< NFC URI record Method */ +} esp_supp_dpp_bootstrap_t; + +/** @brief Types of Callback Events received from DPP Supplicant. */ +typedef enum { + ESP_SUPP_DPP_URI_READY, /**< URI is ready through Bootstrapping */ + ESP_SUPP_DPP_CFG_RECVD, /**< Config received via DPP Authentication */ + ESP_SUPP_DPP_FAIL, /**< DPP Authentication failure */ +} esp_supp_dpp_event_t; + +/** + * @brief Callback function for receiving DPP Events from Supplicant. + * + * Callback function will be called with DPP related information. + * + * @param evt DPP event ID + * @param data Event data payload + */ +typedef void (*esp_supp_dpp_event_cb_t)(esp_supp_dpp_event_t evt, void *data); + +/** + * @brief Initialize DPP Supplicant + * + * Starts DPP Supplicant and initializes related Data Structures. + * + * @param evt_cb Callback function to receive DPP related events + * + * return + * - ESP_OK: Success + * - ESP_FAIL: Failure + */ +esp_err_t esp_supp_dpp_init(esp_supp_dpp_event_cb_t evt_cb); + +/** + * @brief De-initalize DPP Supplicant + * + * Frees memory from DPP Supplicant Data Structures. + */ +void esp_supp_dpp_deinit(void); + +/** + * @brief Generates Bootstrap Information as an Enrollee. + * + * Generates Out Of Band Bootstrap information as an Enrollee which can be + * used by a DPP Configurator to provision the Enrollee. + * + * @param chan_list List of channels device will be available on for listening + * @param type Bootstrap method type, only QR Code method is supported for now. + * @param key (Optional) Private Key used to generate a Bootstrapping Public Key + * @param info (Optional) Ancilliary Device Information like Serial Number + * + * @return + * - ESP_OK: Success + * - ESP_FAIL: Failure + */ +esp_err_t +esp_supp_dpp_bootstrap_gen(const char *chan_list, esp_supp_dpp_bootstrap_t type, + const char *key, const char *info); + +/** + * @brief Start listening on Channels provided during esp_supp_dpp_bootstrap_gen. + * + * Listens on every Channel from Channel List for a pre-defined wait time. + * + * @return + * - ESP_OK: Success + * - ESP_FAIL: Generic Failure + * - ESP_ERR_INVALID_STATE: ROC attempted before WiFi is started + * - ESP_ERR_NO_MEM: Memory allocation failed while posting ROC request + */ +esp_err_t esp_supp_dpp_start_listen(void); + +/** + * @brief Stop listening on Channels. + * + * Stops listening on Channels and cancels ongoing listen operation. + */ +void esp_supp_dpp_stop_listen(void); + +#ifdef __cplusplus +} +#endif +#endif /* ESP_DPP_H */ diff --git a/components/wpa_supplicant/src/common/dpp.c b/components/wpa_supplicant/src/common/dpp.c index 44003add05..c96869b318 100644 --- a/components/wpa_supplicant/src/common/dpp.c +++ b/components/wpa_supplicant/src/common/dpp.c @@ -2397,7 +2397,7 @@ struct dpp_authentication * dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual, struct dpp_bootstrap_info *peer_bi, struct dpp_bootstrap_info *own_bi, - unsigned int freq, const u8 *hdr, const u8 *attr_start, + unsigned int curr_chan, const u8 *hdr, const u8 *attr_start, size_t attr_len) { struct crypto_key *pi = NULL; @@ -2406,10 +2406,16 @@ dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual, size_t len[2]; u8 *unwrapped = NULL; size_t unwrapped_len = 0; - const u8 *wrapped_data, *i_proto, *i_nonce, *i_capab, *i_bootstrap, - *channel; - u16 wrapped_data_len, i_proto_len, i_nonce_len, i_capab_len, - i_bootstrap_len, channel_len; + const u8 *wrapped_data; + const u8 *i_proto; + const u8 *i_nonce; + const u8 *i_capab; + const u8 *i_bootstrap; + u16 wrapped_data_len; + u16 i_proto_len; + u16 i_nonce_len; + u16 i_capab_len; + u16 i_bootstrap_len; struct dpp_authentication *auth = NULL; #ifdef CONFIG_WPA_TESTING_OPTIONS @@ -2438,10 +2444,11 @@ dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual, auth->peer_bi = peer_bi; auth->own_bi = own_bi; auth->curve = own_bi->curve; - auth->curr_freq = freq; + auth->curr_chan = curr_chan; auth->peer_version = 1; /* default to the first version */ +#if 0 channel = dpp_get_attr(attr_start, attr_len, DPP_ATTR_CHANNEL, &channel_len); if (channel) { @@ -2452,7 +2459,6 @@ dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual, goto fail; } -#ifndef ESP_SUPPLICANT neg_freq = ieee80211_chan_to_freq(NULL, channel[0], channel[1]); wpa_printf(MSG_DEBUG, "DPP: Initiator requested different channel for negotiation: op_class=%u channel=%u --> freq=%d", @@ -2469,10 +2475,10 @@ dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual, freq, neg_freq); auth->curr_freq = neg_freq; } -#endif /* rename it to chan */ - auth->curr_freq = *channel; + auth->curr_chan = *channel; } +#endif i_proto = dpp_get_attr(attr_start, attr_len, DPP_ATTR_I_PROTOCOL_KEY, &i_proto_len); @@ -5386,7 +5392,7 @@ fail: int dpp_conf_resp_rx(struct dpp_authentication *auth, - const struct wpabuf *resp) + const uint8_t *resp, uint32_t resp_len) { const u8 *wrapped_data, *e_nonce, *status, *conf_obj; u16 wrapped_data_len, e_nonce_len, status_len, conf_obj_len; @@ -5398,12 +5404,12 @@ int dpp_conf_resp_rx(struct dpp_authentication *auth, auth->conf_resp_status = 255; - if (dpp_check_attrs(wpabuf_head(resp), wpabuf_len(resp)) < 0) { + if (dpp_check_attrs(resp, resp_len) < 0) { dpp_auth_fail(auth, "Invalid attribute in config response"); return -1; } - wrapped_data = dpp_get_attr(wpabuf_head(resp), wpabuf_len(resp), + wrapped_data = dpp_get_attr(resp, resp_len, DPP_ATTR_WRAPPED_DATA, &wrapped_data_len); if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) { @@ -5419,8 +5425,8 @@ int dpp_conf_resp_rx(struct dpp_authentication *auth, if (!unwrapped) return -1; - addr[0] = wpabuf_head(resp); - len[0] = wrapped_data - 4 - (const u8 *) wpabuf_head(resp); + addr[0] = resp; + len[0] = wrapped_data - 4 - resp; wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD", addr[0], len[0]); if (aes_siv_decrypt(auth->ke, auth->curve->hash_len, @@ -5451,7 +5457,7 @@ int dpp_conf_resp_rx(struct dpp_authentication *auth, goto fail; } - status = dpp_get_attr(wpabuf_head(resp), wpabuf_len(resp), + status = dpp_get_attr(resp, resp_len, DPP_ATTR_STATUS, &status_len); if (!status || status_len < 1) { dpp_auth_fail(auth, @@ -6083,9 +6089,6 @@ int dpp_bootstrap_gen(struct dpp_global *dpp, const char *cmd) int ret = -1; struct dpp_bootstrap_info *bi; - if (!dpp) - return -1; - bi = os_zalloc(sizeof(*bi)); if (!bi) goto fail; @@ -6143,6 +6146,7 @@ int dpp_bootstrap_gen(struct dpp_global *dpp, const char *cmd) mac ? "M:" : "", mac ? mac : "", mac ? ";" : "", info ? "I:" : "", info ? info : "", info ? ";" : "", pk); + bi->id = dpp_next_id(dpp); dl_list_add(&dpp->bootstrap, &bi->list); ret = bi->id; diff --git a/components/wpa_supplicant/src/common/dpp.h b/components/wpa_supplicant/src/common/dpp.h index 4d595ceebb..e56bc642a4 100644 --- a/components/wpa_supplicant/src/common/dpp.h +++ b/components/wpa_supplicant/src/common/dpp.h @@ -13,8 +13,11 @@ #ifdef CONFIG_DPP #include "utils/list.h" -#include "common/wpa_common.h" #include "crypto/sha256.h" +#include "utils/includes.h" +#include "utils/common.h" +#include "esp_err.h" +#include "esp_dpp.h" struct crypto_ecdh; struct hostapd_ip_addr; @@ -147,12 +150,6 @@ struct dpp_curve_params { const char *jws_alg; }; -enum dpp_bootstrap_type { - DPP_BOOTSTRAP_QR_CODE, - DPP_BOOTSTRAP_PKEX, - DPP_BOOTSTRAP_NFC_URI, -}; - struct dpp_bootstrap_info { struct dl_list list; unsigned int id; @@ -258,6 +255,7 @@ struct dpp_authentication { * Authentication exchange */ unsigned int freq[DPP_BOOTSTRAP_MAX_FREQ]; unsigned int num_freq, freq_idx; + unsigned int curr_chan; unsigned int curr_freq; unsigned int neg_freq; unsigned int num_freq_iters; @@ -488,8 +486,8 @@ void dpp_auth_deinit(struct dpp_authentication *auth); struct wpabuf * dpp_conf_req_rx(struct dpp_authentication *auth, const u8 *attr_start, size_t attr_len); -int dpp_conf_resp_rx(struct dpp_authentication *auth, - const struct wpabuf *resp); +int dpp_conf_resp_rx(struct dpp_authentication *auth, const u8 *resp, + u32 resp_len); enum dpp_status_error dpp_conf_result_rx(struct dpp_authentication *auth, const u8 *hdr, const u8 *attr_start, size_t attr_len); diff --git a/components/wpa_supplicant/src/common/ieee802_11_defs.h b/components/wpa_supplicant/src/common/ieee802_11_defs.h index 742ad2be64..e0fef0b684 100644 --- a/components/wpa_supplicant/src/common/ieee802_11_defs.h +++ b/components/wpa_supplicant/src/common/ieee802_11_defs.h @@ -263,6 +263,13 @@ #define WLAN_ACTION_UNPROTECTED_WNM 11 #define WLAN_ACTION_WMM 17 /* WMM Specification 1.1 */ +/* Public action codes (IEEE Std 802.11-2016, 9.6.8.1, Table 9-307) */ +#define WLAN_PA_VENDOR_SPECIFIC 9 +#define WLAN_PA_GAS_INITIAL_REQ 10 +#define WLAN_PA_GAS_INITIAL_RESP 11 +#define WLAN_PA_GAS_COMEBACK_REQ 12 +#define WLAN_PA_GAS_COMEBACK_RESP 13 + /* SA Query Action frame (IEEE 802.11w/D8.0, 7.4.9) */ #define WLAN_SA_QUERY_REQUEST 0 #define WLAN_SA_QUERY_RESPONSE 1 @@ -274,6 +281,8 @@ #define WLAN_TIMEOUT_KEY_LIFETIME 2 #define WLAN_TIMEOUT_ASSOC_COMEBACK 3 +#define OUI_WFA 0x506f9a +#define DPP_OUI_TYPE 0x1A #ifdef _MSC_VER #pragma pack(push, 1) @@ -343,110 +352,37 @@ enum lci_req_subelem { LCI_REQ_SUBELEM_MAX_AGE = 4, }; -struct ieee80211_mgmt { - le16 frame_control; - le16 duration; - u8 da[6]; - u8 sa[6]; - u8 bssid[6]; - le16 seq_ctrl; - union { - struct { - le16 auth_alg; - le16 auth_transaction; - le16 status_code; - /* possibly followed by Challenge text */ - u8 variable[0]; - } STRUCT_PACKED auth; - struct { - le16 reason_code; - } STRUCT_PACKED deauth; - struct { - le16 capab_info; - le16 listen_interval; - /* followed by SSID and Supported rates */ - u8 variable[0]; - } STRUCT_PACKED assoc_req; - struct { - le16 capab_info; - le16 status_code; - le16 aid; - /* followed by Supported rates */ - u8 variable[0]; - } STRUCT_PACKED assoc_resp, reassoc_resp; - struct { - le16 capab_info; - le16 listen_interval; - u8 current_ap[6]; - /* followed by SSID and Supported rates */ - u8 variable[0]; - } STRUCT_PACKED reassoc_req; - struct { - le16 reason_code; - } STRUCT_PACKED disassoc; - struct { - u8 timestamp[8]; - le16 beacon_int; - le16 capab_info; - /* followed by some of SSID, Supported rates, - * FH Params, DS Params, CF Params, IBSS Params, TIM */ - u8 variable[0]; - } STRUCT_PACKED beacon; - struct { - /* only variable items: SSID, Supported rates */ - u8 variable[0]; - } STRUCT_PACKED probe_req; - struct { - u8 timestamp[8]; - le16 beacon_int; - le16 capab_info; - /* followed by some of SSID, Supported rates, - * FH Params, DS Params, CF Params, IBSS Params */ - u8 variable[0]; - } STRUCT_PACKED probe_resp; - struct { - u8 category; - union { - struct { - u8 action_code; - u8 dialog_token; - u8 status_code; - u8 variable[0]; - } STRUCT_PACKED wmm_action; - struct{ - u8 action_code; - u8 element_id; - u8 length; - u8 switch_mode; - u8 new_chan; - u8 switch_count; - } STRUCT_PACKED chan_switch; - struct { - u8 action; - u8 sta_addr[ETH_ALEN]; - u8 target_ap_addr[ETH_ALEN]; - u8 variable[0]; /* FT Request */ - } STRUCT_PACKED ft_action_req; - struct { - u8 action; - u8 sta_addr[ETH_ALEN]; - u8 target_ap_addr[ETH_ALEN]; - le16 status_code; - u8 variable[0]; /* FT Request */ - } STRUCT_PACKED ft_action_resp; - struct { - u8 action; - u8 trans_id[WLAN_SA_QUERY_TR_ID_LEN]; - } STRUCT_PACKED sa_query_req; - struct { - u8 action; /* */ - u8 trans_id[WLAN_SA_QUERY_TR_ID_LEN]; - } STRUCT_PACKED sa_query_resp; - } u; - } STRUCT_PACKED action; - } u; +#ifdef ESP_SUPPLICANT +struct ieee80211_pa_vendor { + u8 oui[3]; + u8 wfa_stype; + u8 vendor_data[]; } STRUCT_PACKED; +struct ieee80211_gas_resp { + u8 diag_token; + u16 status_code; + u16 comeback_delay; + u8 type; + u8 length; + u8 data[]; +} STRUCT_PACKED; + +struct ieee80211_public_action { + u8 action; + union { + struct ieee80211_pa_vendor pa_vendor_spec; + struct ieee80211_gas_resp pa_gas_resp; + } v; +} STRUCT_PACKED; + +struct ieee80211_action { + u8 category; + union { + struct ieee80211_public_action public_action; + } u; +} STRUCT_PACKED; +#endif /* ESP_SUPPLICANT */ #define IEEE80211_MAX_MMPDU_SIZE 2304 struct ieee80211_ht_capabilities { diff --git a/components/wpa_supplicant/src/esp_supplicant/esp_dpp.c b/components/wpa_supplicant/src/esp_supplicant/esp_dpp.c new file mode 100644 index 0000000000..1ebe8912bd --- /dev/null +++ b/components/wpa_supplicant/src/esp_supplicant/esp_dpp.c @@ -0,0 +1,648 @@ +// Copyright 2020 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 "esp_dpp_i.h" +#include "esp_dpp.h" +#include "esp_wpa.h" +#include "esp_timer.h" +#include "esp_event.h" +#include "esp_wifi.h" +#include "common/ieee802_11_defs.h" + +static void *s_dpp_task_hdl = NULL; +static void *s_dpp_evt_queue = NULL; +static void *s_dpp_api_lock = NULL; + +static bool s_dpp_stop_listening; +static int s_dpp_auth_retries; +struct esp_dpp_context_t s_dpp_ctx; +static wifi_action_rx_cb_t s_action_rx_cb = esp_supp_rx_action; + +#define DPP_API_LOCK() xSemaphoreTakeRecursive(s_dpp_api_lock, portMAX_DELAY) +#define DPP_API_UNLOCK() xSemaphoreGiveRecursive(s_dpp_api_lock) + +struct action_rx_param { + u8 sa[ETH_ALEN]; + u32 channel; + u32 frm_len; + u32 vendor_data_len; + struct ieee80211_action *action_frm; +}; + +static int esp_dpp_post_evt(uint32_t evt_id, uint32_t data) +{ + DPP_API_LOCK(); + + dpp_event_t *evt = os_zalloc(sizeof(dpp_event_t)); + if (evt == NULL) { + DPP_API_UNLOCK(); + return ESP_ERR_NO_MEM; + } + evt->id = evt_id; + evt->data = data; + if ( xQueueSend(s_dpp_evt_queue, &evt, 10 / portTICK_PERIOD_MS ) != pdPASS) { + DPP_API_UNLOCK(); + os_free(evt); + return ESP_ERR_DPP_FAILURE; + } + DPP_API_UNLOCK(); + return ESP_OK; +} + +static void esp_dpp_call_cb(esp_supp_dpp_event_t evt, void *data) +{ + s_dpp_ctx.dpp_event_cb(evt, data); +} + +void esp_send_action_frame(uint8_t *dest_mac, const uint8_t *buf, uint32_t len, + uint8_t channel, uint32_t wait_time_ms) +{ + wifi_action_tx_req_t *req = os_zalloc(sizeof(*req) + len);; + if (!req) { + return; + } + + req->ifx = ESP_IF_WIFI_STA; + memcpy(req->dest_mac, dest_mac, ETH_ALEN); + req->no_ack = false; + req->data_len = len; + req->rx_cb = s_action_rx_cb; + memcpy(req->data, buf, req->data_len); + + wpa_printf(MSG_DEBUG, "DPP: Mgmt Tx - MAC:" MACSTR ", Channel-%d, WaitT-%d", + MAC2STR(dest_mac), channel, wait_time_ms); + + if (ESP_OK != esp_wifi_action_tx_req(WIFI_OFFCHAN_TX_REQ, channel, + wait_time_ms, req)) { + wpa_printf(MSG_ERROR, "DPP: Failed to perfrm offchannel operation"); + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)ESP_ERR_DPP_TX_FAILURE); + os_free(req); + return; + } + + os_free(req); +} + +static void esp_dpp_rx_auth_req(struct action_rx_param *rx_param, uint8_t *dpp_data) +{ + size_t len = rx_param->vendor_data_len - 2; + const u8 *r_bootstrap, *i_bootstrap; + u16 r_bootstrap_len, i_bootstrap_len; + struct dpp_bootstrap_info *own_bi; + int rc; + + wpa_printf(MSG_INFO, "DPP: Authentication Request from " MACSTR, MAC2STR(rx_param->sa)); + + r_bootstrap = dpp_get_attr(dpp_data, len, DPP_ATTR_R_BOOTSTRAP_KEY_HASH, + &r_bootstrap_len); + if (!r_bootstrap || r_bootstrap_len != SHA256_MAC_LEN) { + wpa_printf(MSG_INFO, "DPP: Missing or invalid Responder Bootstrapping Key Hash attribute"); + rc = ESP_ERR_DPP_INVALID_ATTR; + goto fail; + } + wpa_hexdump(MSG_MSGDUMP, "DPP: Responder Bootstrapping Key Hash", r_bootstrap, r_bootstrap_len); + + i_bootstrap = dpp_get_attr(dpp_data, len, DPP_ATTR_I_BOOTSTRAP_KEY_HASH, + &i_bootstrap_len); + if (!i_bootstrap || i_bootstrap_len != SHA256_MAC_LEN) { + wpa_printf(MSG_INFO, "DPP: Missing or invalid Initiator Bootstrapping Key Hash attribute"); + rc = ESP_ERR_DPP_INVALID_ATTR; + goto fail; + } + wpa_hexdump(MSG_MSGDUMP, "DPP: Initiator Bootstrapping Key Hash", i_bootstrap, i_bootstrap_len); + + own_bi = dpp_bootstrap_get_id(s_dpp_ctx.dpp_global, s_dpp_ctx.id); + /* Try to find own and peer bootstrapping key matches based on the + * received hash values */ + if (os_memcmp(own_bi->pubkey_hash, r_bootstrap, SHA256_MAC_LEN)) { + wpa_printf(MSG_INFO, "DPP: No matching own bootstrapping key found as responder - ignore message"); + rc = ESP_ERR_DPP_INVALID_ATTR; + goto fail; + } + + s_dpp_ctx.dpp_auth = dpp_auth_req_rx(NULL, DPP_CAPAB_ENROLLEE, 0, NULL, + own_bi, rx_param->channel, + (const u8 *)&rx_param->action_frm->u.public_action.v, dpp_data, len); + os_memcpy(s_dpp_ctx.dpp_auth->peer_mac_addr, rx_param->sa, ETH_ALEN); + + esp_send_action_frame(rx_param->sa, wpabuf_head(s_dpp_ctx.dpp_auth->resp_msg), + wpabuf_len(s_dpp_ctx.dpp_auth->resp_msg), + rx_param->channel, OFFCHAN_TX_WAIT_TIME); + return; +fail: + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)rc); +} + +static void gas_query_req_tx(struct dpp_authentication *auth) +{ + struct wpabuf *buf; + int supp_op_classes[] = {81, 0}; + + buf = dpp_build_conf_req_helper(auth, NULL, 0, NULL, + supp_op_classes); + if (!buf) { + wpa_printf(MSG_DEBUG, "DPP: No configuration request data available"); + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)ESP_ERR_DPP_FAILURE); + return; + } + + wpa_printf(MSG_DEBUG, "DPP: GAS request to " MACSTR " (chan %u)", + MAC2STR(auth->peer_mac_addr), auth->curr_chan); + + esp_send_action_frame(auth->peer_mac_addr, wpabuf_head(buf), wpabuf_len(buf), + auth->curr_chan, OFFCHAN_TX_WAIT_TIME); +} + +static int esp_dpp_handle_config_obj(struct dpp_authentication *auth, + struct dpp_config_obj *conf) +{ + wifi_config_t *wifi_cfg = &s_dpp_ctx.wifi_cfg; + + if (conf->ssid_len) { + os_memcpy(wifi_cfg->sta.ssid, conf->ssid, conf->ssid_len); + } + + if (dpp_akm_legacy(conf->akm)) { + if (conf->passphrase[0]) + os_memcpy(wifi_cfg->sta.password, conf->passphrase, + sizeof(wifi_cfg->sta.password)); + if (conf->akm == DPP_AKM_PSK_SAE) { + wifi_cfg->sta.pmf_cfg.capable = true; + wifi_cfg->sta.pmf_cfg.required = true; + } + } + + if (conf->connector) { + /* TODO: Save the Connector and consider using a command + * to fetch the value instead of sending an event with + * it. The Connector could end up being larger than what + * most clients are ready to receive as an event + * message. */ + wpa_printf(MSG_INFO, DPP_EVENT_CONNECTOR "%s", + conf->connector); + } + s_dpp_stop_listening = false; + esp_wifi_action_tx_req(WIFI_OFFCHAN_TX_CANCEL, 0, 0, NULL); + esp_dpp_call_cb(ESP_SUPP_DPP_CFG_RECVD, wifi_cfg); + + return 0; +} + +static void esp_dpp_rx_auth_conf(struct action_rx_param *rx_param, uint8_t *dpp_data) +{ + struct dpp_authentication *auth = s_dpp_ctx.dpp_auth; + struct ieee80211_public_action *public_action = + &rx_param->action_frm->u.public_action; + size_t len = rx_param->vendor_data_len - 2; + int rc; + + wpa_printf(MSG_DEBUG, "DPP: Authentication Confirmation from " MACSTR, + MAC2STR(rx_param->sa)); + + if (!auth) { + wpa_printf(MSG_DEBUG, "DPP: No DPP Authentication in progress - drop"); + rc = ESP_ERR_DPP_FAILURE; + goto fail; + } + + if (os_memcmp(rx_param->sa, auth->peer_mac_addr, ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected " + MACSTR ") - drop", MAC2STR(auth->peer_mac_addr)); + rc = ESP_ERR_DPP_FAILURE; + goto fail; + } + + if (dpp_auth_conf_rx(auth, (const u8 *)&public_action->v, + dpp_data, len) < 0) { + wpa_printf(MSG_DEBUG, "DPP: Authentication failed"); + rc = ESP_ERR_DPP_FAILURE; + goto fail; + } + + /* Send GAS Query Req */ + gas_query_req_tx(auth); + + return; + +fail: + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)rc); +} + +static void esp_dpp_rx_auth(struct action_rx_param *rx_param) +{ + uint8_t crypto_suit, type; + uint8_t *tmp; + + tmp = rx_param->action_frm->u.public_action.v.pa_vendor_spec.vendor_data; + crypto_suit = tmp[0]; + type = tmp[1]; + + if (crypto_suit != 1) { + wpa_printf(MSG_ERROR, "DPP: Unsupported crypto suit"); + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)ESP_ERR_NOT_SUPPORTED); + return; + } + + switch (type) { + case DPP_PA_AUTHENTICATION_REQ: + esp_dpp_rx_auth_req(rx_param, &tmp[2]); + break; + case DPP_PA_AUTHENTICATION_CONF: + esp_dpp_rx_auth_conf(rx_param, &tmp[2]); + break; + } +} + +static void gas_query_resp_rx(struct action_rx_param *rx_param) +{ + struct dpp_authentication *auth = s_dpp_ctx.dpp_auth; + uint8_t *pos = rx_param->action_frm->u.public_action.v.pa_gas_resp.data; + uint8_t *resp = &pos[10]; + int i, res; + + if (pos[1] == WLAN_EID_VENDOR_SPECIFIC && pos[2] == 5 && + WPA_GET_BE24(&pos[3]) == OUI_WFA && pos[6] == 0x1a && pos[7] == 1) { + if (dpp_conf_resp_rx(auth, resp, rx_param->vendor_data_len - 2) < 0) { + wpa_printf(MSG_DEBUG, "DPP: Configuration attempt failed"); + goto fail; + } + + for (i = 0; i < auth->num_conf_obj; i++) { + res = esp_dpp_handle_config_obj(auth, &auth->conf_obj[i]); + if (res < 0) { + goto fail; + } + } + } + + return; +fail: + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)ESP_ERR_DPP_FAILURE); +} + +static void esp_dpp_rx_action(struct action_rx_param *rx_param) +{ + if (rx_param->action_frm->category == WLAN_ACTION_PUBLIC) { + struct ieee80211_public_action *public_action = + &rx_param->action_frm->u.public_action; + + wpa_printf(MSG_DEBUG, "DPP: Rx Public Action frame: action - %d", + public_action->action); + + if (public_action->action == WLAN_PA_VENDOR_SPECIFIC && + WPA_GET_BE24(public_action->v.pa_vendor_spec.oui) == OUI_WFA && + public_action->v.pa_vendor_spec.wfa_stype == DPP_OUI_TYPE) { + + rx_param->vendor_data_len = rx_param->frm_len - + (size_t)(public_action->v.pa_vendor_spec.vendor_data - + (u8 *)rx_param->action_frm); + + if (!s_dpp_stop_listening) { + esp_supp_dpp_stop_listen(); + } + + esp_dpp_rx_auth(rx_param); + } else if (public_action->action == WLAN_PA_GAS_INITIAL_RESP && + public_action->v.pa_gas_resp.type == WLAN_EID_ADV_PROTO && + public_action->v.pa_gas_resp.length == 8 && + public_action->v.pa_gas_resp.status_code == 0) { + + rx_param->vendor_data_len = rx_param->frm_len - + (size_t)(public_action->v.pa_gas_resp.data + + public_action->v.pa_gas_resp.length - + (u8 *)rx_param->action_frm); + + gas_query_resp_rx(rx_param); + } + } + + os_free(rx_param->action_frm); + os_free(rx_param); +} + +static void esp_dpp_task(void *pvParameters ) +{ + dpp_event_t *evt; + bool task_del = false; + + for (;;) { + if (xQueueReceive(s_dpp_evt_queue, &evt, portMAX_DELAY) == pdTRUE) { + if (evt->id < SIG_DPP_MAX) { + DPP_API_LOCK(); + } else { + os_free(evt); + continue; + } + + switch (evt->id) { + case SIG_DPP_DEL_TASK: + task_del = true; + break; + + case SIG_DPP_BOOTSTRAP_GEN: { + char *command = (char *)evt->data; + const char *uri; + + s_dpp_ctx.id = dpp_bootstrap_gen(s_dpp_ctx.dpp_global, command); + uri = dpp_bootstrap_get_uri(s_dpp_ctx.dpp_global, s_dpp_ctx.id); + + esp_dpp_call_cb(ESP_SUPP_DPP_URI_READY, (void *)uri); + os_free(command); + } + break; + + case SIG_DPP_RX_ACTION: { + esp_dpp_rx_action((struct action_rx_param *)evt->data); + } + break; + + case SIG_DPP_LISTEN_NEXT_CHANNEL: { + struct dpp_bootstrap_params_t *p = &s_dpp_ctx.bootstrap_params; + static int counter; + int channel; + + channel = p->chan_list[counter++ % p->num_chan]; + esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, WIFI_ROC_REQ, channel, + BOOTSTRAP_ROC_WAIT_TIME, s_action_rx_cb); + } + break; + + default: + break; + } + + os_free(evt); + DPP_API_UNLOCK(); + + if (task_del) { + break; + } + } + } + + vQueueDelete(s_dpp_evt_queue); + s_dpp_evt_queue = NULL; + + if (s_dpp_api_lock) { + vSemaphoreDelete(s_dpp_api_lock); + s_dpp_api_lock = NULL; + } + + /* At this point, we completed */ + vTaskDelete(NULL); +} + +int esp_supp_rx_action(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel) +{ + struct ieee80211_hdr *rx_hdr = (struct ieee80211_hdr *)hdr; + struct action_rx_param *rx_param; + + if (WLAN_FC_GET_STYPE(rx_hdr->frame_control) == WLAN_FC_STYPE_ACTION) { + rx_param = os_zalloc(sizeof(struct action_rx_param)); + os_memcpy(rx_param->sa, rx_hdr->addr2, ETH_ALEN); + rx_param->channel = channel; + rx_param->action_frm = os_zalloc(len); + rx_param->frm_len = len; + os_memcpy(rx_param->action_frm, payload, len); + + if (ESP_OK != esp_dpp_post_evt(SIG_DPP_RX_ACTION, (u32)rx_param)) { + os_free(rx_param->action_frm); + os_free(rx_param); + } + } + + return ESP_ERR_NOT_SUPPORTED; +} + +static void offchan_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_id == WIFI_EVENT_ACTION_TX_STATUS) { + wifi_event_action_tx_status_t *evt = + (wifi_event_action_tx_status_t *)event_data; + wpa_printf(MSG_DEBUG, "Mgmt Tx Status - %d, Cookie - 0x%x", + evt->status, (uint32_t)evt->context); + + if (evt->status) { + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)ESP_ERR_DPP_TX_FAILURE); + } + + } else if (event_id == WIFI_EVENT_ROC_DONE) { + wifi_event_roc_done_t *evt = (wifi_event_roc_done_t *)event_data; + + if (!s_dpp_stop_listening && evt->context == (uint32_t)s_action_rx_cb) { + esp_dpp_post_evt(SIG_DPP_LISTEN_NEXT_CHANNEL, 0); + } + } +} + +static char *esp_dpp_parse_chan_list(const char *chan_list) +{ + struct dpp_bootstrap_params_t *params = &s_dpp_ctx.bootstrap_params; + char *uri_channels = os_zalloc(14 * 6 + 1); + const char *pos = chan_list; + const char *pos2; + char *pos3 = uri_channels; + params->num_chan = 0; + + os_memcpy(pos3, " chan=", strlen(" chan=")); + pos3 += strlen(" chan="); + + while (pos && *pos) { + int channel; + int len = strlen(chan_list); + + pos2 = pos; + while (*pos2 >= '0' && *pos2 <= '9') { + pos2++; + } + if (*pos2 == ',' || *pos2 == ' ' || *pos2 == '\0') { + channel = atoi(pos); + if (channel < 1 || channel > 14) { + os_free(uri_channels); + return NULL; + } + params->chan_list[params->num_chan++] = channel; + os_memcpy(pos3, "81/", strlen("81/")); + pos3 += strlen("81/"); + os_memcpy(pos3, pos, (pos2 - pos)); + pos3 += (pos2 - pos); + *pos3++ = ','; + + pos = pos2 + 1; + } + while (*pos == ',' || *pos == ' ' || *pos == '\0') { + pos++; + } + + if (((int)(pos - chan_list) >= len)) { + break; + } + } + *(pos3 - 1) = ' '; + + return uri_channels; +} + +esp_err_t +esp_supp_dpp_bootstrap_gen(const char *chan_list, enum dpp_bootstrap_type type, + const char *key, const char *uri_info) +{ + struct dpp_bootstrap_params_t *params = &s_dpp_ctx.bootstrap_params; + char *uri_chan_list = esp_dpp_parse_chan_list(chan_list); + char *command = os_zalloc(1200); + int ret; + + if (!uri_chan_list || !command || params->num_chan >= 14 || params->num_chan == 0) { + wpa_printf(MSG_ERROR, "Invalid Channel list - %s", chan_list); + if (command) { + os_free(command); + } + ret = ESP_ERR_DPP_FAILURE; + goto fail; + } + + if (type != DPP_BOOTSTRAP_QR_CODE) { + wpa_printf(MSG_INFO, "Bootstrap type %d not supported", type); + os_free(command); + ret = ESP_ERR_NOT_SUPPORTED; + goto fail; + } + params->type = type; + esp_wifi_get_mac(ESP_IF_WIFI_STA, params->mac); + + if (uri_info) { + params->info_len = strlen(uri_info); + if (params->info_len) { + params->info = os_zalloc(params->info_len + 1); + if (!params->info) { + os_free(command); + ret = ESP_ERR_NO_MEM; + goto fail; + } + os_memcpy(params->info, uri_info, params->info_len); + } + } + + if (key) { + params->key_len = strlen(key); + if (params->key_len) { + char prefix[] = "30310201010420"; + char postfix[] = "a00a06082a8648ce3d030107"; + + params->key = os_zalloc(params->key_len + + sizeof(prefix) + sizeof(postfix)); + if (!params->key) { + os_free(command); + ret = ESP_ERR_NO_MEM; + goto fail; + } + sprintf(params->key, "%s%s%s", prefix, key, postfix); + } + } + + sprintf(command, "type=qrcode mac=" MACSTR "%s%s%s%s%s", + MAC2STR(params->mac), uri_chan_list, + params->key_len ? "key=" : "", + params->key_len ? params->key : "", + params->info_len ? " info=" : "", + params->info_len ? params->info : ""); + + ret = esp_dpp_post_evt(SIG_DPP_BOOTSTRAP_GEN, (u32)command); + if (ret != ESP_OK) { + os_free(command); + if (params->info) { + os_free(params->info); + params->info = NULL; + } + if (params->key) { + os_free(params->key); + params->key = NULL; + } + goto fail; + } + + ret = ESP_OK; +fail: + if (uri_chan_list) { + os_free(uri_chan_list); + } + + return ret; +} + +esp_err_t esp_supp_dpp_start_listen(void) +{ + if (esp_wifi_get_user_init_flag_internal() == 0) { + wpa_printf(MSG_ERROR, "DPP: ROC not possible before wifi is started"); + return ESP_ERR_INVALID_STATE; + } + + return esp_dpp_post_evt(SIG_DPP_LISTEN_NEXT_CHANNEL, 0); +} + +void esp_supp_dpp_stop_listen(void) +{ + s_dpp_stop_listening = true; + esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, WIFI_ROC_CANCEL, 0, 0, NULL); +} + +esp_err_t esp_supp_dpp_init(esp_supp_dpp_event_cb_t cb) +{ + struct dpp_global_config cfg = {0}; + + os_bzero(&s_dpp_ctx, sizeof(s_dpp_ctx)); + s_dpp_ctx.dpp_event_cb = cb; + + cfg.cb_ctx = &s_dpp_ctx; + cfg.msg_ctx = &s_dpp_ctx; + s_dpp_ctx.dpp_global = dpp_global_init(&cfg); + + s_dpp_stop_listening = false; + s_dpp_evt_queue = xQueueCreate(3, sizeof(dpp_event_t)); + xTaskCreate(esp_dpp_task, "dppT", DPP_TASK_STACK_SIZE, NULL, 2, s_dpp_task_hdl); + + s_dpp_api_lock = xSemaphoreCreateRecursiveMutex(); + if (!s_dpp_api_lock) { + wpa_printf(MSG_ERROR, "DPP: dpp_init: failed to create DPP API lock"); + return ESP_ERR_NO_MEM; + } + + esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_ACTION_TX_STATUS, + &offchan_event_handler, NULL); + esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_ROC_DONE, + &offchan_event_handler, NULL); + + wpa_printf(MSG_INFO, "esp_dpp_task prio:%d, stack:%d\n", 2, DPP_TASK_STACK_SIZE); + + return ESP_OK; +} + +void esp_supp_dpp_deinit(void) +{ + struct dpp_bootstrap_params_t *params = &s_dpp_ctx.bootstrap_params; + + if (params->info) { + os_free(params->info); + params->info = NULL; + } + if (params->key) { + os_free(params->key); + params->key = NULL; + } + + s_dpp_auth_retries = 0; + dpp_global_deinit(s_dpp_ctx.dpp_global); + esp_dpp_post_evt(SIG_DPP_DEL_TASK, 0); +} diff --git a/components/wpa_supplicant/src/esp_supplicant/esp_dpp_i.h b/components/wpa_supplicant/src/esp_supplicant/esp_dpp_i.h new file mode 100644 index 0000000000..3c7aea750a --- /dev/null +++ b/components/wpa_supplicant/src/esp_supplicant/esp_dpp_i.h @@ -0,0 +1,68 @@ +// Copyright 2020 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 ESP_DPP_I_H +#define ESP_DPP_I_H + +#include "esp_err.h" +#include "utils/includes.h" +#include "utils/common.h" + +#include "common/dpp.h" +#include "esp_dpp.h" +#include "esp_wifi_driver.h" + +#define DPP_TASK_STACK_SIZE (6144 + TASK_STACK_SIZE_ADD) + +enum SIG_DPP { + SIG_DPP_RESET = 0, + SIG_DPP_BOOTSTRAP_GEN, + SIG_DPP_RX_ACTION, + SIG_DPP_LISTEN_NEXT_CHANNEL, + SIG_DPP_DEL_TASK, + SIG_DPP_MAX, +}; + +typedef struct { + uint32_t id; + uint32_t data; +} dpp_event_t; + +#define BOOTSTRAP_ROC_WAIT_TIME 500 +#define OFFCHAN_TX_WAIT_TIME 500 + +struct dpp_bootstrap_params_t { + enum dpp_bootstrap_type type; + uint8_t chan_list[14]; + uint8_t num_chan; + uint8_t mac[6]; + uint32_t key_len; + char *key; + uint32_t info_len; + char *info; +}; + +struct esp_dpp_context_t { + struct dpp_bootstrap_params_t bootstrap_params; + struct dpp_authentication *dpp_auth; + int gas_dialog_token; + esp_supp_dpp_event_cb_t dpp_event_cb; + struct dpp_global *dpp_global; + wifi_config_t wifi_cfg; + int id; +}; + +int esp_supp_rx_action(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel); + +#endif /* ESP_DPP_I_H */ diff --git a/components/wpa_supplicant/src/esp_supplicant/esp_wifi_driver.h b/components/wpa_supplicant/src/esp_supplicant/esp_wifi_driver.h index 685976b157..86e951e5f7 100644 --- a/components/wpa_supplicant/src/esp_supplicant/esp_wifi_driver.h +++ b/components/wpa_supplicant/src/esp_supplicant/esp_wifi_driver.h @@ -261,5 +261,9 @@ bool esp_wifi_is_btm_enabled_internal(uint8_t if_index); esp_err_t esp_wifi_register_mgmt_frame_internal(uint32_t type, uint32_t subtype); esp_err_t esp_wifi_send_mgmt_frm_internal(const wifi_mgmt_frm_req_t *req); uint8_t esp_wifi_ap_get_prof_pairwise_cipher_internal(void); +esp_err_t esp_wifi_action_tx_req(uint8_t type, uint8_t channel, + uint32_t wait_time_ms, const wifi_action_tx_req_t *req); +esp_err_t esp_wifi_remain_on_channel(uint8_t ifx, uint8_t type, uint8_t channel, + uint32_t wait_time_ms, wifi_action_rx_cb_t rx_cb); #endif /* _ESP_WIFI_DRIVER_H_ */ diff --git a/components/wpa_supplicant/test/test_offchannel.c b/components/wpa_supplicant/test/test_offchannel.c new file mode 100644 index 0000000000..97e1e8e828 --- /dev/null +++ b/components/wpa_supplicant/test/test_offchannel.c @@ -0,0 +1,245 @@ +// Copyright 2020 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 "string.h" +#include "esp_system.h" +#include "unity.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_wifi_types.h" +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "../src/esp_supplicant/esp_wifi_driver.h" +#include "esp_log.h" +#include "test_utils.h" +#include "freertos/event_groups.h" + +#define WIFI_START_EVENT 0x00000001 +#define WIFI_ROC_DONE_EVENT 0x00000002 +#define WIFI_ACTION_RX_EVENT 0x00000003 +#define WIFI_SCAN_DONE_EVENT 0x00000004 + +#define TEST_LISTEN_CHANNEL 6 + +#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32C3) + +static const char *TAG = "test_offchan"; +esp_netif_t *wifi_netif; +static EventGroupHandle_t wifi_event; + +static void wifi_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + switch (event_id) { + case WIFI_EVENT_STA_START: + ESP_LOGI(TAG, "WIFI Started"); + xEventGroupSetBits(wifi_event, WIFI_START_EVENT); + break; + case WIFI_EVENT_ACTION_TX_STATUS: { + wifi_event_action_tx_status_t *evt = + (wifi_event_action_tx_status_t *)event_data; + + if (evt->status == 0) { + ESP_LOGI(TAG, "Action Tx Successful"); + } + } + break; + case WIFI_EVENT_ROC_DONE: + ESP_LOGI(TAG, "ROC Done"); + xEventGroupSetBits(wifi_event, WIFI_ROC_DONE_EVENT); + break; + case WIFI_EVENT_SCAN_DONE: + ESP_LOGI(TAG, "Scan Done"); + xEventGroupSetBits(wifi_event, WIFI_SCAN_DONE_EVENT); + break; + default: + break; + } + return; +} + +static esp_err_t event_init(void) +{ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); + wifi_netif = esp_netif_create_default_wifi_sta(); + + return ESP_OK; +} + +static void start_wifi_as_sta(void) +{ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + cfg.nvs_enable = false; + + event_init(); + + // can't deinit event loop, need to reset leak check + unity_reset_leak_checks(); + + if (wifi_event == NULL) { + wifi_event = xEventGroupCreate(); + } else { + xEventGroupClearBits(wifi_event, 0x00ffffff); + } + + TEST_ESP_OK(esp_wifi_init(&cfg)); + TEST_ESP_OK(esp_wifi_set_mode(WIFI_MODE_STA)); + TEST_ESP_OK(esp_wifi_start()); + +} + +static void stop_wifi(void) +{ + esp_event_loop_delete_default(); + ESP_LOGI(TAG, "Stop wifi\n"); + TEST_ESP_OK(esp_wifi_stop()); + TEST_ESP_OK(esp_wifi_deinit()); + esp_wifi_clear_default_wifi_driver_and_handlers(wifi_netif); + esp_netif_destroy(wifi_netif); + if (wifi_event) { + vEventGroupDelete(wifi_event); + wifi_event = NULL; + } + vTaskDelay(1000 / portTICK_PERIOD_MS); +} + +int dummy_rx_action(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel) +{ + return ESP_OK; +} + +static const char *frame_data = "This is a test data"; + +void esp_send_action_frame(uint8_t *dest_mac, const uint8_t *buf, uint32_t len, + uint8_t channel, uint32_t wait_time_ms) +{ + wifi_action_tx_req_t *req = os_zalloc(sizeof(*req) + len);; + TEST_ASSERT( req != NULL); + + req->ifx = ESP_IF_WIFI_STA; + memcpy(req->dest_mac, dest_mac, ETH_ALEN); + req->no_ack = false; + req->data_len = len; + req->rx_cb = dummy_rx_action; + memcpy(req->data, buf, req->data_len); + + ESP_LOGI(TAG, "Action Tx - MAC:" MACSTR ", Channel-%d, WaitT-%d", + MAC2STR(dest_mac), channel, wait_time_ms); + + TEST_ESP_OK(esp_wifi_action_tx_req(WIFI_OFFCHAN_TX_REQ, channel, wait_time_ms, req)); + + os_free(req); +} + + +/* Test that foreground Scan doesn't pre-empt ROC & vice versa */ +TEST_CASE("Test scan and ROC simultaneously", "[Offchan]") +{ + wifi_action_rx_cb_t rx_cb = dummy_rx_action; + EventBits_t bits; + + test_case_uses_tcpip(); + start_wifi_as_sta(); + + xEventGroupWaitBits(wifi_event, WIFI_START_EVENT, 1, 0, 5000 / portTICK_RATE_MS); + + TEST_ESP_OK(esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, WIFI_ROC_REQ, TEST_LISTEN_CHANNEL, + 100, rx_cb)); + ESP_ERROR_CHECK(esp_wifi_scan_start(NULL, false)); + bits = xEventGroupWaitBits(wifi_event, WIFI_ROC_DONE_EVENT | WIFI_SCAN_DONE_EVENT, + pdTRUE, pdFALSE, 5000 / portTICK_RATE_MS); + TEST_ASSERT_TRUE(bits == WIFI_ROC_DONE_EVENT); + + vTaskDelay(1000 / portTICK_PERIOD_MS); + ESP_ERROR_CHECK(esp_wifi_scan_start(NULL, false)); + TEST_ESP_OK(esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, WIFI_ROC_REQ, TEST_LISTEN_CHANNEL, + 100, rx_cb)); + bits = xEventGroupWaitBits(wifi_event, WIFI_ROC_DONE_EVENT | WIFI_SCAN_DONE_EVENT, + pdTRUE, pdFALSE, 5000 / portTICK_RATE_MS); + TEST_ASSERT_TRUE(bits == WIFI_SCAN_DONE_EVENT); + + stop_wifi(); +} + +static void test_wifi_offchan_tx(void) +{ + int i; + char mac_str[19]; + uint8_t mac[6]; + + test_case_uses_tcpip(); + start_wifi_as_sta(); + xEventGroupWaitBits(wifi_event, WIFI_START_EVENT, 1, 0, 5000 / portTICK_RATE_MS); + + unity_wait_for_signal_param("Listener mac", mac_str, 19); + + TEST_ASSERT_TRUE(unity_util_convert_mac_from_string(mac_str, mac)); + + for (i = 0; i < 3; i++) { + esp_send_action_frame(mac, (const uint8_t *)frame_data, strlen(frame_data), + TEST_LISTEN_CHANNEL, 500); + vTaskDelay(500 / portTICK_PERIOD_MS); + } + + stop_wifi(); +} + +static int test_rx_action(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel) +{ + struct ieee80211_hdr *rx_hdr = (struct ieee80211_hdr *)hdr; + + ESP_LOGI(TAG, "Rxd Action Frame from " MACSTR " (Seq-%lu)", MAC2STR(rx_hdr->addr2), + WLAN_GET_SEQ_SEQ(rx_hdr->seq_ctrl)); + + if (!os_memcmp(payload, frame_data, strlen(frame_data))) { + xEventGroupSetBits(wifi_event, WIFI_ACTION_RX_EVENT); + } + + return ESP_OK; +} + +static void test_wifi_roc(void) +{ + wifi_action_rx_cb_t rx_cb = test_rx_action; + char mac_str[19] = {0}; + EventBits_t bits; + uint8_t mac[6]; + + test_case_uses_tcpip(); + start_wifi_as_sta(); + + xEventGroupWaitBits(wifi_event, WIFI_START_EVENT, 1, 0, 5000 / portTICK_RATE_MS); + TEST_ESP_OK(esp_wifi_get_mac(ESP_IF_WIFI_STA, mac)); + sprintf(mac_str, MACSTR, MAC2STR(mac)); + unity_send_signal_param("Listener mac", mac_str); + + TEST_ESP_OK(esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, WIFI_ROC_REQ, TEST_LISTEN_CHANNEL, + 10000, rx_cb)); + bits = xEventGroupWaitBits(wifi_event, WIFI_ROC_DONE_EVENT | WIFI_ACTION_RX_EVENT, + pdTRUE, pdFALSE, portMAX_DELAY); + /* Confirm that Frame has been received successfully */ + if (bits == WIFI_ACTION_RX_EVENT) { + TEST_ESP_OK(esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, WIFI_ROC_CANCEL, 0, 0, NULL)); + vTaskDelay(1000 / portTICK_PERIOD_MS); + stop_wifi(); + } else { + stop_wifi(); + TEST_FAIL(); + } +} + +TEST_CASE_MULTIPLE_DEVICES("test ROC and Offchannel Action Frame Tx", "[Offchan][test_env=UT_T2_1][timeout=90]", test_wifi_roc, test_wifi_offchan_tx); + +#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32C3) diff --git a/docs/en/COPYRIGHT.rst b/docs/en/COPYRIGHT.rst index 0a8565f2cd..47f43daed7 100644 --- a/docs/en/COPYRIGHT.rst +++ b/docs/en/COPYRIGHT.rst @@ -67,6 +67,8 @@ These third party libraries can be included into the application (firmware) prod * :component_file:` TLSF allocator ` Two Level Segregated Fit memory allocator, Copyright (c) 2006-2016, Matthew Conte, and licensed under the BSD license. +* `qrcode`_ QR Code generator library Copyright (c) Project Nayuki, is licensed under MIT license. + Build Tools ----------- @@ -179,3 +181,4 @@ Copyright (C) 2011, ChaN, all right reserved. .. _sphinx_idf_theme: https://github.com/espressif/sphinx_idf_theme .. _sphinx_rtd_theme: https://github.com/readthedocs/sphinx_rtd_theme .. _cryptoauthlib: https://github.com/MicrochipTech/cryptoauthlib +.. _qrcode: https://github.com/nayuki/QR-Code-generator diff --git a/examples/common_components/qrcode/CMakeLists.txt b/examples/common_components/qrcode/CMakeLists.txt new file mode 100644 index 0000000000..fa3bd8e418 --- /dev/null +++ b/examples/common_components/qrcode/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "esp_qrcode_main.c" "esp_qrcode_wrapper.c" "qrcodegen.c" + INCLUDE_DIRS "include" + ) diff --git a/examples/common_components/qrcode/README.md b/examples/common_components/qrcode/README.md new file mode 100644 index 0000000000..16bb27c06b --- /dev/null +++ b/examples/common_components/qrcode/README.md @@ -0,0 +1,9 @@ +# QR Code generator component + +This directory contains a QR code generator component written in C. This component is based on [QR-Code-generator](https://github.com/nayuki/QR-Code-generator). +This component is used as part of the following ESP-IDF examples: +- [DPP Enrollee Example](../../wifi/wifi_easy_connect/dpp-enrollee/). + +To learn more about how to use this component, please check API Documentation from header file [qrcode.h](./include/qrcode.h). + +Please note that this component is not considered to be a part of ESP-IDF stable API. It may change and may be removed in the future releases. diff --git a/examples/common_components/qrcode/component.mk b/examples/common_components/qrcode/component.mk new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/common_components/qrcode/esp_qrcode_main.c b/examples/common_components/qrcode/esp_qrcode_main.c new file mode 100644 index 0000000000..7b03d5d4f4 --- /dev/null +++ b/examples/common_components/qrcode/esp_qrcode_main.c @@ -0,0 +1,121 @@ +// Copyright 2020 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 "esp_log.h" + +#include "qrcodegen.h" +#include "qrcode.h" + +static const char *TAG = "QRCODE"; + +static const char *lt[] = { + /* 0 */ " ", + /* 1 */ "\u2580 ", + /* 2 */ " \u2580", + /* 3 */ "\u2580\u2580", + /* 4 */ "\u2584 ", + /* 5 */ "\u2588 ", + /* 6 */ "\u2584\u2580", + /* 7 */ "\u2588\u2580", + /* 8 */ " \u2584", + /* 9 */ "\u2580\u2584", + /* 10 */ " \u2588", + /* 11 */ "\u2580\u2588", + /* 12 */ "\u2584\u2584", + /* 13 */ "\u2588\u2584", + /* 14 */ "\u2584\u2588", + /* 15 */ "\u2588\u2588", +}; + +void esp_qrcode_print_console(esp_qrcode_handle_t qrcode) +{ + int size = qrcodegen_getSize(qrcode); + int border = 2; + unsigned char num = 0; + + for (int y = -border; y < size + border; y+=2) { + for (int x = -border; x < size + border; x+=2) { + num = 0; + if (qrcodegen_getModule(qrcode, x, y)) { + num |= 1 << 0; + } + if ((x < size + border) && qrcodegen_getModule(qrcode, x+1, y)) { + num |= 1 << 1; + } + if ((y < size + border) && qrcodegen_getModule(qrcode, x, y+1)) { + num |= 1 << 2; + } + if ((x < size + border) && (y < size + border) && qrcodegen_getModule(qrcode, x+1, y+1)) { + num |= 1 << 3; + } + printf("%s", lt[num]); + } + printf("\n"); + } + printf("\n"); +} + +esp_err_t esp_qrcode_generate(esp_qrcode_config_t *cfg, const char *text) +{ + enum qrcodegen_Ecc ecc_lvl; + uint8_t *qrcode, *tempbuf; + esp_err_t err = ESP_FAIL; + + qrcode = calloc(1, qrcodegen_BUFFER_LEN_FOR_VERSION(cfg->max_qrcode_version)); + if (!qrcode) { + return ESP_ERR_NO_MEM; + } + + tempbuf = calloc(1, qrcodegen_BUFFER_LEN_FOR_VERSION(cfg->max_qrcode_version)); + if (!tempbuf) { + free(qrcode); + return ESP_ERR_NO_MEM; + } + + switch(cfg->qrcode_ecc_level) { + case ESP_QRCODE_ECC_LOW: + ecc_lvl = qrcodegen_Ecc_LOW; + break; + case ESP_QRCODE_ECC_MED: + ecc_lvl = qrcodegen_Ecc_MEDIUM; + break; + case ESP_QRCODE_ECC_QUART: + ecc_lvl = qrcodegen_Ecc_QUARTILE; + break; + case ESP_QRCODE_ECC_HIGH: + ecc_lvl = qrcodegen_Ecc_HIGH; + break; + default: + ecc_lvl = qrcodegen_Ecc_LOW; + break; + } + + ESP_LOGI(TAG, "Encoding below text with ECC LVL %d & QR Code Version %d", + ecc_lvl, cfg->max_qrcode_version); + ESP_LOGI(TAG, "%s", text); + // Make and print the QR Code symbol + bool ok = qrcodegen_encodeText(text, tempbuf, qrcode, ecc_lvl, + qrcodegen_VERSION_MIN, cfg->max_qrcode_version, + qrcodegen_Mask_AUTO, true); + if (ok && cfg->display_func) { + cfg->display_func((esp_qrcode_handle_t)qrcode); + err = ESP_OK; + } + + free(qrcode); + free(tempbuf); + return err; +} diff --git a/examples/common_components/qrcode/esp_qrcode_wrapper.c b/examples/common_components/qrcode/esp_qrcode_wrapper.c new file mode 100644 index 0000000000..cde516586b --- /dev/null +++ b/examples/common_components/qrcode/esp_qrcode_wrapper.c @@ -0,0 +1,29 @@ +// Copyright 2020 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 "qrcodegen.h" +#include "qrcode.h" + +int esp_qrcode_get_size(esp_qrcode_handle_t qrcode) +{ + return qrcodegen_getSize(qrcode); +} + +bool esp_qrcode_get_module(esp_qrcode_handle_t qrcode, int x, int y) +{ + return qrcodegen_getModule(qrcode, x, y); +} diff --git a/examples/common_components/qrcode/include/qrcode.h b/examples/common_components/qrcode/include/qrcode.h new file mode 100644 index 0000000000..75002c3e0e --- /dev/null +++ b/examples/common_components/qrcode/include/qrcode.h @@ -0,0 +1,104 @@ +// Copyright 2020 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 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief QR Code handle used by the display function + */ +typedef const uint8_t * esp_qrcode_handle_t; + +/** + * @brief QR Code configuration options + */ +typedef struct { + void (*display_func)(esp_qrcode_handle_t qrcode); /**< Function called for displaying the QR Code after encoding is complete */ + int max_qrcode_version; /**< Max QR Code Version to be used. Range: 2 - 40 */ + int qrcode_ecc_level; /**< Error Correction Level for QR Code */ +} esp_qrcode_config_t; + +/** + * @brief Error Correction Level in a QR Code Symbol + */ +enum { + ESP_QRCODE_ECC_LOW, /**< QR Code Error Tolerance of 7% */ + ESP_QRCODE_ECC_MED, /**< QR Code Error Tolerance of 15% */ + ESP_QRCODE_ECC_QUART, /**< QR Code Error Tolerance of 25% */ + ESP_QRCODE_ECC_HIGH /**< QR Code Error Tolerance of 30% */ +}; + +/** + * @brief Encodes the given string into a QR Code and calls the display function + * + * @attention 1. Can successfully encode a UTF-8 string of up to 2953 bytes or an alphanumeric + * string of up to 4296 characters or any digit string of up to 7089 characters + * + * @param cfg Configuration used for QR Code encoding. + * @param text String to encode into a QR Code. + * + * @return + * - ESP_OK: succeed + * - ESP_FAIL: Failed to encode string into a QR Code + * - ESP_ERR_NO_MEM: Failed to allocate buffer for given max_qrcode_version + */ +esp_err_t esp_qrcode_generate(esp_qrcode_config_t *cfg, const char *text); + +/** + * @brief Displays QR Code on the console + * + * @param qrcode QR Code handle used by the display function. + */ +void esp_qrcode_print_console(esp_qrcode_handle_t qrcode); + +/** + * @brief Returns the side length of the given QR Code + * + * @param qrcode QR Code handle used by the display function. + * + * @return + * - val[21, 177]: Side length of QR Code + */ +int esp_qrcode_get_size(esp_qrcode_handle_t qrcode); + +/** + * @brief Returns the Pixel value for the given coordinates + * False indicates White and True indicates Black + * + * @attention 1. Coordinates for top left corner are (x=0, y=0) + * @attention 2. For out of bound coordinates false (White) is returned + * + * @param qrcode QR Code handle used by the display function. + * @param x X-Coordinate of QR Code module + * @param y Y-Coordinate of QR Code module + * + * @return + * - true: (x, y) Pixel is Black + * - false: (x, y) Pixel is White + */ +bool esp_qrcode_get_module(esp_qrcode_handle_t qrcode, int x, int y); + +#define ESP_QRCODE_CONFIG_DEFAULT() (esp_qrcode_config_t) { \ + .display_func = esp_qrcode_print_console, \ + .max_qrcode_version = 10, \ + .qrcode_ecc_level = ESP_QRCODE_ECC_LOW, \ +} + +#ifdef __cplusplus +} +#endif diff --git a/examples/common_components/qrcode/qrcodegen.c b/examples/common_components/qrcode/qrcodegen.c new file mode 100644 index 0000000000..9fce6b0727 --- /dev/null +++ b/examples/common_components/qrcode/qrcodegen.c @@ -0,0 +1,1022 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#include +#include +#include +#include +#include "qrcodegen.h" + +#ifndef QRCODEGEN_TEST + #define testable static // Keep functions private +#else + #define testable // Expose private functions +#endif + + +/*---- Forward declarations for private functions ----*/ + +// Regarding all public and private functions defined in this source file: +// - They require all pointer/array arguments to be not null unless the array length is zero. +// - They only read input scalar/array arguments, write to output pointer/array +// arguments, and return scalar values; they are "pure" functions. +// - They don't read mutable global variables or write to any global variables. +// - They don't perform I/O, read the clock, print to console, etc. +// - They allocate a small and constant amount of stack memory. +// - They don't allocate or free any memory on the heap. +// - They don't recurse or mutually recurse. All the code +// could be inlined into the top-level public functions. +// - They run in at most quadratic time with respect to input arguments. +// Most functions run in linear time, and some in constant time. +// There are no unbounded loops or non-obvious termination conditions. +// - They are completely thread-safe if the caller does not give the +// same writable buffer to concurrent calls to these functions. + +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen); + +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]); +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); +testable int getNumRawDataModules(int ver); + +testable void reedSolomonComputeDivisor(int degree, uint8_t result[]); +testable void reedSolomonComputeRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]); +testable uint8_t reedSolomonMultiply(uint8_t x, uint8_t y); + +testable void initializeFunctionModules(int version, uint8_t qrcode[]); +static void drawWhiteFunctionModules(uint8_t qrcode[], int version); +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]); +testable int getAlignmentPatternPositions(int version, uint8_t result[7]); +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]); + +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]); +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask); +static long getPenaltyScore(const uint8_t qrcode[]); +static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize); +static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize); +static void finderPenaltyAddHistory(int currentRunLength, int runHistory[7]); + +testable bool getModule(const uint8_t qrcode[], int x, int y); +testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack); +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack); +static bool getBit(int x, int i); + +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars); +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version); +static int numCharCountBits(enum qrcodegen_Mode mode, int version); + + + +/*---- Private tables of constants ----*/ + +// The set of all legal characters in alphanumeric mode, where each character +// value maps to the index in the string. For checking text and encoding segments. +static const char *ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + +// For generating error correction codes. +testable const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High +}; + +#define qrcodegen_REED_SOLOMON_DEGREE_MAX 30 // Based on the table above + +// For generating error correction codes. +testable const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High +}; + +// For automatic mask pattern selection. +static const int PENALTY_N1 = 3; +static const int PENALTY_N2 = 3; +static const int PENALTY_N3 = 40; +static const int PENALTY_N4 = 10; + + + +/*---- High-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + size_t textLen = strlen(text); + if (textLen == 0) + return qrcodegen_encodeSegmentsAdvanced(NULL, 0, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + size_t bufLen = (size_t)qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion); + + struct qrcodegen_Segment seg; + if (qrcodegen_isNumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeNumeric(text, tempBuffer); + } else if (qrcodegen_isAlphanumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ALPHANUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeAlphanumeric(text, tempBuffer); + } else { + if (textLen > bufLen) + goto fail; + for (size_t i = 0; i < textLen; i++) + tempBuffer[i] = (uint8_t)text[i]; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, textLen); + if (seg.bitLength == -1) + goto fail; + seg.numChars = (int)textLen; + seg.data = tempBuffer; + } + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + +fail: + qrcode[0] = 0; // Set size to invalid value for safety + return false; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + struct qrcodegen_Segment seg; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, dataLen); + if (seg.bitLength == -1) { + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + seg.numChars = (int)dataLen; + seg.data = dataAndTemp; + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, dataAndTemp, qrcode); +} + + +// Appends the given number of low-order bits of the given value to the given byte-based +// bit buffer, increasing the bit length. Requires 0 <= numBits <= 16 and val < 2^numBits. +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen) { + assert(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0); + for (int i = numBits - 1; i >= 0; i--, (*bitLen)++) + buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7)); +} + + + +/*---- Low-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]) { + return qrcodegen_encodeSegmentsAdvanced(segs, len, ecl, + qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true, tempBuffer, qrcode); +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]) { + assert(segs != NULL || len == 0); + assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX); + assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = getTotalBits(segs, len, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + } + assert(dataUsedBits != -1); + + // Increase the error correction level while the data still fits in the current version number + for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) { // From low to high + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8) + ecl = (enum qrcodegen_Ecc)i; + } + + // Concatenate all segments to create the data bit string + memset(qrcode, 0, (size_t)qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0])); + int bitLen = 0; + for (size_t i = 0; i < len; i++) { + const struct qrcodegen_Segment *seg = &segs[i]; + appendBitsToBuffer((unsigned int)seg->mode, 4, qrcode, &bitLen); + appendBitsToBuffer((unsigned int)seg->numChars, numCharCountBits(seg->mode, version), qrcode, &bitLen); + for (int j = 0; j < seg->bitLength; j++) { + int bit = (seg->data[j >> 3] >> (7 - (j & 7))) & 1; + appendBitsToBuffer((unsigned int)bit, 1, qrcode, &bitLen); + } + } + assert(bitLen == dataUsedBits); + + // Add terminator and pad up to a byte if applicable + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; + assert(bitLen <= dataCapacityBits); + int terminatorBits = dataCapacityBits - bitLen; + if (terminatorBits > 4) + terminatorBits = 4; + appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen); + appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen); + assert(bitLen % 8 == 0); + + // Pad with alternating bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + appendBitsToBuffer(padByte, 8, qrcode, &bitLen); + + // Draw function and data codeword modules + addEccAndInterleave(qrcode, version, ecl, tempBuffer); + initializeFunctionModules(version, qrcode); + drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode); + drawWhiteFunctionModules(qrcode, version); + initializeFunctionModules(version, tempBuffer); + + // Handle masking + if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + enum qrcodegen_Mask msk = (enum qrcodegen_Mask)i; + applyMask(tempBuffer, qrcode, msk); + drawFormatBits(ecl, msk, qrcode); + long penalty = getPenaltyScore(qrcode); + if (penalty < minPenalty) { + mask = msk; + minPenalty = penalty; + } + applyMask(tempBuffer, qrcode, msk); // Undoes the mask due to XOR + } + } + assert(0 <= (int)mask && (int)mask <= 7); + applyMask(tempBuffer, qrcode, mask); + drawFormatBits(ecl, mask, qrcode); + return true; +} + + + +/*---- Error correction code generation functions ----*/ + +// Appends error correction bytes to each block of the given data array, then interleaves +// bytes from the blocks and stores them in the result array. data[0 : dataLen] contains +// the input data. data[dataLen : rawCodewords] is used as a temporary work area and will +// be clobbered by this function. The final answer is stored in result[0 : rawCodewords]. +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) { + // Calculate parameter numbers + assert(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK [(int)ecl][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int dataLen = getNumDataCodewords(version, ecl); + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen; + + // Split data into blocks, calculate ECC, and interleave + // (not concatenate) the bytes into a single sequence + uint8_t rsdiv[qrcodegen_REED_SOLOMON_DEGREE_MAX]; + reedSolomonComputeDivisor(blockEccLen, rsdiv); + const uint8_t *dat = data; + for (int i = 0; i < numBlocks; i++) { + int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); + uint8_t *ecc = &data[dataLen]; // Temporary storage + reedSolomonComputeRemainder(dat, datLen, rsdiv, blockEccLen, ecc); + for (int j = 0, k = i; j < datLen; j++, k += numBlocks) { // Copy data + if (j == shortBlockDataLen) + k -= numShortBlocks; + result[k] = dat[j]; + } + for (int j = 0, k = dataLen + i; j < blockEccLen; j++, k += numBlocks) // Copy ECC + result[k] = ecc[j]; + dat += datLen; + } +} + + +// Returns the number of 8-bit codewords that can be used for storing data (not ECC), +// for the given version number and error correction level. The result is in the range [9, 2956]. +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl) { + int v = version, e = (int)ecl; + assert(0 <= e && e < 4); + return getNumRawDataModules(v) / 8 + - ECC_CODEWORDS_PER_BLOCK [e][v] + * NUM_ERROR_CORRECTION_BLOCKS[e][v]; +} + + +// Returns the number of data bits that can be stored in a QR Code of the given version number, after +// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. +// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. +testable int getNumRawDataModules(int ver) { + assert(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 36; + } + assert(208 <= result && result <= 29648); + return result; +} + + + +/*---- Reed-Solomon ECC generator functions ----*/ + +// Computes a Reed-Solomon ECC generator polynomial for the given degree, storing in result[0 : degree]. +// This could be implemented as a lookup table over all possible parameter values, instead of as an algorithm. +testable void reedSolomonComputeDivisor(int degree, uint8_t result[]) { + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. + // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. + memset(result, 0, (size_t)degree * sizeof(result[0])); + result[degree - 1] = 1; // Start off with the monomial x^0 + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest monomial term which is always 1x^degree. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (int j = 0; j < degree; j++) { + result[j] = reedSolomonMultiply(result[j], root); + if (j + 1 < degree) + result[j] ^= result[j + 1]; + } + root = reedSolomonMultiply(root, 0x02); + } +} + + +// Computes the Reed-Solomon error correction codeword for the given data and divisor polynomials. +// The remainder when data[0 : dataLen] is divided by divisor[0 : degree] is stored in result[0 : degree]. +// All polynomials are in big endian, and the generator has an implicit leading 1 term. +testable void reedSolomonComputeRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]) { + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + memset(result, 0, (size_t)degree * sizeof(result[0])); + for (int i = 0; i < dataLen; i++) { // Polynomial division + uint8_t factor = data[i] ^ result[0]; + memmove(&result[0], &result[1], (size_t)(degree - 1) * sizeof(result[0])); + result[degree - 1] = 0; + for (int j = 0; j < degree; j++) + result[j] ^= reedSolomonMultiply(generator[j], factor); + } +} + +#undef qrcodegen_REED_SOLOMON_DEGREE_MAX + + +// Returns the product of the two given field elements modulo GF(2^8/0x11D). +// All inputs are valid. This could be implemented as a 256*256 lookup table. +testable uint8_t reedSolomonMultiply(uint8_t x, uint8_t y) { + // Russian peasant multiplication + uint8_t z = 0; + for (int i = 7; i >= 0; i--) { + z = (uint8_t)((z << 1) ^ ((z >> 7) * 0x11D)); + z ^= ((y >> i) & 1) * x; + } + return z; +} + + + +/*---- Drawing function modules ----*/ + +// Clears the given QR Code grid with white modules for the given +// version's size, then marks every function module as black. +testable void initializeFunctionModules(int version, uint8_t qrcode[]) { + // Initialize QR Code + int qrsize = version * 4 + 17; + memset(qrcode, 0, (size_t)((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0])); + qrcode[0] = (uint8_t)qrsize; + + // Fill horizontal and vertical timing patterns + fillRectangle(6, 0, 1, qrsize, qrcode); + fillRectangle(0, 6, qrsize, 1, qrcode); + + // Fill 3 finder patterns (all corners except bottom right) and format bits + fillRectangle(0, 0, 9, 9, qrcode); + fillRectangle(qrsize - 8, 0, 8, 9, qrcode); + fillRectangle(0, qrsize - 8, 9, 8, qrcode); + + // Fill numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners + if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) + fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode); + } + } + + // Fill version blocks + if (version >= 7) { + fillRectangle(qrsize - 11, 0, 3, 6, qrcode); + fillRectangle(0, qrsize - 11, 6, 3, qrcode); + } +} + + +// Draws white function modules and possibly some black modules onto the given QR Code, without changing +// non-function modules. This does not draw the format bits. This requires all function modules to be previously +// marked black (namely by initializeFunctionModules()), because this may skip redrawing black function modules. +static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { + // Draw horizontal and vertical timing patterns + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 7; i < qrsize - 7; i += 2) { + setModule(qrcode, 6, i, false); + setModule(qrcode, i, 6, false); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = abs(dx); + if (abs(dy) > dist) + dist = abs(dy); + if (dist == 2 || dist == 4) { + setModuleBounded(qrcode, 3 + dx, 3 + dy, false); + setModuleBounded(qrcode, qrsize - 4 + dx, 3 + dy, false); + setModuleBounded(qrcode, 3 + dx, qrsize - 4 + dy, false); + } + } + } + + // Draw numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + if ((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)) + continue; // Don't draw on the three finder corners + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) + setModule(qrcode, alignPatPos[i] + dx, alignPatPos[j] + dy, dx == 0 && dy == 0); + } + } + } + + // Draw version blocks + if (version >= 7) { + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = (long)version << 12 | rem; // uint18 + assert(bits >> 18 == 0); + + // Draw two copies + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 3; j++) { + int k = qrsize - 11 + j; + setModule(qrcode, k, i, (bits & 1) != 0); + setModule(qrcode, i, k, (bits & 1) != 0); + bits >>= 1; + } + } + } +} + + +// Draws two copies of the format bits (with its own error correction code) based +// on the given mask and error correction level. This always draws all modules of +// the format bits, unlike drawWhiteFunctionModules() which might skip black modules. +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]) { + // Calculate error correction code and pack bits + assert(0 <= (int)mask && (int)mask <= 7); + static const int table[] = {1, 0, 3, 2}; + int data = table[(int)ecl] << 3 | (int)mask; // errCorrLvl is uint2, mask is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + assert(bits >> 15 == 0); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setModule(qrcode, 8, i, getBit(bits, i)); + setModule(qrcode, 8, 7, getBit(bits, 6)); + setModule(qrcode, 8, 8, getBit(bits, 7)); + setModule(qrcode, 7, 8, getBit(bits, 8)); + for (int i = 9; i < 15; i++) + setModule(qrcode, 14 - i, 8, getBit(bits, i)); + + // Draw second copy + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 0; i < 8; i++) + setModule(qrcode, qrsize - 1 - i, 8, getBit(bits, i)); + for (int i = 8; i < 15; i++) + setModule(qrcode, 8, qrsize - 15 + i, getBit(bits, i)); + setModule(qrcode, 8, qrsize - 8, true); // Always black +} + + +// Calculates and stores an ascending list of positions of alignment patterns +// for this version number, returning the length of the list (in the range [0,7]). +// Each position is in the range [0,177), and are used on both the x and y axes. +// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. +testable int getAlignmentPatternPositions(int version, uint8_t result[7]) { + if (version == 1) + return 0; + int numAlign = version / 7 + 2; + int step = (version == 32) ? 26 : + (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; + for (int i = numAlign - 1, pos = version * 4 + 10; i >= 1; i--, pos -= step) + result[i] = (uint8_t)pos; + result[0] = 6; + return numAlign; +} + + +// Sets every pixel in the range [left : left + width] * [top : top + height] to black. +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]) { + for (int dy = 0; dy < height; dy++) { + for (int dx = 0; dx < width; dx++) + setModule(qrcode, left + dx, top + dy, true); + } +} + + + +/*---- Drawing data modules and masking ----*/ + +// Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of +// the QR Code to be black at function modules and white at codeword modules (including unused remainder bits). +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + int i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = qrsize - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < qrsize; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + int x = right - j; // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + int y = upward ? qrsize - 1 - vert : vert; // Actual y coordinate + if (!getModule(qrcode, x, y) && i < dataLen * 8) { + bool black = getBit(data[i >> 3], 7 - (i & 7)); + setModule(qrcode, x, y, black); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/white by the constructor and are left unchanged by this method + } + } + } + assert(i == dataLen * 8); +} + + +// XORs the codeword modules in this QR Code with the given mask pattern. +// The function modules must be marked and the codeword bits must be drawn +// before masking. Due to the arithmetic of XOR, calling applyMask() with +// the same mask value a second time will undo the mask. A final well-formed +// QR Code needs exactly one (not zero, two, etc.) mask applied. +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask) { + assert(0 <= (int)mask && (int)mask <= 7); // Disallows qrcodegen_Mask_AUTO + int qrsize = qrcodegen_getSize(qrcode); + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModule(functionModules, x, y)) + continue; + bool invert; + switch ((int)mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: assert(false); return; + } + bool val = getModule(qrcode, x, y); + setModule(qrcode, x, y, val ^ invert); + } + } +} + + +// Calculates and returns the penalty score based on state of the given QR Code's current modules. +// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. +static long getPenaltyScore(const uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + long result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < qrsize; y++) { + bool runColor = false; + int runX = 0; + int runHistory[7] = {0}; + int padRun = qrsize; // Add white border to initial run + for (int x = 0; x < qrsize; x++) { + if (getModule(qrcode, x, y) == runColor) { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } else { + finderPenaltyAddHistory(runX + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; + runColor = getModule(qrcode, x, y); + runX = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runX + padRun, runHistory, qrsize) * PENALTY_N3; + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < qrsize; x++) { + bool runColor = false; + int runY = 0; + int runHistory[7] = {0}; + int padRun = qrsize; // Add white border to initial run + for (int y = 0; y < qrsize; y++) { + if (getModule(qrcode, x, y) == runColor) { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } else { + finderPenaltyAddHistory(runY + padRun, runHistory); + padRun = 0; + if (!runColor) + result += finderPenaltyCountPatterns(runHistory, qrsize) * PENALTY_N3; + runColor = getModule(qrcode, x, y); + runY = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runY + padRun, runHistory, qrsize) * PENALTY_N3; + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < qrsize - 1; y++) { + for (int x = 0; x < qrsize - 1; x++) { + bool color = getModule(qrcode, x, y); + if ( color == getModule(qrcode, x + 1, y) && + color == getModule(qrcode, x, y + 1) && + color == getModule(qrcode, x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Balance of black and white modules + int black = 0; + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModule(qrcode, x, y)) + black++; + } + } + int total = qrsize * qrsize; // Note that size is odd, so black/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% + int k = (int)((labs(black * 20L - total * 10L) + total - 1) / total) - 1; + result += k * PENALTY_N4; + return result; +} + + +// Can only be called immediately after a white run is added, and +// returns either 0, 1, or 2. A helper function for getPenaltyScore(). +static int finderPenaltyCountPatterns(const int runHistory[7], int qrsize) { + int n = runHistory[1]; + assert(n <= qrsize * 3); + bool core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; + // The maximum QR Code size is 177, hence the black run length n <= 177. + // Arithmetic is promoted to int, so n*4 will not overflow. + return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); +} + + +// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). +static int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int runHistory[7], int qrsize) { + if (currentRunColor) { // Terminate black run + finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += qrsize; // Add white border to final run + finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory, qrsize); +} + + +// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). +static void finderPenaltyAddHistory(int currentRunLength, int runHistory[7]) { + memmove(&runHistory[1], &runHistory[0], 6 * sizeof(runHistory[0])); + runHistory[0] = currentRunLength; +} + + + +/*---- Basic QR Code information ----*/ + +// Public function - see documentation comment in header file. +int qrcodegen_getSize(const uint8_t qrcode[]) { + assert(qrcode != NULL); + int result = qrcode[0]; + assert((qrcodegen_VERSION_MIN * 4 + 17) <= result + && result <= (qrcodegen_VERSION_MAX * 4 + 17)); + return result; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y) { + assert(qrcode != NULL); + int qrsize = qrcode[0]; + return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && getModule(qrcode, x, y); +} + + +// Gets the module at the given coordinates, which must be in bounds. +testable bool getModule(const uint8_t qrcode[], int x, int y) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + return getBit(qrcode[(index >> 3) + 1], index & 7); +} + + +// Sets the module at the given coordinates, which must be in bounds. +testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + int bitIndex = index & 7; + int byteIndex = (index >> 3) + 1; + if (isBlack) + qrcode[byteIndex] |= 1 << bitIndex; + else + qrcode[byteIndex] &= (1 << bitIndex) ^ 0xFF; +} + + +// Sets the module at the given coordinates, doing nothing if out of bounds. +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack) { + int qrsize = qrcode[0]; + if (0 <= x && x < qrsize && 0 <= y && y < qrsize) + setModule(qrcode, x, y, isBlack); +} + + +// Returns true iff the i'th bit of x is set to 1. Requires x >= 0 and 0 <= i <= 14. +static bool getBit(int x, int i) { + return ((x >> i) & 1) != 0; +} + + + +/*---- Segment handling ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_isAlphanumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (strchr(ALPHANUMERIC_CHARSET, *text) == NULL) + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_isNumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (*text < '0' || *text > '9') + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars) { + int temp = calcSegmentBitLength(mode, numChars); + if (temp == -1) + return SIZE_MAX; + assert(0 <= temp && temp <= INT16_MAX); + return ((size_t)temp + 7) / 8; +} + + +// Returns the number of data bits needed to represent a segment +// containing the given number of characters using the given mode. Notes: +// - Returns -1 on failure, i.e. numChars > INT16_MAX or +// the number of needed bits exceeds INT16_MAX (i.e. 32767). +// - Otherwise, all valid results are in the range [0, INT16_MAX]. +// - For byte mode, numChars measures the number of bytes, not Unicode code points. +// - For ECI mode, numChars must be 0, and the worst-case number of bits is returned. +// An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) { + // All calculations are designed to avoid overflow on all platforms + if (numChars > (unsigned int)INT16_MAX) + return -1; + long result = (long)numChars; + if (mode == qrcodegen_Mode_NUMERIC) + result = (result * 10 + 2) / 3; // ceil(10/3 * n) + else if (mode == qrcodegen_Mode_ALPHANUMERIC) + result = (result * 11 + 1) / 2; // ceil(11/2 * n) + else if (mode == qrcodegen_Mode_BYTE) + result *= 8; + else if (mode == qrcodegen_Mode_KANJI) + result *= 13; + else if (mode == qrcodegen_Mode_ECI && numChars == 0) + result = 3 * 8; + else { // Invalid argument + assert(false); + return -1; + } + assert(result >= 0); + if (result > INT16_MAX) + return -1; + return (int)result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]) { + assert(data != NULL || len == 0); + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_BYTE; + result.bitLength = calcSegmentBitLength(result.mode, len); + assert(result.bitLength != -1); + result.numChars = (int)len; + if (len > 0) + memcpy(buf, data, len * sizeof(buf[0])); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]) { + assert(digits != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(digits); + result.mode = qrcodegen_Mode_NUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != -1); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *digits != '\0'; digits++) { + char c = *digits; + assert('0' <= c && c <= '9'); + accumData = accumData * 10 + (unsigned int)(c - '0'); + accumCount++; + if (accumCount == 3) { + appendBitsToBuffer(accumData, 10, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + appendBitsToBuffer(accumData, accumCount * 3 + 1, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]) { + assert(text != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(text); + result.mode = qrcodegen_Mode_ALPHANUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != -1); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *text != '\0'; text++) { + const char *temp = strchr(ALPHANUMERIC_CHARSET, *text); + assert(temp != NULL); + accumData = accumData * 45 + (unsigned int)(temp - ALPHANUMERIC_CHARSET); + accumCount++; + if (accumCount == 2) { + appendBitsToBuffer(accumData, 11, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + appendBitsToBuffer(accumData, 6, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_ECI; + result.numChars = 0; + result.bitLength = 0; + if (assignVal < 0) + assert(false); + else if (assignVal < (1 << 7)) { + memset(buf, 0, 1 * sizeof(buf[0])); + appendBitsToBuffer((unsigned int)assignVal, 8, buf, &result.bitLength); + } else if (assignVal < (1 << 14)) { + memset(buf, 0, 2 * sizeof(buf[0])); + appendBitsToBuffer(2, 2, buf, &result.bitLength); + appendBitsToBuffer((unsigned int)assignVal, 14, buf, &result.bitLength); + } else if (assignVal < 1000000L) { + memset(buf, 0, 3 * sizeof(buf[0])); + appendBitsToBuffer(6, 3, buf, &result.bitLength); + appendBitsToBuffer((unsigned int)(assignVal >> 10), 11, buf, &result.bitLength); + appendBitsToBuffer((unsigned int)(assignVal & 0x3FF), 10, buf, &result.bitLength); + } else + assert(false); + result.data = buf; + return result; +} + + +// Calculates the number of bits needed to encode the given segments at the given version. +// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too +// many characters to fit its length field, or the total bits exceeds INT16_MAX. +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version) { + assert(segs != NULL || len == 0); + long result = 0; + for (size_t i = 0; i < len; i++) { + int numChars = segs[i].numChars; + int bitLength = segs[i].bitLength; + assert(0 <= numChars && numChars <= INT16_MAX); + assert(0 <= bitLength && bitLength <= INT16_MAX); + int ccbits = numCharCountBits(segs[i].mode, version); + assert(0 <= ccbits && ccbits <= 16); + if (numChars >= (1L << ccbits)) + return -1; // The segment's length doesn't fit the field's bit width + result += 4L + ccbits + bitLength; + if (result > INT16_MAX) + return -1; // The sum might overflow an int type + } + assert(0 <= result && result <= INT16_MAX); + return (int)result; +} + + +// Returns the bit width of the character count field for a segment in the given mode +// in a QR Code at the given version number. The result is in the range [0, 16]. +static int numCharCountBits(enum qrcodegen_Mode mode, int version) { + assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int i = (version + 7) / 17; + switch (mode) { + case qrcodegen_Mode_NUMERIC : { static const int temp[] = {10, 12, 14}; return temp[i]; } + case qrcodegen_Mode_ALPHANUMERIC: { static const int temp[] = { 9, 11, 13}; return temp[i]; } + case qrcodegen_Mode_BYTE : { static const int temp[] = { 8, 16, 16}; return temp[i]; } + case qrcodegen_Mode_KANJI : { static const int temp[] = { 8, 10, 12}; return temp[i]; } + case qrcodegen_Mode_ECI : return 0; + default: assert(false); return -1; // Dummy value + } +} diff --git a/examples/common_components/qrcode/qrcodegen.h b/examples/common_components/qrcode/qrcodegen.h new file mode 100644 index 0000000000..f76be204f2 --- /dev/null +++ b/examples/common_components/qrcode/qrcodegen.h @@ -0,0 +1,311 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#pragma once + +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * This library creates QR Code symbols, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + * A QR Code structure is an immutable square grid of black and white cells. + * The library provides functions to create a QR Code from text or binary data. + * The library covers the QR Code Model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + * + * Ways to create a QR Code object: + * - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary(). + * - Low level: Custom-make the list of segments and call + * qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced(). + * (Note that all ways require supplying the desired error correction level and various byte buffers.) + */ + + +/*---- Enum and struct types----*/ + +/* + * The error correction level in a QR Code symbol. + */ +enum qrcodegen_Ecc { + // Must be declared in ascending order of error protection + // so that an internal qrcodegen function works properly + qrcodegen_Ecc_LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords + qrcodegen_Ecc_MEDIUM , // The QR Code can tolerate about 15% erroneous codewords + qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords + qrcodegen_Ecc_HIGH , // The QR Code can tolerate about 30% erroneous codewords +}; + + +/* + * The mask pattern used in a QR Code symbol. + */ +enum qrcodegen_Mask { + // A special value to tell the QR Code encoder to + // automatically select an appropriate mask pattern + qrcodegen_Mask_AUTO = -1, + // The eight actual mask patterns + qrcodegen_Mask_0 = 0, + qrcodegen_Mask_1, + qrcodegen_Mask_2, + qrcodegen_Mask_3, + qrcodegen_Mask_4, + qrcodegen_Mask_5, + qrcodegen_Mask_6, + qrcodegen_Mask_7, +}; + + +/* + * Describes how a segment's data bits are interpreted. + */ +enum qrcodegen_Mode { + qrcodegen_Mode_NUMERIC = 0x1, + qrcodegen_Mode_ALPHANUMERIC = 0x2, + qrcodegen_Mode_BYTE = 0x4, + qrcodegen_Mode_KANJI = 0x8, + qrcodegen_Mode_ECI = 0x7, +}; + + +/* + * A segment of character/binary/control data in a QR Code symbol. + * The mid-level way to create a segment is to take the payload data + * and call a factory function such as qrcodegen_makeNumeric(). + * The low-level way to create a segment is to custom-make the bit buffer + * and initialize a qrcodegen_Segment struct with appropriate values. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + * Moreover, the maximum allowed bit length is 32767 because + * the largest QR Code (version 40) has 31329 modules. + */ +struct qrcodegen_Segment { + // The mode indicator of this segment. + enum qrcodegen_Mode mode; + + // The length of this segment's unencoded data. Measured in characters for + // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + // Always zero or positive. Not the same as the data's bit length. + int numChars; + + // The data bits of this segment, packed in bitwise big endian. + // Can be null if the bit length is zero. + uint8_t *data; + + // The number of valid data bits used in the buffer. Requires + // 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8. + // The character count (numChars) must agree with the mode and the bit buffer length. + int bitLength; +}; + + + +/*---- Macro constants and functions ----*/ + +#define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard +#define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard + +// Calculates the number of bytes needed to store any QR Code up to and including the given version number, +// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];' +// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16). +// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX. +#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1) + +// The worst-case number of bytes needed to store one QR Code, up to and including +// version 40. This value equals 3918, which is just under 4 kilobytes. +// Use this more convenient value to avoid calculating tighter memory bounds for buffers. +#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX) + + + +/*---- Functions (high level) to generate QR Codes ----*/ + +/* + * Encodes the given text string to a QR Code, returning true if encoding succeeded. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * - The input text must be encoded in UTF-8 and contain no NULs. + * - The variables ecl and mask must correspond to enum constant values. + * - Requires 1 <= minVersion <= maxVersion <= 40. + * - The arrays tempBuffer and qrcode must each have a length + * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). + * - After the function returns, tempBuffer contains no useful data. + * - If successful, the resulting QR Code may use numeric, + * alphanumeric, or byte mode to encode the text. + * - In the most optimistic case, a QR Code at version 40 with low ECC + * can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string + * up to 4296 characters, or any digit string up to 7089 characters. + * These numbers represent the hard upper limit of the QR Code standard. + * - Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/* + * Encodes the given binary data to a QR Code, returning true if encoding succeeded. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * - The input array range dataAndTemp[0 : dataLen] should normally be + * valid UTF-8 text, but is not required by the QR Code standard. + * - The variables ecl and mask must correspond to enum constant values. + * - Requires 1 <= minVersion <= maxVersion <= 40. + * - The arrays dataAndTemp and qrcode must each have a length + * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). + * - After the function returns, the contents of dataAndTemp may have changed, + * and does not represent useful data anymore. + * - If successful, the resulting QR Code will use byte mode to encode the data. + * - In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte + * sequence up to length 2953. This is the hard upper limit of the QR Code standard. + * - Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/*---- Functions (low level) to generate QR Codes ----*/ + +/* + * Renders a QR Code representing the given segments at the given error correction level. + * The smallest possible QR Code version is automatically chosen for the output. Returns true if + * QR Code creation succeeded, or false if the data is too long to fit in any version. The ECC level + * of the result may be higher than the ecl argument if it can be done without increasing the version. + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will + * result in them being clobbered, but the QR Code output will still be correct. + * But the qrcode array must not overlap tempBuffer or any segment's data buffer. + */ +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Renders a QR Code representing the given segments with the given encoding parameters. + * Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions. + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or + * qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow). + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will + * result in them being clobbered, but the QR Code output will still be correct. + * But the qrcode array must not overlap tempBuffer or any segment's data buffer. + */ +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Tests whether the given string can be encoded as a segment in alphanumeric mode. + * A string is encodable iff each character is in the following set: 0 to 9, A to Z + * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +bool qrcodegen_isAlphanumeric(const char *text); + + +/* + * Tests whether the given string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + */ +bool qrcodegen_isNumeric(const char *text); + + +/* + * Returns the number of bytes (uint8_t) needed for the data buffer of a segment + * containing the given number of characters using the given mode. Notes: + * - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or + * the number of needed bits exceeds INT16_MAX (i.e. 32767). + * - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096. + * - It is okay for the user to allocate more bytes for the buffer than needed. + * - For byte mode, numChars measures the number of bytes, not Unicode code points. + * - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned. + * An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. + */ +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars); + + +/* + * Returns a segment representing the given binary data encoded in + * byte mode. All input byte arrays are acceptable. Any text string + * can be converted to UTF-8 bytes and encoded as a byte mode segment. + */ +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]); + + +/* + * Returns a segment representing the given string of decimal digits encoded in numeric mode. + */ +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]); + + +/* + * Returns a segment representing the given text string encoded in alphanumeric mode. + * The characters allowed are: 0 to 9, A to Z (uppercase only), space, + * dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]); + + +/* + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the given assignment value. + */ +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]); + + +/*---- Functions to extract raw data from QR Codes ----*/ + +/* + * Returns the side length of the given QR Code, assuming that encoding succeeded. + * The result is in the range [21, 177]. Note that the length of the array buffer + * is related to the side length - every 'uint8_t qrcode[]' must have length at least + * qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1). + */ +int qrcodegen_getSize(const uint8_t qrcode[]); + + +/* + * Returns the color of the module (pixel) at the given coordinates, which is false + * for white or true for black. The top left corner has the coordinates (x=0, y=0). + * If the given coordinates are out of bounds, then false (white) is returned. + */ +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y); + + +#ifdef __cplusplus +} +#endif diff --git a/examples/wifi/wifi_easy_connect/dpp-enrollee/CMakeLists.txt b/examples/wifi/wifi_easy_connect/dpp-enrollee/CMakeLists.txt new file mode 100644 index 0000000000..2d4a908a8e --- /dev/null +++ b/examples/wifi/wifi_easy_connect/dpp-enrollee/CMakeLists.txt @@ -0,0 +1,8 @@ +# 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) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/qrcode) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(dpp-enrollee) diff --git a/examples/wifi/wifi_easy_connect/dpp-enrollee/Makefile b/examples/wifi/wifi_easy_connect/dpp-enrollee/Makefile new file mode 100644 index 0000000000..681e7b2b48 --- /dev/null +++ b/examples/wifi/wifi_easy_connect/dpp-enrollee/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := dpp-enrollee + +EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/qrcode + +include $(IDF_PATH)/make/project.mk diff --git a/examples/wifi/wifi_easy_connect/dpp-enrollee/README.md b/examples/wifi/wifi_easy_connect/dpp-enrollee/README.md new file mode 100644 index 0000000000..03ced1a144 --- /dev/null +++ b/examples/wifi/wifi_easy_connect/dpp-enrollee/README.md @@ -0,0 +1,66 @@ +# Device Provisioning Protocol (Enrollee) Example + +This example shows how to configure ESP32 as an enrollee using Device Provisioning Protocol(DPP) also known as Wi-Fi Easy Connect. + +DPP provides a simple and secure way to onboard ESP32 to a network. +We now support Responder-Enrollee mode of DPP with PSK mode of authentication. + +You need a Wi-Fi Easy Connect with Initiator mode capable device to make use of this example. Some Android 10+ devices have this capability. (Vendor specific) + +To run the example with an Android 10+ device follow below steps - +1. Compile and flash the example on ESP32, a QR code will appear on your console. +2. Connect your phone to the network, say named "Example-AP". +3. Now go to Settings->WiFi & Internet->Wi-Fi->Example-AP->Advanced->Add Device. +4. Scan QR Code using the scanner, which will make ESP32 connect to Example-AP. + +Optional configuration available + +*Note:* +- QR Code should be displayed as dark on a white/light background to work properly. +- If displayed QR Code had line gaps, try switching to a new font or a diiferent Terminal program. See below QR Code for for checking beforehand. + +### Example output + +Here is an example of the console output. +``` +I (807) wifi:mode : sta (24:0a:c4:23:da:20) +I (807) wifi dpp-enrollee: Started listening on Channel 11 for DPP Authentication +I (1157) wifi dpp-enrollee: Scan below QR Code to configure the enrollee: + + + █▀▀▀▀▀█ ██▄▄▄█▄▀██▄▄█▄ ▀ ▀▄ █▄▄ █▀▀▀▀▀█ + █ ███ █ ██▀█▀ ▀▀██▀█▄█▀▄▀ ██▀▀█ ▄ █ ███ █ + █ ▀▀▀ █ ▄█▀▄▄ ▄▄▀ █▄▀ ▄ ▄ ▄▀▄ ██ █ ▀▀▀ █ + ▀▀▀▀▀▀▀ ▀ █▄▀ ▀ ▀▄▀▄▀▄▀ █ ▀ ▀▄█ ▀ ▀▀▀▀▀▀▀ + █▀ ▄██▀ ▄█ ▀█ ▄▀▄▄▄ ▀▀█▄ ▄▀█▄█▀▀▄▄▄▀▄██▀█ + █▄▀ ▄ ▀▄█▄ ▀▀█▀▀█ ▀▄ ▄█▀▀▀▀█▀▄▄▄ ██▄ ▄█ + ▀█▀█▀ ▀▀ ▀ ▄▀▄▀▀ ▄ ▄▀▀▀ █▄ ▄▄ ▀█▄▀▄ █ + ▀ ▀ ▀▀▀█▄ █▀▀ █▄▄▄ █▄ █▄▀ ██▄ ▄▄▀█▄▀ ▄█ + ▀██▀▄█▀▄ ▄█ ▀▄▀ █ ▄ ▄█▄▀▄▀▄▄▀▄ ▄▄▄▀▄▄ + ▀▀▄█▀█▄▀▀█▄ ▄▀ █▄ ▀█▄█▄▀ ▀█▄▄ ▄▀▄ █▄▀ █ + ▄▀▀ ▀█▀▀▀ ▄ ▀█▀▀▄ ▀ ▄▄█▄ █ ██▀▄▀▀▄▄▄▄█▀▄ + ▀ ███▀▀▄ ▄ ▄ ▀█▄▄▀█▀▀▀ ▀▀▄▄ ▀ █▄ ▄█ + █ ▀▄▄ ▀▀▀▀▄▀▀▀▄█▄▄ ▄▀▄▀ ▀▄▀▄▀█▀▀▄▀ ▄█▄▀ + ███ ▄▀▄▀▀▄▀▀█▀▀▄ ▀▄ ████ █▀▄█▄▄ ▀█▄ ▀▀ ▀ + ▄▀█▀▀▀▀█▀ ▄█▄▀▀ ▄ ▀█▀▀ ▀ ▄▀▀ ▀▄█ ▄ ▀ + █ ▀▀▀▄██▄█▀ ▀█▄█▄ ▀██▀▄▀▄▀ █▀ ▀ ▄▄▀█ ▄█ + ▀▀▀ ▀▀▀▀▄▄█▄▀█▄ ▄ ▄ ▀▀▀█▄▄▀▀▀ █▀▀▀██▀▀▄ + █▀▀▀▀▀█ ▄▄▀█▀ ▄█▄█▄▄█▄ ▀ ▀▀▀█▄ ▀█ ▀ █ ▀ █ + █ ███ █ ▀█▀ ▀█▀▀▄▄▀ ▀▄█▀▀ ██▀█▀▀▀█▀▄▄▄█ + █ ▀▀▀ █ ▄▀█ ▄ ▄ ▀█▄ ▀▄▀█ ▀▄██▄ ▀ ▄█ ▄▀▄█ + ▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀ ▀▀▀ ▀▀▀▀▀ ▀ ▀ ▀ + +I (6357) wifi dpp-enrollee: DPP Authentication successful, connecting to AP : DigitalFortress +I (6477) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1 +I (7277) wifi:state: init -> auth (b0) +I (7277) wifi:state: auth -> assoc (0) +I (7287) wifi:state: assoc -> run (10) +I (7317) wifi:connected with DigitalFortress, aid = 4, channel 1, BW20, bssid = 04:d4:c4:5e:22:f0 +I (7317) wifi:security type: 3, phy: bgn, rssi: -60 +I (7427) wifi:pm start, type: 1 + +I (7427) wifi:AP's beacon interval = 102400 us, DTIM period = 1 +I (11617) esp_netif_handlers: sta ip: 192.168.1.216, mask: 255.255.255.0, gw: 192.168.1.1 +I (11617) wifi dpp-enrollee: got ip:192.168.1.216 +I (11617) wifi dpp-enrollee: connected to ap SSID:DigitalFortress password:password +``` diff --git a/examples/wifi/wifi_easy_connect/dpp-enrollee/main/CMakeLists.txt b/examples/wifi/wifi_easy_connect/dpp-enrollee/main/CMakeLists.txt new file mode 100644 index 0000000000..97a762af08 --- /dev/null +++ b/examples/wifi/wifi_easy_connect/dpp-enrollee/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "dpp_enrollee_main.c" + INCLUDE_DIRS ".") diff --git a/examples/wifi/wifi_easy_connect/dpp-enrollee/main/Kconfig.projbuild b/examples/wifi/wifi_easy_connect/dpp-enrollee/main/Kconfig.projbuild new file mode 100644 index 0000000000..307533583f --- /dev/null +++ b/examples/wifi/wifi_easy_connect/dpp-enrollee/main/Kconfig.projbuild @@ -0,0 +1,17 @@ +menu "Example Configuration" + config ESP_DPP_LISTEN_CHANNEL_LIST + string "DPP Listen channel list" + default "6" + help + DPP Bootstrapping listen channels separated by commas. + + config ESP_DPP_BOOTSTRAPPING_KEY + string "Bootstrapping key" + help + Private key string for DPP Bootstrapping in PEM format. + + config ESP_DPP_DEVICE_INFO + string "Additional Device Info" + help + Additional ancillary information to be included in QR Code. +endmenu diff --git a/examples/wifi/wifi_easy_connect/dpp-enrollee/main/component.mk b/examples/wifi/wifi_easy_connect/dpp-enrollee/main/component.mk new file mode 100644 index 0000000000..0adf45649a --- /dev/null +++ b/examples/wifi/wifi_easy_connect/dpp-enrollee/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/wifi/wifi_easy_connect/dpp-enrollee/main/dpp_enrollee_main.c b/examples/wifi/wifi_easy_connect/dpp-enrollee/main/dpp_enrollee_main.c new file mode 100644 index 0000000000..ae9f99d73c --- /dev/null +++ b/examples/wifi/wifi_easy_connect/dpp-enrollee/main/dpp_enrollee_main.c @@ -0,0 +1,169 @@ +/* DPP Enrollee 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_dpp.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "qrcode.h" + +#ifdef CONFIG_ESP_DPP_LISTEN_CHANNEL +#define EXAMPLE_DPP_LISTEN_CHANNEL_LIST CONFIG_ESP_DPP_LISTEN_CHANNEL_LIST +#else +#define EXAMPLE_DPP_LISTEN_CHANNEL_LIST "6" +#endif + +#ifdef CONFIG_ESP_DPP_BOOTSTRAPPING_KEY +#define EXAMPLE_DPP_BOOTSTRAPPING_KEY CONFIG_ESP_DPP_BOOTSTRAPPING_KEY +#else +#define EXAMPLE_DPP_BOOTSTRAPPING_KEY 0 +#endif + +#ifdef CONFIG_ESP_DPP_DEVICE_INFO +#define EXAMPLE_DPP_DEVICE_INFO CONFIG_ESP_DPP_DEVICE_INFO +#else +#define EXAMPLE_DPP_DEVICE_INFO 0 +#endif + +static const char *TAG = "wifi dpp-enrollee"; +wifi_config_t s_dpp_wifi_config; + +static int s_retry_num = 0; + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t s_dpp_event_group; + +#define DPP_CONNECTED_BIT BIT0 +#define DPP_CONNECT_FAIL_BIT BIT1 +#define DPP_AUTH_FAIL_BIT BIT2 + +static void event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + ESP_ERROR_CHECK(esp_supp_dpp_start_listen()); + ESP_LOGI(TAG, "Started listening for DPP Authentication"); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + if (s_retry_num < 5) { + esp_wifi_connect(); + s_retry_num++; + ESP_LOGI(TAG, "retry to connect to the AP"); + } else { + xEventGroupSetBits(s_dpp_event_group, DPP_CONNECT_FAIL_BIT); + } + ESP_LOGI(TAG, "connect to the AP fail"); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; + ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + s_retry_num = 0; + xEventGroupSetBits(s_dpp_event_group, DPP_CONNECTED_BIT); + } +} + +void dpp_enrollee_event_cb(esp_supp_dpp_event_t event, void *data) +{ + switch (event) { + case ESP_SUPP_DPP_URI_READY: + if (data != NULL) { + esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT(); + + ESP_LOGI(TAG, "Scan below QR Code to configure the enrollee:\n"); + esp_qrcode_generate(&cfg, (const char *)data); + } + break; + case ESP_SUPP_DPP_CFG_RECVD: + memcpy(&s_dpp_wifi_config, data, sizeof(s_dpp_wifi_config)); + esp_wifi_set_config(ESP_IF_WIFI_STA, &s_dpp_wifi_config); + ESP_LOGI(TAG, "DPP Authentication successful, connecting to AP : %s", + s_dpp_wifi_config.sta.ssid); + s_retry_num = 0; + esp_wifi_connect(); + break; + case ESP_SUPP_DPP_FAIL: + if (s_retry_num < 5) { + ESP_LOGI(TAG, "DPP Auth failed (Reason: %s), retry...", esp_err_to_name((int)data)); + ESP_ERROR_CHECK(esp_supp_dpp_start_listen()); + s_retry_num++; + } else { + xEventGroupSetBits(s_dpp_event_group, DPP_AUTH_FAIL_BIT); + } + break; + default: + break; + } +} + +void dpp_enrollee_init(void) +{ + s_dpp_event_group = xEventGroupCreate(); + + ESP_ERROR_CHECK(esp_netif_init()); + + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_create_default_wifi_sta(); + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL)); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_supp_dpp_init(dpp_enrollee_event_cb)); + /* Currently only supported method is QR Code */ + ESP_ERROR_CHECK(esp_supp_dpp_bootstrap_gen(EXAMPLE_DPP_LISTEN_CHANNEL_LIST, DPP_BOOTSTRAP_QR_CODE, + EXAMPLE_DPP_BOOTSTRAPPING_KEY, EXAMPLE_DPP_DEVICE_INFO)); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); + + /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum + * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */ + EventBits_t bits = xEventGroupWaitBits(s_dpp_event_group, + DPP_CONNECTED_BIT | DPP_CONNECT_FAIL_BIT | DPP_AUTH_FAIL_BIT, + pdFALSE, + pdFALSE, + portMAX_DELAY); + + /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually + * happened. */ + if (bits & DPP_CONNECTED_BIT) { + ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", + s_dpp_wifi_config.sta.ssid, s_dpp_wifi_config.sta.password); + } else if (bits & DPP_CONNECT_FAIL_BIT) { + ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", + s_dpp_wifi_config.sta.ssid, s_dpp_wifi_config.sta.password); + } else if (bits & DPP_AUTH_FAIL_BIT) { + ESP_LOGI(TAG, "DPP Authentication failed after %d retries", s_retry_num); + } else { + ESP_LOGE(TAG, "UNEXPECTED EVENT"); + } + + esp_supp_dpp_deinit(); + ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler)); + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler)); + vEventGroupDelete(s_dpp_event_group); +} + +void app_main(void) +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + dpp_enrollee_init(); +}