From 1a85d64da898405f9bd68cfb077e42bbaa6dbfc9 Mon Sep 17 00:00:00 2001 From: Nachiket Kukade Date: Tue, 27 Oct 2020 16:09:40 +0530 Subject: [PATCH 1/4] Add QR Code display support Add a component to support QR Code display required for different types of provisioning. --- docs/en/COPYRIGHT.rst | 3 + .../common_components/qrcode/CMakeLists.txt | 5 + .../qrcode/esp_qrcode_main.c | 114 ++ .../qrcode/esp_qrcode_wrapper.c | 29 + .../common_components/qrcode/include/qrcode.h | 104 ++ examples/common_components/qrcode/qrcodegen.c | 1022 +++++++++++++++++ examples/common_components/qrcode/qrcodegen.h | 311 +++++ 7 files changed, 1588 insertions(+) create mode 100644 examples/common_components/qrcode/CMakeLists.txt create mode 100644 examples/common_components/qrcode/esp_qrcode_main.c create mode 100644 examples/common_components/qrcode/esp_qrcode_wrapper.c create mode 100644 examples/common_components/qrcode/include/qrcode.h create mode 100644 examples/common_components/qrcode/qrcodegen.c create mode 100644 examples/common_components/qrcode/qrcodegen.h 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..605e33b2d5 --- /dev/null +++ b/examples/common_components/qrcode/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "esp_qrcode_main.c" "esp_qrcode_wrapper.c" "qrcodegen.c" + INCLUDE_DIRS "include" + REQUIRES + PRIV_REQUIRES ) + 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..e6ba3fa03f --- /dev/null +++ b/examples/common_components/qrcode/esp_qrcode_main.c @@ -0,0 +1,114 @@ +// Copyright 2019 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" + +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; + } + + // 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..3b44421333 --- /dev/null +++ b/examples/common_components/qrcode/esp_qrcode_wrapper.c @@ -0,0 +1,29 @@ +// Copyright 2019 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..651a2baca2 --- /dev/null +++ b/examples/common_components/qrcode/include/qrcode.h @@ -0,0 +1,104 @@ +// Copyright 2019 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, \ + .qrcode_ecc_level = ESP_QRCODE_ECC_LOW, \ + .max_qrcode_version = 10, \ +} \ + +#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 From 1ca80b4221745785cd1352c75d5591e8c291a0e9 Mon Sep 17 00:00:00 2001 From: Nachiket Kukade Date: Tue, 27 Oct 2020 18:12:40 +0530 Subject: [PATCH 2/4] Add support for Remain-on-Channel and Action Tx 1. Add API's and structures for Remain-onChannel & Action Tx 2. Handling of events and callbacks for the operations 3. Update WiFi lib with related API support --- components/esp_event/event_send.c | 6 ++++ .../esp_event/include/esp_event_legacy.h | 2 ++ components/esp_wifi/include/esp_wifi_types.h | 30 +++++++++++++++++++ components/esp_wifi/lib | 2 +- .../src/esp_supplicant/esp_wifi_driver.h | 5 ++++ 5 files changed, 44 insertions(+), 1 deletion(-) 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..b69f47de2e 100644 --- a/components/esp_wifi/include/esp_wifi_types.h +++ b/components/esp_wifi/include/esp_wifi_types.h @@ -483,6 +483,21 @@ typedef struct { enabled_ant1: 4; /**< Index (in antenna GPIO configuration) of enabled WIFI_ANT_MODE_ANT1 */ } wifi_ant_config_t; +/** + * @brief Management Frame Tx Request + * + * + */ +typedef struct { + wifi_interface_t ifx; /**< WiFi interface to send request to */ + uint8_t subtype; /**< Frame Subtype of Management frame */ + uint8_t dest_mac[6]; /**< Destination MAC address */ + bool no_ack; /**< Indicates no ack required for the frame */ + uint32_t cookie; /**< Context to identify the request */ + uint32_t data_len; /**< Length of the appended Data */ + uint8_t data[0]; /**< Appended Data payload */ +} mgmt_tx_req_t; + /** * @brief WiFi PHY rate encodings * @@ -551,6 +566,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 +662,19 @@ typedef struct { #define WIFI_STATIS_PS (1<<4) #define WIFI_STATIS_ALL (-1) +/** Argument structure for WIFI_EVENT_MGMT_TX_STATUS event */ +typedef struct { + wifi_interface_t ifx; /**< WiFi interface to send request to */ + uint32_t cookie; /**< Context to identify the request */ + uint8_t da[6]; /**< Destination MAC address */ + uint8_t status; /**< Status of the operation */ +} wifi_event_mgmt_tx_status_t; + +/** Argument structure for WIFI_EVENT_ROC_DONE event */ +typedef struct { + uint32_t cookie; /**< 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/src/esp_supplicant/esp_wifi_driver.h b/components/wpa_supplicant/src/esp_supplicant/esp_wifi_driver.h index 685976b157..9f79597386 100644 --- a/components/wpa_supplicant/src/esp_supplicant/esp_wifi_driver.h +++ b/components/wpa_supplicant/src/esp_supplicant/esp_wifi_driver.h @@ -136,6 +136,7 @@ struct wpa_funcs { uint8_t *(*wpa3_build_sae_msg)(uint8_t *bssid, uint32_t type, size_t *len); int (*wpa3_parse_sae_msg)(uint8_t *buf, size_t len, uint32_t type, uint16_t status); int (*wpa_sta_rx_mgmt)(u8 type, u8 *frame, size_t len, u8 *sender, u32 rssi, u8 channel, u64 current_tsf); + int (*offchan_rx_mgmt)(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel); }; struct wpa2_funcs { @@ -261,5 +262,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_mgmt_tx_req(uint8_t action, uint8_t channel, + uint32_t wait_time_ms, const mgmt_tx_req_t *req); +esp_err_t esp_wifi_remain_on_channel(uint8_t ifx, uint8_t action, uint8_t channel, + uint32_t wait_time_ms, void *ctx); #endif /* _ESP_WIFI_DRIVER_H_ */ From 87205dc2f483e1c38c60df0a5278f1e94da0d3a6 Mon Sep 17 00:00:00 2001 From: Nachiket Kukade Date: Tue, 27 Oct 2020 18:17:38 +0530 Subject: [PATCH 3/4] Add DPP Enrollee Support 1. Modify DPP Protocol modules for our purpose 2. Add DPP supplicant task and modules to handle DPP frames 3. Add DPP Public API's and definitions for DPP --- components/esp_common/src/esp_err_to_name.c | 22 + components/esp_wifi/Kconfig | 6 + components/wpa_supplicant/CMakeLists.txt | 3 +- .../include/esp_supplicant/esp_dpp.h | 59 ++ components/wpa_supplicant/src/common/dpp.c | 38 +- components/wpa_supplicant/src/common/dpp.h | 16 +- .../src/common/ieee802_11_defs.h | 140 ++--- .../src/esp_supplicant/esp_dpp.c | 562 ++++++++++++++++++ .../src/esp_supplicant/esp_dpp_i.h | 69 +++ .../src/esp_supplicant/esp_wpa_main.c | 2 + 10 files changed, 784 insertions(+), 133 deletions(-) create mode 100644 components/wpa_supplicant/include/esp_supplicant/esp_dpp.h create mode 100644 components/wpa_supplicant/src/esp_supplicant/esp_dpp.c create mode 100644 components/wpa_supplicant/src/esp_supplicant/esp_dpp_i.h diff --git a/components/esp_common/src/esp_err_to_name.c b/components/esp_common/src/esp_err_to_name.c index 45e5d11f6e..a6ac17ddbd 100644 --- a/components/esp_common/src/esp_err_to_name.c +++ b/components/esp_common/src/esp_err_to_name.c @@ -62,6 +62,9 @@ #if __has_include("ulp_common.h") #include "ulp_common.h" #endif +#if __has_include("esp_supplicant/esp_dpp.h") +#include "esp_supplicant/esp_dpp.h" +#endif #ifdef CONFIG_ESP_ERR_TO_NAME_LOOKUP #define ERR_TBL_IT(err) {err, #err} @@ -407,6 +410,25 @@ 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_NO_MEM + ERR_TBL_IT(ESP_ERR_DPP_NO_MEM), /* 12440 0x3098 Failure to allocate memory in DPP Operation */ +# endif +# ifdef ESP_ERR_DPP_TIMEOUT + ERR_TBL_IT(ESP_ERR_DPP_TIMEOUT), /* 12441 0x3099 DPP Operation timed out */ +# endif +# ifdef ESP_ERR_DPP_TX_FAILURE + ERR_TBL_IT(ESP_ERR_DPP_TX_FAILURE), /* 12442 0x309a DPP Frame Tx failed OR not Acked */ +# endif +# ifdef ESP_ERR_DPP_INVALID_ATTR + ERR_TBL_IT(ESP_ERR_DPP_INVALID_ATTR), /* 12443 0x309b Encountered invalid DPP Attribute */ +# endif +# ifdef ESP_ERR_DPP_NOT_SUPPORTED + ERR_TBL_IT(ESP_ERR_DPP_NOT_SUPPORTED), /* 12444 0x309c DPP Configuration not supported */ # endif // components/esp_common/include/esp_err.h # ifdef ESP_ERR_MESH_BASE diff --git a/components/esp_wifi/Kconfig b/components/esp_wifi/Kconfig index e72afb2e09..131cf35884 100644 --- a/components/esp_wifi/Kconfig +++ b/components/esp_wifi/Kconfig @@ -331,6 +331,12 @@ menu "Wi-Fi" If neither of them are enabled, the other 7.4KB IRAM memory would be taken by this option. Wi-Fi power-save mode average current would be reduced if this option is enabled. + config ESP32_WIFI_OFFCHANNEL_OPS + bool "Enable Offchannel operations" + default n + help + Select this option to enable Offchannel Tx and Remain on Channel features. + endmenu # Wi-Fi menu "PHY" 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..0e5bccbf55 --- /dev/null +++ b/components/wpa_supplicant/include/esp_supplicant/esp_dpp.h @@ -0,0 +1,59 @@ +// Copyright 2019 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_NO_MEM (ESP_ERR_WIFI_BASE + 152) /*!< Failure to allocate memory in DPP Operation */ +#define ESP_ERR_DPP_TIMEOUT (ESP_ERR_WIFI_BASE + 153) /*!< DPP Operation timed out */ +#define ESP_ERR_DPP_TX_FAILURE (ESP_ERR_WIFI_BASE + 154) /*!< DPP Frame Tx failed OR not Acked */ +#define ESP_ERR_DPP_INVALID_ATTR (ESP_ERR_WIFI_BASE + 155) /*!< Encountered invalid DPP Attribute */ +#define ESP_ERR_DPP_NOT_SUPPORTED (ESP_ERR_WIFI_BASE + 156) /*!< Encountered invalid DPP Attribute */ + +enum dpp_bootstrap_type { + DPP_BOOTSTRAP_QR_CODE, + DPP_BOOTSTRAP_PKEX, + DPP_BOOTSTRAP_NFC_URI, +}; + +typedef enum { + WIFI_DPP_URI_READY, + WIFI_DPP_CFG_RECVD, + WIFI_DPP_FAIL, +} wifi_dpp_event_t; + +typedef void (*wifi_dpp_event_cb_t)(wifi_dpp_event_t evt, void *data); + +esp_err_t esp_supp_dpp_init(wifi_dpp_event_cb_t evt_cb); +void esp_supp_dpp_deinit(void); + +esp_err_t esp_dpp_bootstrap_gen(uint8_t channel, enum dpp_bootstrap_type type, + const char *key, const char *info); + +void esp_dpp_start_listen(uint8_t channel); +void esp_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..462a228d9a 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,8 @@ 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, *i_proto, *i_nonce, *i_capab, *i_bootstrap; + u16 wrapped_data_len, i_proto_len, i_nonce_len, i_capab_len, i_bootstrap_len; struct dpp_authentication *auth = NULL; #ifdef CONFIG_WPA_TESTING_OPTIONS @@ -2438,10 +2436,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 +2451,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 +2467,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); @@ -4630,7 +4628,7 @@ static int dpp_parse_cred_legacy(struct dpp_config_obj *conf, (u8 *)pass->string, len); if (len < 8 || len > 63) return -1; - os_strlcpy(conf->passphrase, pass->string, + os_strncpy(conf->passphrase, pass->string, sizeof(conf->passphrase)); } else if (psk_hex && psk_hex->type == JSON_STRING) { if (dpp_akm_sae(conf->akm) && !dpp_akm_psk(conf->akm)) { @@ -5386,7 +5384,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 +5396,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 +5417,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 +5449,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 +6081,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,8 +6138,9 @@ 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); + + bi->id = dpp_next_id(dpp); + dl_list_add(&dpp->bootstrap, &bi->list); ret = bi->id; bi = NULL; fail: 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..5c59d9c2f7 --- /dev/null +++ b/components/wpa_supplicant/src/esp_supplicant/esp_dpp.c @@ -0,0 +1,562 @@ +/* + * wpa_supplicant - DPP + * Copyright (c) 2017, Qualcomm Atheros, Inc. + * Copyright (c) 2018-2019, The Linux Foundation + * + * This software may be distributed under the terms of the BSD license. + */ + +#include "esp_dpp_i.h" +#include "esp_dpp.h" +#include "esp_wpa.h" +#include "esp_timer.h" +#include "esp_event.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_auth_start; +static int s_dpp_auth_retries; +struct esp_dpp_context_t s_dpp_ctx; + +#define REQUEST_ADD 1 +#define REQUEST_CANCEL 0 + +#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; +}; + +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_DPP_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(wifi_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) +{ + mgmt_tx_req_t *req = os_zalloc(sizeof(*req) + len);; + + req->ifx = ESP_IF_WIFI_STA; + req->subtype = WLAN_FC_STYPE_ACTION; + memcpy(req->dest_mac, dest_mac, ETH_ALEN); + req->no_ack = false; + req->data_len = len; + 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_mgmt_tx_req(REQUEST_ADD, channel, wait_time_ms, req)) { + wpa_printf(MSG_ERROR, "DPP: Failed to perfrm offchannel operation"); + esp_dpp_call_cb(WIFI_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(WIFI_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(WIFI_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_auth_start = false; + esp_wifi_mgmt_tx_req(REQUEST_CANCEL, 0, 0, NULL); + esp_dpp_call_cb(WIFI_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(WIFI_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(WIFI_DPP_FAIL, (void *)ESP_ERR_DPP_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(WIFI_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_auth_start) { + s_dpp_auth_start = true; + esp_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); +} + +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(WIFI_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; + + 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_dpp_rx_mgmt(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, + int event_id, void* event_data) +{ + if (event_id == WIFI_EVENT_MGMT_TX_STATUS) { + wifi_event_mgmt_tx_status_t *evt = + (wifi_event_mgmt_tx_status_t *)event_data; + wpa_printf(MSG_DEBUG, "Mgmt Tx Status - %d, Cookie - 0x%x", + evt->status, (uint32_t)evt->cookie); + + if (evt->status) + esp_dpp_call_cb(WIFI_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_auth_start && evt->cookie == BOOTSTRAP_ROC_COOKIE) { + esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, REQUEST_ADD, + s_dpp_ctx.bootstrap_params.channel, + BOOTSTRAP_ROC_WAIT_TIME, + (void *)BOOTSTRAP_ROC_COOKIE); + } + } +} + +esp_err_t esp_dpp_bootstrap_gen(uint8_t channel, enum dpp_bootstrap_type type, + const char *key, const char *uri_info) +{ + struct dpp_bootstrap_params_t *params = &s_dpp_ctx.bootstrap_params; + char *command = os_zalloc(1200); + int ret; + + if (type != DPP_BOOTSTRAP_QR_CODE) { + wpa_printf(MSG_INFO, "Bootstrap type %d not supported", type); + os_free(command); + ret = ESP_ERR_DPP_NOT_SUPPORTED; + goto fail; + } + params->type = type; + params->channel = channel; + 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) { + ret = ESP_ERR_DPP_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) { + ret = ESP_ERR_DPP_NO_MEM; + goto fail; + } + sprintf(params->key, "%s%s%s", prefix, key, postfix); + } + } + + sprintf(command, "type=qrcode mac=" MACSTR " chan=81/%d %s%s%s%s", + MAC2STR(params->mac), channel, + 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; + } + + return ESP_OK; +fail: + return ret; +} + +void esp_dpp_start_listen(uint8_t channel) +{ + esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, REQUEST_ADD, channel, + BOOTSTRAP_ROC_WAIT_TIME, (void *)BOOTSTRAP_ROC_COOKIE); +} + +void esp_dpp_stop_listen(void) +{ + esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, REQUEST_CANCEL, 0, 0, NULL); +} + +esp_err_t esp_supp_dpp_init(wifi_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_auth_start = 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_DPP_NO_MEM; + } + + esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_MGMT_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..43dce6a75d --- /dev/null +++ b/components/wpa_supplicant/src/esp_supplicant/esp_dpp_i.h @@ -0,0 +1,69 @@ +// Copyright 2019 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_DEL_TASK, + SIG_DPP_MAX, +}; + +typedef struct { + uint32_t id; + uint32_t data; +} dpp_event_t; + +#define BOOTSTRAP_ROC_WAIT_TIME 5000 +#define OFFCHAN_TX_WAIT_TIME 500 + +#define BOOTSTRAP_ROC_COOKIE 0xABABABAB + +struct dpp_bootstrap_params_t { + enum dpp_bootstrap_type type; + uint8_t channel; + 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; + wifi_config_t wifi_config; + wifi_dpp_event_cb_t dpp_event_cb; + struct dpp_global *dpp_global; + wifi_config_t wifi_cfg; + int id; +}; + +int esp_dpp_rx_mgmt(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_wpa_main.c b/components/wpa_supplicant/src/esp_supplicant/esp_wpa_main.c index f2fad8643f..8177c9c1e7 100644 --- a/components/wpa_supplicant/src/esp_supplicant/esp_wpa_main.c +++ b/components/wpa_supplicant/src/esp_supplicant/esp_wpa_main.c @@ -36,6 +36,7 @@ #include "esp_wpa3_i.h" #include "esp_wpa2.h" #include "esp_common_i.h" +#include "esp_dpp_i.h" void wpa_install_key(enum wpa_alg alg, u8 *addr, int key_idx, int set_tx, u8 *seq, size_t seq_len, u8 *key, size_t key_len, int key_entry_valid) @@ -253,6 +254,7 @@ int esp_supplicant_init(void) wpa_cb->wpa_parse_wpa_ie = wpa_parse_wpa_ie_wrapper; wpa_cb->wpa_config_bss = NULL;//wpa_config_bss; wpa_cb->wpa_michael_mic_failure = wpa_michael_mic_failure; + wpa_cb->offchan_rx_mgmt = esp_dpp_rx_mgmt; esp_wifi_register_wpa3_cb(wpa_cb); esp_supplicant_common_init(wpa_cb); From 76b2cb28d23afcb2ca2f625718dc2fa376fdafda Mon Sep 17 00:00:00 2001 From: Nachiket Kukade Date: Tue, 27 Oct 2020 18:23:19 +0530 Subject: [PATCH 4/4] Add DPP Enrollee example 1. Add Example for DPP Enrollee 2. Use DPP Supplicant API's to setup connection 3. Add support for multiple channels in Bootstrapping 4. Add Unity testcase for testing Offchannel operations Closes https://github.com/espressif/esp-idf/issues/5654 --- components/esp_common/src/esp_err_to_name.c | 19 +- components/esp_wifi/Kconfig | 6 - components/esp_wifi/include/esp_wifi_types.h | 43 ++- .../include/esp_supplicant/esp_dpp.h | 101 ++++-- components/wpa_supplicant/src/common/dpp.c | 18 +- .../src/esp_supplicant/esp_dpp.c | 292 ++++++++++++------ .../src/esp_supplicant/esp_dpp_i.h | 15 +- .../src/esp_supplicant/esp_wifi_driver.h | 9 +- .../src/esp_supplicant/esp_wpa_main.c | 2 - .../wpa_supplicant/test/test_offchannel.c | 245 +++++++++++++++ .../common_components/qrcode/CMakeLists.txt | 6 +- examples/common_components/qrcode/README.md | 9 + .../common_components/qrcode/component.mk | 0 .../qrcode/esp_qrcode_main.c | 23 +- .../qrcode/esp_qrcode_wrapper.c | 2 +- .../common_components/qrcode/include/qrcode.h | 6 +- .../dpp-enrollee/CMakeLists.txt | 8 + .../wifi_easy_connect/dpp-enrollee/Makefile | 10 + .../wifi_easy_connect/dpp-enrollee/README.md | 66 ++++ .../dpp-enrollee/main/CMakeLists.txt | 2 + .../dpp-enrollee/main/Kconfig.projbuild | 17 + .../dpp-enrollee/main/component.mk | 8 + .../dpp-enrollee/main/dpp_enrollee_main.c | 169 ++++++++++ 23 files changed, 882 insertions(+), 194 deletions(-) create mode 100644 components/wpa_supplicant/test/test_offchannel.c create mode 100644 examples/common_components/qrcode/README.md create mode 100644 examples/common_components/qrcode/component.mk create mode 100644 examples/wifi/wifi_easy_connect/dpp-enrollee/CMakeLists.txt create mode 100644 examples/wifi/wifi_easy_connect/dpp-enrollee/Makefile create mode 100644 examples/wifi/wifi_easy_connect/dpp-enrollee/README.md create mode 100644 examples/wifi/wifi_easy_connect/dpp-enrollee/main/CMakeLists.txt create mode 100644 examples/wifi/wifi_easy_connect/dpp-enrollee/main/Kconfig.projbuild create mode 100644 examples/wifi/wifi_easy_connect/dpp-enrollee/main/component.mk create mode 100644 examples/wifi/wifi_easy_connect/dpp-enrollee/main/dpp_enrollee_main.c diff --git a/components/esp_common/src/esp_err_to_name.c b/components/esp_common/src/esp_err_to_name.c index a6ac17ddbd..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 @@ -62,9 +65,6 @@ #if __has_include("ulp_common.h") #include "ulp_common.h" #endif -#if __has_include("esp_supplicant/esp_dpp.h") -#include "esp_supplicant/esp_dpp.h" -#endif #ifdef CONFIG_ESP_ERR_TO_NAME_LOOKUP #define ERR_TBL_IT(err) {err, #err} @@ -415,20 +415,11 @@ static const esp_err_msg_t esp_err_msg_table[] = { # ifdef ESP_ERR_DPP_FAILURE ERR_TBL_IT(ESP_ERR_DPP_FAILURE), /* 12439 0x3097 Generic failure during DPP Operation */ # endif -# ifdef ESP_ERR_DPP_NO_MEM - ERR_TBL_IT(ESP_ERR_DPP_NO_MEM), /* 12440 0x3098 Failure to allocate memory in DPP Operation */ -# endif -# ifdef ESP_ERR_DPP_TIMEOUT - ERR_TBL_IT(ESP_ERR_DPP_TIMEOUT), /* 12441 0x3099 DPP Operation timed out */ -# endif # ifdef ESP_ERR_DPP_TX_FAILURE - ERR_TBL_IT(ESP_ERR_DPP_TX_FAILURE), /* 12442 0x309a DPP Frame Tx failed OR not Acked */ + 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), /* 12443 0x309b Encountered invalid DPP Attribute */ -# endif -# ifdef ESP_ERR_DPP_NOT_SUPPORTED - ERR_TBL_IT(ESP_ERR_DPP_NOT_SUPPORTED), /* 12444 0x309c DPP Configuration not supported */ + 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_wifi/Kconfig b/components/esp_wifi/Kconfig index 131cf35884..e72afb2e09 100644 --- a/components/esp_wifi/Kconfig +++ b/components/esp_wifi/Kconfig @@ -331,12 +331,6 @@ menu "Wi-Fi" If neither of them are enabled, the other 7.4KB IRAM memory would be taken by this option. Wi-Fi power-save mode average current would be reduced if this option is enabled. - config ESP32_WIFI_OFFCHANNEL_OPS - bool "Enable Offchannel operations" - default n - help - Select this option to enable Offchannel Tx and Remain on Channel features. - endmenu # Wi-Fi menu "PHY" diff --git a/components/esp_wifi/include/esp_wifi_types.h b/components/esp_wifi/include/esp_wifi_types.h index b69f47de2e..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 */ @@ -484,19 +490,30 @@ typedef struct { } wifi_ant_config_t; /** - * @brief Management Frame Tx Request + * @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 subtype; /**< Frame Subtype of Management frame */ - uint8_t dest_mac[6]; /**< Destination MAC address */ - bool no_ack; /**< Indicates no ack required for the frame */ - uint32_t cookie; /**< Context to identify the request */ - uint32_t data_len; /**< Length of the appended Data */ - uint8_t data[0]; /**< Appended Data payload */ -} mgmt_tx_req_t; + 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 @@ -662,17 +679,17 @@ typedef struct { #define WIFI_STATIS_PS (1<<4) #define WIFI_STATIS_ALL (-1) -/** Argument structure for WIFI_EVENT_MGMT_TX_STATUS event */ +/** Argument structure for WIFI_EVENT_ACTION_TX_STATUS event */ typedef struct { wifi_interface_t ifx; /**< WiFi interface to send request to */ - uint32_t cookie; /**< Context to identify the request */ + uint32_t context; /**< Context to identify the request */ uint8_t da[6]; /**< Destination MAC address */ uint8_t status; /**< Status of the operation */ -} wifi_event_mgmt_tx_status_t; +} wifi_event_action_tx_status_t; /** Argument structure for WIFI_EVENT_ROC_DONE event */ typedef struct { - uint32_t cookie; /**< Context to identify the request */ + uint32_t context; /**< Context to identify the request */ } wifi_event_roc_done_t; #ifdef __cplusplus diff --git a/components/wpa_supplicant/include/esp_supplicant/esp_dpp.h b/components/wpa_supplicant/include/esp_supplicant/esp_dpp.h index 0e5bccbf55..c6c86bc536 100644 --- a/components/wpa_supplicant/include/esp_supplicant/esp_dpp.h +++ b/components/wpa_supplicant/include/esp_supplicant/esp_dpp.h @@ -1,4 +1,4 @@ -// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// 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. @@ -23,35 +23,92 @@ extern "C" { #endif -#define ESP_ERR_DPP_FAILURE (ESP_ERR_WIFI_BASE + 151) /*!< Generic failure during DPP Operation */ -#define ESP_ERR_DPP_NO_MEM (ESP_ERR_WIFI_BASE + 152) /*!< Failure to allocate memory in DPP Operation */ -#define ESP_ERR_DPP_TIMEOUT (ESP_ERR_WIFI_BASE + 153) /*!< DPP Operation timed out */ -#define ESP_ERR_DPP_TX_FAILURE (ESP_ERR_WIFI_BASE + 154) /*!< DPP Frame Tx failed OR not Acked */ -#define ESP_ERR_DPP_INVALID_ATTR (ESP_ERR_WIFI_BASE + 155) /*!< Encountered invalid DPP Attribute */ -#define ESP_ERR_DPP_NOT_SUPPORTED (ESP_ERR_WIFI_BASE + 156) /*!< Encountered invalid DPP Attribute */ +#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 */ -enum dpp_bootstrap_type { - DPP_BOOTSTRAP_QR_CODE, - DPP_BOOTSTRAP_PKEX, - DPP_BOOTSTRAP_NFC_URI, -}; +/** @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 { - WIFI_DPP_URI_READY, - WIFI_DPP_CFG_RECVD, - WIFI_DPP_FAIL, -} wifi_dpp_event_t; + 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; -typedef void (*wifi_dpp_event_cb_t)(wifi_dpp_event_t evt, void *data); +/** + * @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); -esp_err_t esp_supp_dpp_init(wifi_dpp_event_cb_t evt_cb); +/** + * @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); -esp_err_t esp_dpp_bootstrap_gen(uint8_t channel, enum dpp_bootstrap_type type, - const char *key, const char *info); +/** + * @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); -void esp_dpp_start_listen(uint8_t channel); -void esp_dpp_stop_listen(void); +/** + * @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 } diff --git a/components/wpa_supplicant/src/common/dpp.c b/components/wpa_supplicant/src/common/dpp.c index 462a228d9a..c96869b318 100644 --- a/components/wpa_supplicant/src/common/dpp.c +++ b/components/wpa_supplicant/src/common/dpp.c @@ -2406,8 +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; - u16 wrapped_data_len, i_proto_len, i_nonce_len, i_capab_len, i_bootstrap_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 @@ -4628,7 +4636,7 @@ static int dpp_parse_cred_legacy(struct dpp_config_obj *conf, (u8 *)pass->string, len); if (len < 8 || len > 63) return -1; - os_strncpy(conf->passphrase, pass->string, + os_strlcpy(conf->passphrase, pass->string, sizeof(conf->passphrase)); } else if (psk_hex && psk_hex->type == JSON_STRING) { if (dpp_akm_sae(conf->akm) && !dpp_akm_psk(conf->akm)) { @@ -6139,8 +6147,8 @@ int dpp_bootstrap_gen(struct dpp_global *dpp, const char *cmd) info ? "I:" : "", info ? info : "", info ? ";" : "", pk); - bi->id = dpp_next_id(dpp); - dl_list_add(&dpp->bootstrap, &bi->list); + bi->id = dpp_next_id(dpp); + dl_list_add(&dpp->bootstrap, &bi->list); ret = bi->id; bi = NULL; fail: diff --git a/components/wpa_supplicant/src/esp_supplicant/esp_dpp.c b/components/wpa_supplicant/src/esp_supplicant/esp_dpp.c index 5c59d9c2f7..1ebe8912bd 100644 --- a/components/wpa_supplicant/src/esp_supplicant/esp_dpp.c +++ b/components/wpa_supplicant/src/esp_supplicant/esp_dpp.c @@ -1,28 +1,33 @@ -/* - * wpa_supplicant - DPP - * Copyright (c) 2017, Qualcomm Atheros, Inc. - * Copyright (c) 2018-2019, The Linux Foundation - * - * This software may be distributed under the terms of the BSD license. - */ +// 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_auth_start; +static bool s_dpp_stop_listening; static int s_dpp_auth_retries; struct esp_dpp_context_t s_dpp_ctx; - -#define REQUEST_ADD 1 -#define REQUEST_CANCEL 0 +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) @@ -35,14 +40,14 @@ struct action_rx_param { struct ieee80211_action *action_frm; }; -int esp_dpp_post_evt(uint32_t evt_id, uint32_t data) +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_DPP_NO_MEM; + return ESP_ERR_NO_MEM; } evt->id = evt_id; evt->data = data; @@ -55,7 +60,7 @@ int esp_dpp_post_evt(uint32_t evt_id, uint32_t data) return ESP_OK; } -static void esp_dpp_call_cb(wifi_dpp_event_t evt, void *data) +static void esp_dpp_call_cb(esp_supp_dpp_event_t evt, void *data) { s_dpp_ctx.dpp_event_cb(evt, data); } @@ -63,21 +68,25 @@ static void esp_dpp_call_cb(wifi_dpp_event_t evt, void *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) { - mgmt_tx_req_t *req = os_zalloc(sizeof(*req) + len);; + wifi_action_tx_req_t *req = os_zalloc(sizeof(*req) + len);; + if (!req) { + return; + } req->ifx = ESP_IF_WIFI_STA; - req->subtype = WLAN_FC_STYPE_ACTION; 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); + MAC2STR(dest_mac), channel, wait_time_ms); - if (ESP_OK != esp_wifi_mgmt_tx_req(REQUEST_ADD, channel, wait_time_ms, req)) { + 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(WIFI_DPP_FAIL, (void *)ESP_ERR_DPP_TX_FAILURE); + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)ESP_ERR_DPP_TX_FAILURE); os_free(req); return; } @@ -96,7 +105,7 @@ static void esp_dpp_rx_auth_req(struct action_rx_param *rx_param, uint8_t *dpp_d 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); + &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; @@ -105,7 +114,7 @@ static void esp_dpp_rx_auth_req(struct action_rx_param *rx_param, uint8_t *dpp_d 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); + &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; @@ -132,7 +141,7 @@ static void esp_dpp_rx_auth_req(struct action_rx_param *rx_param, uint8_t *dpp_d rx_param->channel, OFFCHAN_TX_WAIT_TIME); return; fail: - esp_dpp_call_cb(WIFI_DPP_FAIL, (void *)rc); + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)rc); } static void gas_query_req_tx(struct dpp_authentication *auth) @@ -144,7 +153,7 @@ static void gas_query_req_tx(struct dpp_authentication *auth) supp_op_classes); if (!buf) { wpa_printf(MSG_DEBUG, "DPP: No configuration request data available"); - esp_dpp_call_cb(WIFI_DPP_FAIL, (void *)ESP_ERR_DPP_FAILURE); + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)ESP_ERR_DPP_FAILURE); return; } @@ -183,9 +192,9 @@ static int esp_dpp_handle_config_obj(struct dpp_authentication *auth, wpa_printf(MSG_INFO, DPP_EVENT_CONNECTOR "%s", conf->connector); } - s_dpp_auth_start = false; - esp_wifi_mgmt_tx_req(REQUEST_CANCEL, 0, 0, NULL); - esp_dpp_call_cb(WIFI_DPP_CFG_RECVD, wifi_cfg); + 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; } @@ -193,8 +202,8 @@ static int esp_dpp_handle_config_obj(struct dpp_authentication *auth, 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; + struct ieee80211_public_action *public_action = + &rx_param->action_frm->u.public_action; size_t len = rx_param->vendor_data_len - 2; int rc; @@ -227,7 +236,7 @@ static void esp_dpp_rx_auth_conf(struct action_rx_param *rx_param, uint8_t *dpp_ return; fail: - esp_dpp_call_cb(WIFI_DPP_FAIL, (void *)rc); + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)rc); } static void esp_dpp_rx_auth(struct action_rx_param *rx_param) @@ -241,7 +250,7 @@ static void esp_dpp_rx_auth(struct action_rx_param *rx_param) if (crypto_suit != 1) { wpa_printf(MSG_ERROR, "DPP: Unsupported crypto suit"); - esp_dpp_call_cb(WIFI_DPP_FAIL, (void *)ESP_ERR_DPP_NOT_SUPPORTED); + esp_dpp_call_cb(ESP_SUPP_DPP_FAIL, (void *)ESP_ERR_NOT_SUPPORTED); return; } @@ -263,44 +272,44 @@ static void gas_query_resp_rx(struct action_rx_param *rx_param) 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_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) + if (res < 0) { goto fail; + } } } return; fail: - esp_dpp_call_cb(WIFI_DPP_FAIL, (void *)ESP_ERR_DPP_FAILURE); + 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; + 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) { + 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); + (size_t)(public_action->v.pa_vendor_spec.vendor_data - + (u8 *)rx_param->action_frm); - if (!s_dpp_auth_start) { - s_dpp_auth_start = true; - esp_dpp_stop_listen(); + if (!s_dpp_stop_listening) { + esp_supp_dpp_stop_listen(); } esp_dpp_rx_auth(rx_param); @@ -310,9 +319,9 @@ static void esp_dpp_rx_action(struct action_rx_param *rx_param) 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); + (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); } @@ -322,7 +331,7 @@ static void esp_dpp_rx_action(struct action_rx_param *rx_param) os_free(rx_param); } -void esp_dpp_task(void *pvParameters ) +static void esp_dpp_task(void *pvParameters ) { dpp_event_t *evt; bool task_del = false; @@ -337,38 +346,48 @@ void esp_dpp_task(void *pvParameters ) } switch (evt->id) { - case SIG_DPP_DEL_TASK: - task_del = true; + case SIG_DPP_DEL_TASK: + task_del = true; break; - case SIG_DPP_BOOTSTRAP_GEN: - { - char *command = (char *)evt->data; - const char *uri; + 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); + 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(WIFI_DPP_URI_READY, (void *)uri); - os_free(command); - } - break; + 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_RX_ACTION: { + esp_dpp_rx_action((struct action_rx_param *)evt->data); + } + break; - default: + 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) + if (task_del) { break; + } } } @@ -384,7 +403,7 @@ void esp_dpp_task(void *pvParameters ) vTaskDelete(NULL); } -int esp_dpp_rx_mgmt(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel) +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; @@ -406,53 +425,110 @@ int esp_dpp_rx_mgmt(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel) return ESP_ERR_NOT_SUPPORTED; } -static void offchan_event_handler(void* arg, esp_event_base_t event_base, - int event_id, void* event_data) +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_MGMT_TX_STATUS) { - wifi_event_mgmt_tx_status_t *evt = - (wifi_event_mgmt_tx_status_t *)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->cookie); + evt->status, (uint32_t)evt->context); - if (evt->status) - esp_dpp_call_cb(WIFI_DPP_FAIL, (void *)ESP_ERR_DPP_TX_FAILURE); + 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_auth_start && evt->cookie == BOOTSTRAP_ROC_COOKIE) { - esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, REQUEST_ADD, - s_dpp_ctx.bootstrap_params.channel, - BOOTSTRAP_ROC_WAIT_TIME, - (void *)BOOTSTRAP_ROC_COOKIE); + if (!s_dpp_stop_listening && evt->context == (uint32_t)s_action_rx_cb) { + esp_dpp_post_evt(SIG_DPP_LISTEN_NEXT_CHANNEL, 0); } } } -esp_err_t esp_dpp_bootstrap_gen(uint8_t channel, enum dpp_bootstrap_type type, - const char *key, const char *uri_info) +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_DPP_NOT_SUPPORTED; + ret = ESP_ERR_NOT_SUPPORTED; goto fail; } params->type = type; - params->channel = channel; 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); + params->info = os_zalloc(params->info_len + 1); if (!params->info) { - ret = ESP_ERR_DPP_NO_MEM; + os_free(command); + ret = ESP_ERR_NO_MEM; goto fail; } os_memcpy(params->info, uri_info, params->info_len); @@ -468,19 +544,20 @@ esp_err_t esp_dpp_bootstrap_gen(uint8_t channel, enum dpp_bootstrap_type type, params->key = os_zalloc(params->key_len + sizeof(prefix) + sizeof(postfix)); if (!params->key) { - ret = ESP_ERR_DPP_NO_MEM; + 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 " chan=81/%d %s%s%s%s", - MAC2STR(params->mac), channel, - params->key_len ? "key=" : "", - params->key_len ? params->key : "", - params->info_len ? " info=" : "", - params->info_len ? params->info : ""); + 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) { @@ -496,23 +573,32 @@ esp_err_t esp_dpp_bootstrap_gen(uint8_t channel, enum dpp_bootstrap_type type, goto fail; } - return ESP_OK; + ret = ESP_OK; fail: + if (uri_chan_list) { + os_free(uri_chan_list); + } + return ret; } -void esp_dpp_start_listen(uint8_t channel) +esp_err_t esp_supp_dpp_start_listen(void) { - esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, REQUEST_ADD, channel, - BOOTSTRAP_ROC_WAIT_TIME, (void *)BOOTSTRAP_ROC_COOKIE); + 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_dpp_stop_listen(void) +void esp_supp_dpp_stop_listen(void) { - esp_wifi_remain_on_channel(ESP_IF_WIFI_STA, REQUEST_CANCEL, 0, 0, NULL); + 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(wifi_dpp_event_cb_t cb) +esp_err_t esp_supp_dpp_init(esp_supp_dpp_event_cb_t cb) { struct dpp_global_config cfg = {0}; @@ -523,17 +609,17 @@ esp_err_t esp_supp_dpp_init(wifi_dpp_event_cb_t cb) cfg.msg_ctx = &s_dpp_ctx; s_dpp_ctx.dpp_global = dpp_global_init(&cfg); - s_dpp_auth_start = false; + 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_DPP_NO_MEM; + return ESP_ERR_NO_MEM; } - esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_MGMT_TX_STATUS, + 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); diff --git a/components/wpa_supplicant/src/esp_supplicant/esp_dpp_i.h b/components/wpa_supplicant/src/esp_supplicant/esp_dpp_i.h index 43dce6a75d..3c7aea750a 100644 --- a/components/wpa_supplicant/src/esp_supplicant/esp_dpp_i.h +++ b/components/wpa_supplicant/src/esp_supplicant/esp_dpp_i.h @@ -1,4 +1,4 @@ -// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// 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. @@ -29,6 +29,7 @@ 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, }; @@ -38,14 +39,13 @@ typedef struct { uint32_t data; } dpp_event_t; -#define BOOTSTRAP_ROC_WAIT_TIME 5000 +#define BOOTSTRAP_ROC_WAIT_TIME 500 #define OFFCHAN_TX_WAIT_TIME 500 -#define BOOTSTRAP_ROC_COOKIE 0xABABABAB - struct dpp_bootstrap_params_t { enum dpp_bootstrap_type type; - uint8_t channel; + uint8_t chan_list[14]; + uint8_t num_chan; uint8_t mac[6]; uint32_t key_len; char *key; @@ -57,13 +57,12 @@ struct esp_dpp_context_t { struct dpp_bootstrap_params_t bootstrap_params; struct dpp_authentication *dpp_auth; int gas_dialog_token; - wifi_config_t wifi_config; - wifi_dpp_event_cb_t dpp_event_cb; + esp_supp_dpp_event_cb_t dpp_event_cb; struct dpp_global *dpp_global; wifi_config_t wifi_cfg; int id; }; -int esp_dpp_rx_mgmt(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel); +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 9f79597386..86e951e5f7 100644 --- a/components/wpa_supplicant/src/esp_supplicant/esp_wifi_driver.h +++ b/components/wpa_supplicant/src/esp_supplicant/esp_wifi_driver.h @@ -136,7 +136,6 @@ struct wpa_funcs { uint8_t *(*wpa3_build_sae_msg)(uint8_t *bssid, uint32_t type, size_t *len); int (*wpa3_parse_sae_msg)(uint8_t *buf, size_t len, uint32_t type, uint16_t status); int (*wpa_sta_rx_mgmt)(u8 type, u8 *frame, size_t len, u8 *sender, u32 rssi, u8 channel, u64 current_tsf); - int (*offchan_rx_mgmt)(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel); }; struct wpa2_funcs { @@ -262,9 +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_mgmt_tx_req(uint8_t action, uint8_t channel, - uint32_t wait_time_ms, const mgmt_tx_req_t *req); -esp_err_t esp_wifi_remain_on_channel(uint8_t ifx, uint8_t action, uint8_t channel, - uint32_t wait_time_ms, void *ctx); +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/src/esp_supplicant/esp_wpa_main.c b/components/wpa_supplicant/src/esp_supplicant/esp_wpa_main.c index 8177c9c1e7..f2fad8643f 100644 --- a/components/wpa_supplicant/src/esp_supplicant/esp_wpa_main.c +++ b/components/wpa_supplicant/src/esp_supplicant/esp_wpa_main.c @@ -36,7 +36,6 @@ #include "esp_wpa3_i.h" #include "esp_wpa2.h" #include "esp_common_i.h" -#include "esp_dpp_i.h" void wpa_install_key(enum wpa_alg alg, u8 *addr, int key_idx, int set_tx, u8 *seq, size_t seq_len, u8 *key, size_t key_len, int key_entry_valid) @@ -254,7 +253,6 @@ int esp_supplicant_init(void) wpa_cb->wpa_parse_wpa_ie = wpa_parse_wpa_ie_wrapper; wpa_cb->wpa_config_bss = NULL;//wpa_config_bss; wpa_cb->wpa_michael_mic_failure = wpa_michael_mic_failure; - wpa_cb->offchan_rx_mgmt = esp_dpp_rx_mgmt; esp_wifi_register_wpa3_cb(wpa_cb); esp_supplicant_common_init(wpa_cb); 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/examples/common_components/qrcode/CMakeLists.txt b/examples/common_components/qrcode/CMakeLists.txt index 605e33b2d5..fa3bd8e418 100644 --- a/examples/common_components/qrcode/CMakeLists.txt +++ b/examples/common_components/qrcode/CMakeLists.txt @@ -1,5 +1,3 @@ idf_component_register(SRCS "esp_qrcode_main.c" "esp_qrcode_wrapper.c" "qrcodegen.c" - INCLUDE_DIRS "include" - REQUIRES - PRIV_REQUIRES ) - + 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 index e6ba3fa03f..7b03d5d4f4 100644 --- a/examples/common_components/qrcode/esp_qrcode_main.c +++ b/examples/common_components/qrcode/esp_qrcode_main.c @@ -1,4 +1,4 @@ -// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// 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. @@ -14,10 +14,13 @@ #include #include +#include "esp_log.h" #include "qrcodegen.h" #include "qrcode.h" +static const char *TAG = "QRCODE"; + static const char *lt[] = { /* 0 */ " ", /* 1 */ "\u2580 ", @@ -67,13 +70,14 @@ void esp_qrcode_print_console(esp_qrcode_handle_t qrcode) esp_err_t esp_qrcode_generate(esp_qrcode_config_t *cfg, const char *text) { - enum qrcodegen_Ecc ecc_lvl; + 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) + if (!qrcode) { return ESP_ERR_NO_MEM; + } tempbuf = calloc(1, qrcodegen_BUFFER_LEN_FOR_VERSION(cfg->max_qrcode_version)); if (!tempbuf) { @@ -99,12 +103,15 @@ esp_err_t esp_qrcode_generate(esp_qrcode_config_t *cfg, const char *text) break; } - // Make and print the QR Code symbol - bool ok = qrcodegen_encodeText(text, tempbuf, qrcode, ecc_lvl, - qrcodegen_VERSION_MIN, cfg->max_qrcode_version, + 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); + if (ok && cfg->display_func) { + cfg->display_func((esp_qrcode_handle_t)qrcode); err = ESP_OK; } diff --git a/examples/common_components/qrcode/esp_qrcode_wrapper.c b/examples/common_components/qrcode/esp_qrcode_wrapper.c index 3b44421333..cde516586b 100644 --- a/examples/common_components/qrcode/esp_qrcode_wrapper.c +++ b/examples/common_components/qrcode/esp_qrcode_wrapper.c @@ -1,4 +1,4 @@ -// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// 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. diff --git a/examples/common_components/qrcode/include/qrcode.h b/examples/common_components/qrcode/include/qrcode.h index 651a2baca2..75002c3e0e 100644 --- a/examples/common_components/qrcode/include/qrcode.h +++ b/examples/common_components/qrcode/include/qrcode.h @@ -1,4 +1,4 @@ -// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// 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. @@ -95,9 +95,9 @@ 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, \ - .qrcode_ecc_level = ESP_QRCODE_ECC_LOW, \ .max_qrcode_version = 10, \ -} \ + .qrcode_ecc_level = ESP_QRCODE_ECC_LOW, \ +} #ifdef __cplusplus } 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(); +}