diff --git a/.github/workflows/lws_build.yml b/.github/workflows/lws_build.yml new file mode 100644 index 0000000000..da30b6d025 --- /dev/null +++ b/.github/workflows/lws_build.yml @@ -0,0 +1,86 @@ +name: "lws: build-tests" + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, labeled] + +jobs: + build_lws: + if: contains(github.event.pull_request.labels.*.name, 'lws') || github.event_name == 'push' + name: Libwebsockets build + strategy: + matrix: + idf_ver: ["latest", "release-v5.3"] + test: [ { app: example, path: "examples/client-echo" }] + runs-on: ubuntu-22.04 + container: espressif/idf:${{ matrix.idf_ver }} + env: + TEST_DIR: components/libwebsockets/${{ matrix.test.path }} + steps: + - name: Checkout esp-protocols + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} + shell: bash + run: | + . ${IDF_PATH}/export.sh + python -m pip install idf-build-apps + python ./ci/build_apps.py ${TEST_DIR} + cd ${TEST_DIR} + for dir in `ls -d build_esp32_*`; do + $GITHUB_WORKSPACE/ci/clean_build_artifacts.sh `pwd`/$dir + zip -qur artifacts.zip $dir + done + - uses: actions/upload-artifact@v4 + with: + name: lws_target_esp32_${{ matrix.idf_ver }}_${{ matrix.test.app }} + path: ${{ env.TEST_DIR }}/artifacts.zip + if-no-files-found: error + + run-target-lws: + # Skip running on forks since it won't have access to secrets + if: | + github.repository == 'espressif/esp-protocols' && + ( contains(github.event.pull_request.labels.*.name, 'lws') || github.event_name == 'push' ) + name: Target test + needs: build_lws + strategy: + fail-fast: false + matrix: + idf_ver: ["latest", "release-v5.3"] + idf_target: ["esp32"] + test: [ { app: example, path: "examples/client-echo" }] + runs-on: + - self-hosted + - ESP32-ETHERNET-KIT + env: + TEST_DIR: components/libwebsockets/${{ matrix.test.path }} + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: lws_target_esp32_${{ matrix.idf_ver }}_${{ matrix.test.app }} + path: ${{ env.TEST_DIR }}/ci/ + - name: Install Python packages + env: + PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple" + run: | + pip install --only-binary cryptography --extra-index-url https://dl.espressif.com/pypi/ -r $GITHUB_WORKSPACE/ci/requirements.txt + - name: Run Example Test on target + working-directory: ${{ env.TEST_DIR }} + run: | + unzip ci/artifacts.zip -d ci + for dir in `ls -d ci/build_*`; do + rm -rf build sdkconfig.defaults + mv $dir build + python -m pytest --log-cli-level DEBUG --junit-xml=./results_${{ matrix.test.app }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${dir#"ci/build_"}.xml --target=${{ matrix.idf_target }} + done + - uses: actions/upload-artifact@v4 + if: always() + with: + name: results_${{ matrix.test.app }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }}.xml + path: components/libwebsockets/${{ matrix.test.path }}/*.xml diff --git a/.github/workflows/publish-docs-component.yml b/.github/workflows/publish-docs-component.yml index c364a0bf0d..601c3700d5 100644 --- a/.github/workflows/publish-docs-component.yml +++ b/.github/workflows/publish-docs-component.yml @@ -102,5 +102,6 @@ jobs: components/mbedtls_cxx; components/mosquitto; components/sock_utils; + components/libwebsockets; namespace: "espressif" api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} diff --git a/.gitmodules b/.gitmodules index 60b3730713..c6c9187ddc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "components/mosquitto/mosquitto"] path = components/mosquitto/mosquitto url = https://github.com/eclipse/mosquitto +[submodule "components/libwebsockets/libwebsockets"] + path = components/libwebsockets/libwebsockets + url = https://github.com/warmcat/libwebsockets.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2b7af80bff..cd1b0ceb7e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -61,8 +61,8 @@ repos: - repo: local hooks: - id: commit message scopes - name: "commit message must be scoped with: mdns, modem, websocket, asio, mqtt_cxx, console, common, eppp, tls_cxx, mosq, sockutls" - entry: '\A(?!(feat|fix|ci|bump|test|docs|chore)\((mdns|modem|common|console|websocket|asio|mqtt_cxx|examples|eppp|tls_cxx|mosq|sockutls)\)\:)' + name: "commit message must be scoped with: mdns, modem, websocket, asio, mqtt_cxx, console, common, eppp, tls_cxx, mosq, sockutls, lws" + entry: '\A(?!(feat|fix|ci|bump|test|docs|chore)\((mdns|modem|common|console|websocket|asio|mqtt_cxx|examples|eppp|tls_cxx|mosq|sockutls|lws)\)\:)' language: pygrep args: [--multiline] stages: [commit-msg] diff --git a/README.md b/README.md index 3962546f0b..e8e6ec398d 100644 --- a/README.md +++ b/README.md @@ -66,3 +66,7 @@ Please refer to instructions in [ESP-IDF](https://github.com/espressif/esp-idf) ### Socket helpers (sock-utils) * Brief introduction [README](components/sock_utils/README.md) + +### libwebsockets + +* Brief introduction [README](components/libwebsockets/README.md) diff --git a/components/libwebsockets/CMakeLists.txt b/components/libwebsockets/CMakeLists.txt new file mode 100644 index 0000000000..cd039d68cb --- /dev/null +++ b/components/libwebsockets/CMakeLists.txt @@ -0,0 +1,21 @@ +idf_component_register(REQUIRES esp-tls) + +option(LWS_WITH_EXPORT_LWSTARGETS "Export libwebsockets CMake targets. Disable if they conflict with an outer cmake project." OFF) +set(LWS_WITH_EXPORT_LWSTARGETS OFF) + +option(LWS_WITH_MBEDTLS "Use mbedTLS (>=2.0) replacement for OpenSSL. When setting this, you also may need to specify LWS_MBEDTLS_LIBRARIES and LWS_MBEDTLS_INCLUDE_DIRS" ON) +set(LWS_WITH_MBEDTLS ON) + +set(WRAP_FUNCTIONS mbedtls_ssl_handshake_step + lws_adopt_descriptor_vhost) + +foreach(wrap ${WRAP_FUNCTIONS}) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}") +endforeach() + +target_link_libraries(${COMPONENT_LIB} INTERFACE websockets) + +target_sources(${COMPONENT_LIB} INTERFACE "port/lws_port.c") + + +add_subdirectory(libwebsockets) diff --git a/components/libwebsockets/README.md b/components/libwebsockets/README.md new file mode 100644 index 0000000000..108e4e8be0 --- /dev/null +++ b/components/libwebsockets/README.md @@ -0,0 +1,35 @@ +# ESP32 libwebsockets Port + +This is a lightweight port of the libwebsockets library designed to run on the ESP32. It provides WebSocket client and server functionalities. + +## Supported Options + +The ESP32 port of libwebsockets supports a set of common WebSocket configurations, including both server and client modes. These options can be configured through a structure passed to the `lws_create_context()` function. + +Key features supported: +- WebSocket server with optional SSL/TLS +- WebSocket client support +- HTTP/1.1 server and client support + +## Mandatory Use of mbedTLS + +For secure WebSocket communication (WSS) and SSL/TLS support, **mbedTLS** is mandatory. mbedTLS provides the cryptographic functions required for establishing secure connections and is included as part of the ESP32 toolchain. + +- **mbedTLS** is required for all encrypted WebSocket (WSS) and HTTPS connections. +- Ensure that mbedTLS is properly configured in your ESP32 project, as it is essential for enabling SSL/TLS functionality. + +## Memory Footprint Considerations + +libwebsockets on the ESP32 has been optimized for minimal memory usage. The memory consumption primarily depends on the number of concurrent connections and the selected options for WebSocket frames, protocol handling, and SSL/TLS features. + +- **Initial Memory Usage**: TBD +- **Per Client Memory Usage**: TBD +- **Per Server Memory Usage**: TBD + +### Memory Management Tips: +- When configuring a WebSocket server, ensure that you have enough heap space to handle the desired number of concurrent client connections. +- SSL/TLS configurations may require additional memory overhead, depending on the certificate size and cryptographic settings. + +## Testing + +TBD diff --git a/components/libwebsockets/examples/client-echo/CMakeLists.txt b/components/libwebsockets/examples/client-echo/CMakeLists.txt new file mode 100644 index 0000000000..f2c1e167b0 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(client_echo_example) diff --git a/components/libwebsockets/examples/client-echo/LICENSE b/components/libwebsockets/examples/client-echo/LICENSE new file mode 100644 index 0000000000..8f82a43bc3 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/LICENSE @@ -0,0 +1,311 @@ +Libwebsockets and included programs are provided under the terms of the +MIT license shown below, with the exception that some sources are under +a similar permissive license like BSD, or are explicitly CC0 / public +domain to remove any obstacles from basing differently-licensed code on +them. + +Original liberal license retained: + + - lib/misc/sha-1.c - 3-clause BSD license retained, link to original [BSD3] + - win32port/zlib + - lib/drivers/display/upng.* - ZLIB license (see zlib.h) [ZLIB] + - lib/tls/mbedtls/wrapper - Apache 2.0 (only built if linked against mbedtls) [APACHE2] + - lib/tls/mbedtls/mbedtls-extensions.c + - lib/misc/base64-decode.c - already MIT + - contrib/mcufont/encoder + - lib/misc/ieeehalfprecision.c - 2-clause BSD license retained [BSD2] + - contrib/mcufont/fonts - Open Font License [OFL] + +Relicensed to MIT: + + - lib/misc/daemonize.c - relicensed from Public Domain to MIT, + link to original Public Domain version + - lib/plat/windows/windows-resolv.c - relicensed from "Beerware v42" to MIT + +Public Domain (CC-zero) to simplify reuse: + + - test-apps/*.c + - test-apps/*.h + - minimal-examples/* + - lwsws/* + +Although libwebsockets is available under a permissive license, it does not +change the reality of dealing with large lumps of external code... if your +copy diverges it is guaranteed to contain security problems after a while +and can be very painful to pick backports (especially since historically, +we are very hot on cleaning and refactoring the codebase). The least +painful and lowest risk way remains sending your changes and fixes upstream +to us so you can easily use later releases and fixes. + +## MIT License applied to libwebsockets + +https://opensource.org/licenses/MIT + + 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. + +## BSD2 + +``` + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. +``` + +## BSD3 + +For convenience, a copy of the license on `./lib/misc/sha-1.c`. In binary +distribution, this applies to builds with ws support enabled, and without +`LWS_WITHOUT_BUILTIN_SHA1` at cmake. + +``` +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH +``` + +## ZLIB + +For convenience, a copy of the license on zlib. In binary distribution, +this applies for win32 builds with internal zlib only. You can avoid +building any zlib usage or copy at all with `-DLWS_WITH_ZLIB=0` (the +default), and so avoid needing to observe the license for binary +distribution that doesn't include the related code. + +``` + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu +``` + + +## APACHE2 + +For convenience, a copy of the license on the mbedtls wrapper part. In binary +distribution, this applies only when building lws against mbedtls. + +The canonical license application to source files uses the URL reference, so the +whole is not reproduced here. + +``` +// Copyright 2015-2016 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. +``` + +## CC0 + +For convenience,the full text of CC0 dedication found on the lws examples. +The intention of this is to dedicate the examples to the public domain, so +users can build off and modify them without any constraint. + +``` +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: + + the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; + moral rights retained by the original author(s) and/or performer(s); + publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; + rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; + rights protecting the extraction, dissemination, use and reuse of data in a Work; + database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and + other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. + Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. + Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. + Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. +``` + +## OFL: Open Font License + +``` +Copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. +``` diff --git a/components/libwebsockets/examples/client-echo/README.md b/components/libwebsockets/examples/client-echo/README.md new file mode 100644 index 0000000000..f78dc254be --- /dev/null +++ b/components/libwebsockets/examples/client-echo/README.md @@ -0,0 +1,152 @@ +# Websocket LWS client example + +This example will shows how to set up and communicate over a websocket. + +## How to Use Example + +### Hardware Required + +This example can be executed on any ESP32 board, the only required interface is WiFi and connection to internet or a local server. + +### Configure the project + +* Open the project configuration menu (`idf.py menuconfig`) +* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. +* Configure the websocket endpoint URI under "Example Configuration" + +### Server Certificate Verification + +* Mutual Authentication: When `CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y` is enabled, it's essential to provide valid certificates for both the server and client. + This ensures a secure two-way verification process. +* Server-Only Authentication: To perform verification of the server's certificate only (without requiring a client certificate), set `CONFIG_WS_OVER_TLS_SERVER_AUTH=y`. + This method skips client certificate verification. +* Example below demonstrates how to generate a new self signed certificates for the server and client using the OpenSSL command line tool + +Please note: This example represents an extremely simplified approach to generating self-signed certificates/keys with a single common CA, devoid of CN checks, lacking password protection, and featuring hardcoded key sizes and types. It is intended solely for testing purposes. +In the outlined steps, we are omitting the configuration of the CN (Common Name) field due to the context of a testing environment. However, it's important to recognize that the CN field is a critical element of SSL/TLS certificates, significantly influencing the security and efficacy of HTTPS communications. This field facilitates the verification of a website's identity, enhancing trust and security in web interactions. In practical deployments beyond testing scenarios, ensuring the CN field is accurately set is paramount for maintaining the integrity and reliability of secure communications + +### Generating a self signed Certificates with OpenSSL +* The example below outlines the process for creating new certificates for both the server and client using OpenSSL, a widely-used command line tool for implementing TLS protocol: + +``` +Generate the CA's Private Key; +openssl genrsa -out ca_key.pem 2048 + +Create the CA's Certificate +openssl req -new -x509 -days 3650 -key ca_key.pem -out ca_cert.pem + +Generate the Server's Private Key +openssl genrsa -out server_key.pem 2048 + +Generate a Certificate Signing Request (CSR) for the Server +openssl req -new -key server_key.pem -out server_csr.pem + +Sign the Server's CSR with the CA's Certificate +openssl x509 -req -days 3650 -in server_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out server_cert.pem + +Generate the Client's Private Key +openssl genrsa -out client_key.pem 2048 + +Generate a Certificate Signing Request (CSR) for the Client +openssl req -new -key client_key.pem -out client_csr.pem + +Sign the Client's CSR with the CA's Certificate +openssl x509 -req -days 3650 -in client_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out client_cert.pem + +``` + +Expiry time and metadata fields can be adjusted in the invocation. + +Please see the openssl man pages (man openssl) for more details. + +It is **strongly recommended** to not reuse the example certificate in your application; +it is included only for demonstration. + + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (18208) lws-client-echo: LWS minimal ws client echo + +219868: lws_create_context: LWS: 4.3.99-v4.3.0-424-ga74362ff, MbedTLS-3.6.2 NET CLI SRV H1 H2 WS SS-JSON-POL ConMon IPv6-absent +219576: mem: platform fd map: 20 bytes +217880: __lws_lc_tag: ++ [wsi|0|pipe] (1) +216516: __lws_lc_tag: ++ [vh|0|default||-1] (1) +I (18248) lws-client-echo: connect_cb: connecting + +210112: __lws_lc_tag: ++ [wsicli|0|WS/h1/default/echo.websocket.events] (1) +204800: [wsicli|0|WS/h1/default/echo.websocket.events]: lws_client_connect_3_connect: trying 13.248.241.119 +180776: lws_ssl_client_bio_create: allowing selfsigned +I (19998) wifi:idx:0 (ifx:0, b4:89:01:63:9d:08), tid:0, ssn:321, winSize:64 +I (20768) lws-client-echo: WEBSOCKET_EVENT_CONNECTED +I (20768) lws-client-echo: Sending hello 0000 +I (20778) lws-client-echo: WEBSOCKET_EVENT_DATA +W (20778) lws-client-echo: Received=echo.websocket.events sponsored by Lob.com + + +I (20968) lws-client-echo: WEBSOCKET_EVENT_DATA +W (20968) lws-client-echo: Received=hello 0000 + + +I (22978) lws-client-echo: Sending hello 0001 +I (23118) lws-client-echo: WEBSOCKET_EVENT_DATA +W (23118) lws-client-echo: Received=hello 0001 + + +I (23778) lws-client-echo: Sending hello 0002 +I (23938) lws-client-echo: WEBSOCKET_EVENT_DATA +W (23938) lws-client-echo: Received=hello 0002 + + +I (25948) lws-client-echo: Sending hello 0003 +I (26088) lws-client-echo: WEBSOCKET_EVENT_DATA +W (26088) lws-client-echo: Received=hello 0003 + + +I (26948) lws-client-echo: Sending hello 0004 +I (27118) lws-client-echo: WEBSOCKET_EVENT_DATA +W (27118) lws-client-echo: Received=hello 0004 +``` + +## Python Flask echo server + +By default, the `ws://echo.websocket.events` endpoint is used. You can setup a Python websocket echo server locally and try the `ws://:5000` endpoint. To do this, install Flask-sock Python package + +``` +pip install flask-sock +``` + +and start a Flask websocket echo server locally by executing the following Python code: + +```python +from flask import Flask +from flask_sock import Sock + +app = Flask(__name__) +sock = Sock(app) + + +@sock.route('/') +def echo(ws): + while True: + data = ws.receive() + ws.send(data) + + +if __name__ == '__main__': + # To run your Flask + WebSocket server in production you can use Gunicorn: + # gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app + app.run(host="0.0.0.0", debug=True) +``` diff --git a/components/libwebsockets/examples/client-echo/main/CMakeLists.txt b/components/libwebsockets/examples/client-echo/main/CMakeLists.txt new file mode 100644 index 0000000000..a0ada8f0dd --- /dev/null +++ b/components/libwebsockets/examples/client-echo/main/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SRC_FILES "lws-client-echo.c") # Define source files +set(INCLUDE_DIRS ".") # Define include directories +set(EMBED_FILES "") # Initialize an empty list for files to embed + +list(APPEND EMBED_FILES + "certs/client_cert.pem" + "certs/ca_cert.pem" + "certs/client_key.pem") + +idf_component_register(SRCS "${SRC_FILES}" + INCLUDE_DIRS "${INCLUDE_DIRS}" + EMBED_TXTFILES "${EMBED_FILES}") diff --git a/components/libwebsockets/examples/client-echo/main/Kconfig.projbuild b/components/libwebsockets/examples/client-echo/main/Kconfig.projbuild new file mode 100644 index 0000000000..82aa549974 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/main/Kconfig.projbuild @@ -0,0 +1,45 @@ +menu "Example Configuration" + choice WEBSOCKET_URI_SOURCE + prompt "Websocket URI source" + default WEBSOCKET_URI_FROM_STRING + help + Selects the source of the URI used in the example. + + config WEBSOCKET_URI_FROM_STRING + bool "From string" + + config WEBSOCKET_URI_FROM_STDIN + bool "From stdin" + endchoice + + config WEBSOCKET_URI + string "Websocket endpoint URI" + default "echo.websocket.events" + help + URL or IP of websocket endpoint this example connects to and sends echo + config WEBSOCKET_PORT + int "Websocket endpoint PORT" + default 443 + help + Port of websocket endpoint this example connects to and sends echo + + config WS_OVER_TLS_SERVER_AUTH + bool "Enable WebSocket over TLS with Server Certificate Verification Only" + default n + help + Enables WebSocket connections over TLS (WSS) with server certificate verification. + This setting mandates the client to verify the servers certificate, while the server + does not require client certificate verification. + + config WS_OVER_TLS_MUTUAL_AUTH + bool "Enable WebSocket over TLS with Server Client Mutual Authentification" + default y + help + Enables WebSocket connections over TLS (WSS) with server and client mutual certificate verification. + + config WS_OVER_TLS_SKIP_COMMON_NAME_CHECK + bool "Skip common name(CN) check during TLS authentification" + default n + help + Skipping Common Name(CN) check during TLS(WSS) authentification +endmenu diff --git a/components/libwebsockets/examples/client-echo/main/certs/ca_cert.pem b/components/libwebsockets/examples/client-echo/main/certs/ca_cert.pem new file mode 100644 index 0000000000..e9a27099b9 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/main/certs/ca_cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUL04QhbSEt5oNbV4f7CeLLqTCw2gwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA2MjVaFw0zNDAy +MjAwODA2MjVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDjc78SuXAmJeBc0el2/m+2lwtk3J/VrNxHYkhjHa8K +/ybU89VvKGuv9+L3IP67WMguFTaMgivJYUePjfMchtNJLJ+4cR9BkBKH4JnyXDae +s0a5181LxRo8rqcaOw9hmJTgt9R4dIRTR3GN2/VLhlR+L9OTYA54RUtMyMMpyk5M +YIJbcOwiwkVLsIYnexXDfgz9vQGl/2vBQ/RBtDBvbSyBiWox9SuzOrya1HUBzJkM +Iu5L0bSa0LAeXHT3i3P1Y4WPt9ub70OhUNfJtHC+XbGFSEkkQG+lfbXU75XLoMWa +iATMREOcb3Mq+pn1G8o1ZHVc6lBHUkfrNfxs5P/GQcSvAgMBAAGjUzBRMB0GA1Ud +DgQWBBQGkdK2gR2HrQTnZnbuWO7I1+wdxDAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn +ZnbuWO7I1+wdxDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBx +G0hFtMwV/agIwC3ZaYC36ZWiijFzWkJSZG+fqAy32mSoVL2uQvOT8vEfF0ZnAcPc +JI4oI059dBhAVlwqv6uLHyD4Gf2bF4oSLljdTz3X23llF+/wrTC2LLqMrm09aUC0 +ac74Q0FVwVJJcqH1HgemCMVjna5MkwNA6B+q7uR3eQ692VqXk6vjd4fRLBg1bBO1 +hXjasfNxA8A9quORF5+rjYrwyUZHuzcs0FfSClckIt4tHKtt4moLufOW6/PM4fRe +AgdDfiTupxYLJFz4hFPhfgCh4TjQ+f9+uP4IAjW42dJmTVZjLEku/hm5lxCFObAq +RgfaNwH8Ug1r1xswjSZG +-----END CERTIFICATE----- diff --git a/components/libwebsockets/examples/client-echo/main/certs/client_cert.pem b/components/libwebsockets/examples/client-echo/main/certs/client_cert.pem new file mode 100644 index 0000000000..e99921a3c4 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/main/certs/client_cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIUUPCOgMA2v09E29fCkogx3RUBRtEwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA3MzFaFw0zNDAy +MjAwODA3MzFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCrNeomxI2aoP+4iUy5SiA+41oHUDZDFeJOBjv5JCsK +mlvFqxE9zynmPOVpuABErOJwzerPTa4NYKvuvs5CxVJUV5CXtWANuu9majioZNzj +f877MDNX/GnZHK2gnkxVrZCPaDmx9yiMsFMXgmfdrDhwoUpXbdgSyeU/al9Ds2kF +0hrHOH2LBWt/mVeLbONU5CC1HOdVVw+uRlhVlxnfhTPd/Nru3rJx7R0sN7qXcZpJ +PL87WvrszLVOux24DeaOz9oiD2b7egFyUuq1BM25iCwi8s/Ths8xd0Ca1d8mEcHW +FVd4w2+nUMXFE+IbP+wo6FXuiSaOBNri3rztpvCCMaWjAgMBAAGjQjBAMB0GA1Ud +DgQWBBSOlA+9Vfbcfy8iS4HSd4V0KPtm4jAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn +ZnbuWO7I1+wdxDANBgkqhkiG9w0BAQsFAAOCAQEAOmzm/MwowKTrSpMSrmfA3MmW +ULzsfa25WyAoTl90ATlg4653Y7pRaNfdvVvyi2V2LlPcmc7E0rfD53t1NxjDH1uM +LgFMTNEaZ9nMRSW0kMiwaRpvmXS8Eb9PXfvIM/Mw0co/aMOtAQnfTGIqsgkQwKyk +1GG7QKQq3p4QGu5ZaTnjnaoa79hODt+0xQDD1wp6C9xwBY0M4gndAi3wkOeFkGv+ +OmGPtaCBu5V9tJCZ9dfZvjkaK44NGwDw0urAcYRK2h7asnlflu7cnlGMBB0qY4kQ +BX5WI8UjN6rECBHbtNRvEh06ogDdHbxYV+TibrqkkeDRw6HX1qqiEJ+iCgWEDQ== +-----END CERTIFICATE----- diff --git a/components/libwebsockets/examples/client-echo/main/certs/client_key.pem b/components/libwebsockets/examples/client-echo/main/certs/client_key.pem new file mode 100644 index 0000000000..68dcc7af61 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/main/certs/client_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCrNeomxI2aoP+4 +iUy5SiA+41oHUDZDFeJOBjv5JCsKmlvFqxE9zynmPOVpuABErOJwzerPTa4NYKvu +vs5CxVJUV5CXtWANuu9majioZNzjf877MDNX/GnZHK2gnkxVrZCPaDmx9yiMsFMX +gmfdrDhwoUpXbdgSyeU/al9Ds2kF0hrHOH2LBWt/mVeLbONU5CC1HOdVVw+uRlhV +lxnfhTPd/Nru3rJx7R0sN7qXcZpJPL87WvrszLVOux24DeaOz9oiD2b7egFyUuq1 +BM25iCwi8s/Ths8xd0Ca1d8mEcHWFVd4w2+nUMXFE+IbP+wo6FXuiSaOBNri3rzt +pvCCMaWjAgMBAAECggEAOTWjz16AXroLmRMv8v5E9h6sN6Ni7lnCrAXDRoYCZ+Ga +Ztu5wCiYPJn+oqvcUxZd+Ammu6yeS1QRP468h20+DHbSFw+BUDU1x8gYtJQ3h0Fu +3VqG3ZC3odfGYNRkd4CuvGy8Uq5e+1vz9/gYUuc4WNJccAiBWg3ir6UQviOWJV46 +LGfdEd9hVvIGl5pmArMBVYdpj9+JHunDtG4uQxiWla5pdLjlkC2mGexD18T9d718 +6I+o3YHv1Y9RPT1d4rNhYQWx6YdTTD2rmS7nTrzroj/4fXsblpXzR+/l7crlNERY +67RMPwgDR1NiAbCAJKsSbMS66lRCNlhTM4YffGAN6QKBgQDkIdcNm9j49SK5Wbl5 +j8U6UbcVYPzPG+2ea+fDfUIafA0VQHIuX6FgA17Kp7BDX9ldKtSBpr0Z8vetVswr +agmXVMR/7QdvnZ9NpL66YA/BRs67CvsryVu4AVAzThFGySmlcXGlPq47doWDQ3B9 +0BOEnVoeDXR3SabaNsEbhDYn1wKBgQDAIAUyhJcgz+LcgaAtBwdnEN57y66JlRVZ +bsb6cEG/MNmnLjQYsplJjNbz4yrB5ukTChPTGRF/JQRqHoXh6DGQFHvobukwwA6x +RAIIq0NLJ5HUipfOi+VpCbWUHdoUNhwjAB2qVtD4LXE2Lyn46C8ET5eRtRjUKpzV +lpsq63KHFQKBgFB+cDbpCoGtXPcxZXQy+lA9jPAKLKmXHRyMzlX32F8n7iXVe3RJ +YdNS3Rt8V4EuTK/G8PxeLNL/G80ZlyiqXX/79Ol+ZOVJJHBs9K8mPejgZwkwMrec +cLRYIkg3/3iOehdaE9NOboOkqi9KmGKMDJb6PlXkQXflkO3l6/UdjU45AoGAen0v +sxiTncjMU1eVfn+nuY8ouXaPbYoOFXmqBItDb5i+e3baohBj6F+Rv+ZKIVuNp6Ta +JNErtYstOFcDdpbp2nkk0ni71WftNhkszsgZ3DV7JS3DQV0xwvj8ulUZ757b63is +cShujHu0XR5OvTGSoEX6VVxHWyVb3lTp0sBPwU0CgYBe2Ieuya0X8mAbputFN64S +Kv++dqktTUT8i+tp07sIrpDeYwO3D89x9kVSJj4ImlmhiBVGkxFWPkpGyBLotdse +Ai/E6f5I7CDSZZC0ZucgcItNd4Yy459QY+dFwFtT3kIaD9ml8fnqQ83J9W8DWtv9 +6mY9FnUUufbJcpHxN58RTw== +-----END PRIVATE KEY----- diff --git a/components/libwebsockets/examples/client-echo/main/certs/server/server_cert.pem b/components/libwebsockets/examples/client-echo/main/certs/server/server_cert.pem new file mode 100644 index 0000000000..cb1e9dfe74 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/main/certs/server/server_cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIUUPCOgMA2v09E29fCkogx3RUBRtAwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA2NTlaFw0zNDAy +MjAwODA2NTlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC8WWbDxnLzTSfuQaO+kQnnzbwjhUHWn58s+BIEaO8M +GG6bX+8r/SH9XjMfFS36qAN3qxgRun3YoRTaHc2QByiGjf5IL4EAPDnLN+NzUIL5 +7Gi2QPQP/GksAsOGKWk/nMRPk1vcMptkFVIWSp474SQ0A92Z9z0dUIqBpjRa34kr +HsAIcT59/EG7YBBadMk0fQIxQVLh3Vosky85q+0waFihe47Ef5U2UftexoUx4Vcz +6EtP60Wx+4qN+FLsr+n2B7Oz2ITqfwgqLzjNLZwm9bMjcLZ0fWm1A/W1C989MXwI +w6DAPEZv7pbgp8r9phyrNieSDuuRaCvFsaXh6troAjLxAgMBAAGjQjBAMB0GA1Ud +DgQWBBRJCYAQG2+1FN5P/wyAR1AsrAyb4DAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn +ZnbuWO7I1+wdxDANBgkqhkiG9w0BAQsFAAOCAQEAmllul/GIH7RVq85mM/SxP47J +M7Z7T032KuR3n/Psyv2iq/uEV2CUje3XrKNwR2PaJL4Q6CtoWy7xgIP+9CBbjddR +M7sdNQab8P2crAUtBKnkNOl/na/5KnXnjwi/PmWJJ9i2Cqt0PPkaykTWp/MLfYIw +RPkY2Yo8f8gEiqXQd+0qTuMgumbgkPq3V8Lk1ocy62F5/qUhXxH+ifAXEoUQS6EG +8DlgwdZlfUY+jeM6N56WzYmxD1syjNW7faPio+qXINfpYatROhqphaMQ5SA6TRj6 +jcnLa31TdDdWmWYDcYgZntAv6yGi3rh0MdYqeNS0FKlMKmaH81VHs7V1UUXwUQ== +-----END CERTIFICATE----- diff --git a/components/libwebsockets/examples/client-echo/main/certs/server/server_key.pem b/components/libwebsockets/examples/client-echo/main/certs/server/server_key.pem new file mode 100644 index 0000000000..cf2fdadb7a --- /dev/null +++ b/components/libwebsockets/examples/client-echo/main/certs/server/server_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8WWbDxnLzTSfu +QaO+kQnnzbwjhUHWn58s+BIEaO8MGG6bX+8r/SH9XjMfFS36qAN3qxgRun3YoRTa +Hc2QByiGjf5IL4EAPDnLN+NzUIL57Gi2QPQP/GksAsOGKWk/nMRPk1vcMptkFVIW +Sp474SQ0A92Z9z0dUIqBpjRa34krHsAIcT59/EG7YBBadMk0fQIxQVLh3Vosky85 +q+0waFihe47Ef5U2UftexoUx4Vcz6EtP60Wx+4qN+FLsr+n2B7Oz2ITqfwgqLzjN +LZwm9bMjcLZ0fWm1A/W1C989MXwIw6DAPEZv7pbgp8r9phyrNieSDuuRaCvFsaXh +6troAjLxAgMBAAECggEACNVCggTxCCMCr+RJKxs/NS1LWPkbZNbYjrHVmnpXV6Bf +s460t0HoUasUx6zlGp+9heOyvcYat8maIj6KkOodBu5q0fTUXm/0n+ivlI1ejxz8 +ritupr9GKWe5xrVzd6XA+SBmivWenvt2/Y+jSxica4oQ3vMe3RyVWk4yn15jXu+9 +7B9lNyNeZtOBr6OozHGLYw4dwWcBNv2S6wevRKfHPwn/Ch5yTH1uAskgoMxUuyK2 +ynNVHWUhyS4pFU7Tex5ENDel15VYdbxV/2lQ2W6fHMLtC5GWKJXXbigCX7pfOpzC +BFJEfZl7ze/qptE9AR7DkLFYyMtrS7OlebYbLDOM9wKBgQD+rTdwULZibpKwlI3a +9Y22d4N/EDFvuu8LnuEiVQnXgwg9M+tlaa2liP18j1a7y/FCfoXf5sjUWCsdYR6d +C0TuiOGI59hYGI94NvVLAmOutR+vJ/3jhbv5wyqEQLhJ42Yz9kWBrDCI+V3q3TdO +H7wcH6suUIZpeLEJF4qHzY/1dwKBgQC9U/Pvswiww8sfysmd5shUNo4ofAZnTM1A +ak6pWE3lSyiOkSm+3B2GqxYWLRoo1v+pTyhhXDtRRmxGtMNrKCsmlHef/o3c6kkG +cuC2h/DiSmoITHy3BYKJoDeE54E8ubXUUKqHo41LYUs+D7M/IGxeiO13MUoIrEtF +AwzVWPBU1wKBgH8barD2x6Bm+XWCHy6qIZlxGsMfDN1r2gTdvhWJhcj3D/Sj5heO +X+lfbsxtKee+yOHcDesK3y8D9jjKkSHmTvgSfyX6OML3NxvTqidOwPugUHj2J8QX +qhLk8mJhftj50reacWRf0TV76ADhecnXEuaic6hA7mTTpOAZzL0svm3PAoGBALWF +r6VLX3KzVqZVtLb7FWmAoQ35093pCgXPpznAW3cTd4Axd/fxbTG4CUYb2i/760X2 +ij3Gw2yqe5fTKmYsLisgQA2bb4K28msHa6I2dmNQe5cXVp/X3Y98mJ6JpCSH3ekB +qm7ABfGXCCApx28n9B8zY5JbJKNqJgS15vELA+ojAoGAAkaV2w46+3iQ6gJtQepr +zGNybiYBx/Wo5fDdTS5u0xN+ZdC9fl2Zs0n7sMmUT8bWdDLcMnntHHO+oDIKyRHs +TQh1n68vQ4JoegQv3Z9Z/TLEKqr9gyJC1Ao6M4bZpPhUWQwupfHColtsr2TskcnJ +Nf2FpJZ7z6fQEShGlK1yTXM= +-----END PRIVATE KEY----- diff --git a/components/libwebsockets/examples/client-echo/main/idf_component.yml b/components/libwebsockets/examples/client-echo/main/idf_component.yml new file mode 100644 index 0000000000..959a1dfb33 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + espressif/libwebsockets: + version: "*" + override_path: "../../../" + protocol_examples_common: + path: ${IDF_PATH}/examples/common_components/protocol_examples_common diff --git a/components/libwebsockets/examples/client-echo/main/lws-client-echo.c b/components/libwebsockets/examples/client-echo/main/lws-client-echo.c new file mode 100644 index 0000000000..f52cbc8788 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/main/lws-client-echo.c @@ -0,0 +1,310 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* ESP libwebsockets client example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include + +#include "esp_wifi.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "protocol_examples_common.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_log.h" +#include "esp_task_wdt.h" + +#include + +static uint32_t disconnect_timeout = 5000000; // microseconds +static uint8_t message_count = 0; +static const char *TAG = "lws-client-echo"; + +static struct lws_context *context; +static struct lws *client_wsi; + +static lws_sorted_usec_list_t sul; +static unsigned char msg[LWS_PRE + 128]; + +static const lws_retry_bo_t retry = { + .secs_since_valid_ping = 3, + .secs_since_valid_hangup = 10, +}; + +#if CONFIG_WEBSOCKET_URI_FROM_STDIN +static void get_string(char *line, size_t size) +{ + int count = 0; + while (count < size) { + int c = fgetc(stdin); + if (c == '\n') { + line[count] = '\0'; + break; + } else if (c > 0 && c < 127) { + line[count] = c; + ++count; + } + vTaskDelay(10 / portTICK_PERIOD_MS); + esp_task_wdt_reset(); + } +} + +#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */ + +static void send_data(struct lws *wsi, const char *data, size_t len, enum lws_write_protocol type) +{ + unsigned char buf[LWS_PRE + len]; + unsigned char *p = &buf[LWS_PRE]; + memcpy(p, data, len); + + int n = lws_write(wsi, p, len, type); + if (n < len) { + ESP_LOGE(TAG, "ERROR %d writing ws\n", n); + } +} + +static void send_large_text_data(struct lws *wsi) +{ + const int size = 2000; + char *long_data = malloc(size); + memset(long_data, 'a', size); + send_data(wsi, long_data, size, LWS_WRITE_TEXT); + free(long_data); +} + +static void send_fragmented_text_data(struct lws *wsi) +{ + char data[32]; + memset(data, 'a', sizeof(data)); + send_data(wsi, data, sizeof(data), LWS_WRITE_TEXT | LWS_WRITE_NO_FIN); + memset(data, 'b', sizeof(data)); + send_data(wsi, data, sizeof(data), LWS_WRITE_CONTINUATION); +} + +static void send_fragmented_binary_data(struct lws *wsi) +{ + char binary_data[5]; + memset(binary_data, 0, sizeof(binary_data)); + send_data(wsi, binary_data, sizeof(binary_data), LWS_WRITE_BINARY | LWS_WRITE_NO_FIN); + memset(binary_data, 1, sizeof(binary_data)); + send_data(wsi, binary_data, sizeof(binary_data), LWS_WRITE_CONTINUATION); +} + +static void connect_cb(lws_sorted_usec_list_t *_sul) +{ + struct lws_client_connect_info connect_info; + + ESP_LOGI(TAG, "%s: connecting\n", __func__); + + memset(&connect_info, 0, sizeof(connect_info)); +#if CONFIG_WEBSOCKET_URI_FROM_STDIN + char line[128]; + + ESP_LOGI(TAG, "Please enter uri of websocket endpoint"); + get_string(line, sizeof(line)); + + connect_info.address = line; + ESP_LOGI(TAG, "Endpoint uri: %s\n", line); + +#else + connect_info.address = CONFIG_WEBSOCKET_URI; +#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */ + + + connect_info.context = context; + connect_info.port = CONFIG_WEBSOCKET_PORT; + connect_info.host = connect_info.address; + connect_info.origin = connect_info.address; + connect_info.local_protocol_name = "lws-echo"; + connect_info.pwsi = &client_wsi; + connect_info.retry_and_idle_policy = &retry; + +#if defined(CONFIG_WS_OVER_TLS_MUTUAL_AUTH) || defined(CONFIG_WS_OVER_TLS_SERVER_AUTH) + connect_info.ssl_connection = LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED; + +#if defined(CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK) && defined(CONFIG_WS_OVER_TLS_SERVER_AUTH) + connect_info.ssl_connection |= LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; +#endif +#else + connect_info.ssl_connection = LCCSCF_ALLOW_INSECURE; +#endif + + if (!lws_client_connect_via_info(&connect_info)) { + lws_sul_schedule(context, 0, _sul, connect_cb, 5 * LWS_USEC_PER_SEC); + } +} + +static int callback_minimal_echo(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch (reason) { + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + ESP_LOGE(TAG, "CLIENT_CONNECTION_ERROR: %s\n", + in ? (char *)in : "(null)"); + lws_sul_schedule(context, 0, &sul, connect_cb, 5 * LWS_USEC_PER_SEC); + break; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED"); + lws_callback_on_writable(wsi); + + break; + + case LWS_CALLBACK_CLIENT_WRITEABLE: + if (message_count < 5) { + char text_data[32]; + sprintf(text_data, "hello %04d", message_count++); + ESP_LOGI(TAG, "Sending text: %s", text_data); + send_data(wsi, text_data, strlen(text_data), LWS_WRITE_TEXT); + } else if (message_count == 5) { + ESP_LOGI(TAG, "Sending fragmented text message"); + send_fragmented_text_data(wsi); + ESP_LOGI(TAG, "Sending fragmented binary message"); + send_fragmented_binary_data(wsi); + ESP_LOGI(TAG, "Sending text longer than ws buffer (1024)"); + send_large_text_data(wsi); + message_count++; + } + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); + + if (lws_frame_is_binary(wsi)) { + ESP_LOGI(TAG, "Received binary data"); + ESP_LOG_BUFFER_HEX("Received binary data", in, len); + } else { + ESP_LOGW(TAG, "Received=%.*s\n\n", len, (char *)in); + } + + size_t remain = lws_remaining_packet_payload(wsi); + + // If received data is larger than the ws buffer + if (remain > 0) { + ESP_LOGW(TAG, "Total payload length=%u, data_len=%u\n\n", remain + len, len); + } + + // If received data contains json structure it succeed to parse + cJSON *root = cJSON_Parse(in); + if (root) { + for (int i = 0 ; i < cJSON_GetArraySize(root) ; i++) { + cJSON *elem = cJSON_GetArrayItem(root, i); + cJSON *id = cJSON_GetObjectItem(elem, "id"); + cJSON *name = cJSON_GetObjectItem(elem, "name"); + ESP_LOGW(TAG, "Json={'id': '%s', 'name': '%s'}", id->valuestring, name->valuestring); + } + cJSON_Delete(root); + } + + /* Reset the timeout*/ + lws_set_timer_usecs(wsi, disconnect_timeout); + break; + case LWS_CALLBACK_TIMER: + ESP_LOGW(TAG, "Closing connection"); + lws_close_reason(wsi, LWS_CLOSE_STATUS_NORMAL, (unsigned char *)"bye", 3); + /* Return non-null to close the connection. */ + return -1; + break; + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +static const struct lws_protocols protocols[] = { + { + "lws-echo", + callback_minimal_echo, + 1024, 1024, 0, NULL, 0 + }, + LWS_PROTOCOL_LIST_TERM +}; + +int app_main(void) +{ + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + esp_log_level_set("*", ESP_LOG_INFO); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + /* Configure WDT. */ + TaskHandle_t handle = xTaskGetCurrentTaskHandle(); + esp_task_wdt_add(handle); + + /* Create LWS Context - Client. */ + struct lws_context_creation_info info; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + + memset(msg, 'x', sizeof(msg)); + + lws_set_log_level(logs, NULL); + ESP_LOGI(TAG, "LWS minimal ws client echo\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ + info.protocols = protocols; + info.fd_limit_per_thread = 1 + 1 + 1; + +#if CONFIG_WS_OVER_TLS_MUTUAL_AUTH + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + + /* Configuring client certificates for mutual authentification */ + extern const char cert_start[] asm("_binary_client_cert_pem_start"); // Client certificate + extern const char cert_end[] asm("_binary_client_cert_pem_end"); + extern const char key_start[] asm("_binary_client_key_pem_start"); // Client private key + extern const char key_end[] asm("_binary_client_key_pem_end"); + + info.client_ssl_cert_mem = cert_start; + info.client_ssl_cert_mem_len = cert_end - cert_start; + info.client_ssl_key_mem = key_start; + info.client_ssl_key_mem_len = key_end - key_start; +#elif CONFIG_WS_OVER_TLS_SERVER_AUTH + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + + extern const char cacert_start[] asm("_binary_ca_cert_pem_start"); // CA certificate + extern const char cacert_end[] asm("_binary_ca_cert_pem_end"); + + info.client_ssl_ca_mem = cacert_start; + info.client_ssl_ca_mem_len = cacert_end - cacert_start; +#endif + + context = lws_create_context(&info); + if (!context) { + ESP_LOGE(TAG, "lws init failed\n"); + return 1; + } + + lws_sul_schedule(context, 0, &sul, connect_cb, 100); + + while (n >= 0) { + n = lws_service(context, 0); + } + lws_context_destroy(context); + + return 0; +} diff --git a/components/libwebsockets/examples/client-echo/pytest_websocket.py b/components/libwebsockets/examples/client-echo/pytest_websocket.py new file mode 100644 index 0000000000..452a043550 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/pytest_websocket.py @@ -0,0 +1,237 @@ +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import json +import random +import re +import socket +import ssl +import string +import sys +from threading import Event, Thread + +from SimpleWebSocketServer import (SimpleSSLWebSocketServer, + SimpleWebSocketServer, WebSocket) + + +def get_my_ip(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('8.8.8.8', 1)) + IP = s.getsockname()[0] + except Exception: + IP = '127.0.0.1' + finally: + s.close() + return IP + + +class WebsocketTestEcho(WebSocket): + def handleMessage(self): + if isinstance(self.data, bytes): + print(f'\n Server received binary data: {self.data.hex()}\n') + self.sendMessage(self.data, binary=True) + else: + print(f'\n Server received: {self.data}\n') + self.sendMessage(self.data) + + def handleConnected(self): + print('Connection from: {}'.format(self.address)) + + def handleClose(self): + print('{} closed the connection'.format(self.address)) + + +# Simple Websocket server for testing purposes +class Websocket(object): + + def send_data(self, data): + for nr, conn in self.server.connections.items(): + conn.sendMessage(data) + + def run(self): + if self.use_tls is True: + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ssl_context.load_cert_chain(certfile='main/certs/server/server_cert.pem', keyfile='main/certs/server/server_key.pem') + if self.client_verify is True: + ssl_context.load_verify_locations(cafile='main/certs/ca_cert.pem') + ssl_context.verify = ssl.CERT_REQUIRED + ssl_context.check_hostname = False + self.server = SimpleSSLWebSocketServer('', self.port, WebsocketTestEcho, ssl_context=ssl_context) + else: + self.server = SimpleWebSocketServer('', self.port, WebsocketTestEcho) + while not self.exit_event.is_set(): + self.server.serveonce() + + def __init__(self, port, use_tls, verify): + self.port = port + self.use_tls = use_tls + self.client_verify = verify + self.exit_event = Event() + self.thread = Thread(target=self.run) + self.thread.start() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.exit_event.set() + self.thread.join(10) + if self.thread.is_alive(): + print('Thread cannot be joined', 'orange') + + +def test_examples_protocol_websocket(dut): + """ + steps: + 1. obtain IP address + 2. connect to uri specified in the config + 3. send and receive data + """ + + # Test for echo functionality: + # Sends a series of simple "hello" messages to the WebSocket server and verifies that each one is echoed back correctly. + # This tests the basic responsiveness and correctness of the WebSocket connection. + def test_echo(dut): + dut.expect('WEBSOCKET_EVENT_CONNECTED') + for i in range(0, 5): + dut.expect(re.compile(b'Received=hello (\\d)')) + print('All echos received') + sys.stdout.flush() + + # Test for clean closure of the WebSocket connection: + # Ensures that the WebSocket can correctly receive a close frame and terminate the connection without issues. + def test_close(dut): + dut.expect('__lws_lc_untag') + + # Test for JSON message handling: + # Sends a JSON formatted string and verifies that the received message matches the expected JSON structure. + def test_json(dut, websocket): + json_string = """ + [ + { + "id":"1", + "name":"user1" + }, + { + "id":"2", + "name":"user2" + } + ] + """ + websocket.send_data(json_string) + data = json.loads(json_string) + + match = dut.expect( + re.compile(b'Json=({[a-zA-Z0-9]*).*}')).group(0).decode()[5:] + if match == str(data[0]): + print('\n Sent message and received message are equal \n') + sys.stdout.flush() + else: + raise ValueError( + 'DUT received string do not match sent string, \nexpected: {}\nwith length {}\ + \nreceived: {}\nwith length {}'.format( + data[0], len(data[0]), match, len(match))) + + # Test for receiving long messages: + # This sends a message with a specified length (2000 characters) to ensure the WebSocket can handle large data payloads. Repeated 3 times for reliability. + def test_recv_long_msg(dut, websocket, msg_len, repeats): + + send_msg = ''.join( + random.choice(string.ascii_uppercase + string.ascii_lowercase + + string.digits) for _ in range(msg_len)) + + for _ in range(repeats): + websocket.send_data(send_msg) + + recv_msg = '' + while len(recv_msg) < msg_len: + match = dut.expect(re.compile( + b'Received=([a-zA-Z0-9]*).*\n')).group(1).decode() + recv_msg += match + + if recv_msg == send_msg: + print('\n Sent message and received message are equal \n') + sys.stdout.flush() + else: + raise ValueError( + 'DUT received string do not match sent string, \nexpected: {}\nwith length {}\ + \nreceived: {}\nwith length {}'.format( + send_msg, len(send_msg), recv_msg, len(recv_msg))) + + # Test for receiving the first fragment of a large message: + # Verifies the WebSocket's ability to correctly process the initial segment of a fragmented message. + def test_recv_fragmented_msg1(dut): + dut.expect('Total payload length=2000, data_len=1024') + + # Test for receiving fragmented text messages: + # Checks if the WebSocket can accurately reconstruct a message sent in several smaller parts. + def test_fragmented_txt_msg(dut): + dut.expect('Received=' + 32 * 'a' + 32 * 'b') + print('\nFragmented data received\n') + + # Extract the hexdump portion of the log line + def parse_hexdump(line): + match = re.search(r'\(.*\) Received binary data: ([0-9A-Fa-f ]+)', line) + if match: + hexdump = match.group(1).strip().replace(' ', '') + # Convert the hexdump string to a bytearray + return bytearray.fromhex(hexdump) + return bytearray() + + # Capture the binary log output from the DUT + def test_fragmented_binary_msg(dut): + match = dut.expect(r'\(.*\) Received binary data: .*') + if match: + line = match.group(0).strip() + if isinstance(line, bytes): + line = line.decode('utf-8') + + # Parse the hexdump from the log line + received_data = parse_hexdump(line) + + # Create the expected bytearray with the specified pattern + expected_data = bytearray([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) + + # Validate the received data + assert received_data == expected_data, f'Received data does not match expected data. Received: {received_data}, Expected: {expected_data}' + print('\nFragmented data received\n') + else: + assert False, 'Log line with binary data not found' + + # Starting of the test + try: + if dut.app.sdkconfig.get('WEBSOCKET_URI_FROM_STDIN') is True: + uri_from_stdin = True + else: + uri = dut.app.sdkconfig['WEBSOCKET_URI'] + uri_from_stdin = False + + if dut.app.sdkconfig.get('WS_OVER_TLS_MUTUAL_AUTH') is True: + use_tls = True + client_verify = True + else: + use_tls = False + client_verify = False + + except Exception: + print('ENV_TEST_FAILURE: Cannot find uri settings in sdkconfig') + raise + + if uri_from_stdin: + server_port = 8080 + with Websocket(server_port, use_tls, client_verify) as ws: + uri = '{}'.format(get_my_ip()) + print('DUT connecting to {}'.format(uri)) + dut.expect('Please enter uri of websocket endpoint', timeout=30) + dut.write(uri) + test_echo(dut) + test_recv_long_msg(dut, ws, 2000, 3) + test_json(dut, ws) + test_fragmented_txt_msg(dut) + test_fragmented_binary_msg(dut) + test_recv_fragmented_msg1(dut) + test_close(dut) + else: + print('DUT connecting to {}'.format(uri)) + test_echo(dut) diff --git a/components/libwebsockets/examples/client-echo/sdkconfig.ci b/components/libwebsockets/examples/client-echo/sdkconfig.ci new file mode 100644 index 0000000000..2c3b8ccaa3 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/sdkconfig.ci @@ -0,0 +1,13 @@ +CONFIG_IDF_TARGET="esp32" +CONFIG_IDF_TARGET_LINUX=n +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 +CONFIG_EXAMPLE_CONNECT_IPV6=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=5120 diff --git a/components/libwebsockets/examples/client-echo/sdkconfig.ci.mutual_auth b/components/libwebsockets/examples/client-echo/sdkconfig.ci.mutual_auth new file mode 100644 index 0000000000..a4be691edd --- /dev/null +++ b/components/libwebsockets/examples/client-echo/sdkconfig.ci.mutual_auth @@ -0,0 +1,18 @@ +CONFIG_IDF_TARGET="esp32" +CONFIG_IDF_TARGET_LINUX=n +CONFIG_WEBSOCKET_URI_FROM_STDIN=y +CONFIG_WEBSOCKET_URI_FROM_STRING=n +CONFIG_WEBSOCKET_PORT=8080 +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 +CONFIG_EXAMPLE_CONNECT_IPV6=y +CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y +CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=5120 diff --git a/components/libwebsockets/examples/client-echo/sdkconfig.ci.plain_tcp b/components/libwebsockets/examples/client-echo/sdkconfig.ci.plain_tcp new file mode 100644 index 0000000000..eb6c3e4de7 --- /dev/null +++ b/components/libwebsockets/examples/client-echo/sdkconfig.ci.plain_tcp @@ -0,0 +1,18 @@ +CONFIG_IDF_TARGET="esp32" +CONFIG_IDF_TARGET_LINUX=n +CONFIG_WEBSOCKET_URI_FROM_STDIN=y +CONFIG_WEBSOCKET_URI_FROM_STRING=n +CONFIG_WEBSOCKET_PORT=8080 +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 +CONFIG_EXAMPLE_CONNECT_IPV6=y +CONFIG_WS_OVER_TLS_MUTUAL_AUTH=n +CONFIG_WS_OVER_TLS_SERVER_AUTH=n +CONFIG_ESP_MAIN_TASK_STACK_SIZE=5120 diff --git a/components/libwebsockets/examples/server-echo/CMakeLists.txt b/components/libwebsockets/examples/server-echo/CMakeLists.txt new file mode 100644 index 0000000000..7cf24f7122 --- /dev/null +++ b/components/libwebsockets/examples/server-echo/CMakeLists.txt @@ -0,0 +1,7 @@ +# 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.16) +set(requirements 1) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(server_echo_example) diff --git a/components/libwebsockets/examples/server-echo/README.md b/components/libwebsockets/examples/server-echo/README.md new file mode 100644 index 0000000000..5655d82b41 --- /dev/null +++ b/components/libwebsockets/examples/server-echo/README.md @@ -0,0 +1,66 @@ +# Websocket LWS server example + +This example will shows how to set up and communicate over a websocket. + +## How to Use Example + +### Hardware Required + +This example can be executed on any ESP32 board, the only required interface is WiFi and connection to internet or a local server. + +### Configure the project + +* Open the project configuration menu (`idf.py menuconfig`) +* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. + +### Server Certificate Verification + + +### Generating a self signed Certificates with OpenSSL + + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + + +## Python Flask echo server + +By default, the `ws://echo.websocket.events` endpoint is used. You can setup a Python websocket echo server locally and try the `ws://:5000` endpoint. To do this, install Flask-sock Python package + +``` +pip install flask-sock +``` + +and start a Flask websocket echo server locally by executing the following Python code: + +```python +from flask import Flask +from flask_sock import Sock + +app = Flask(__name__) +sock = Sock(app) + + +@sock.route('/') +def echo(ws): + while True: + data = ws.receive() + ws.send(data) + + +if __name__ == '__main__': + # To run your Flask + WebSocket server in production you can use Gunicorn: + # gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app + app.run(host="0.0.0.0", debug=True) +``` diff --git a/components/libwebsockets/examples/server-echo/main/CMakeLists.txt b/components/libwebsockets/examples/server-echo/main/CMakeLists.txt new file mode 100644 index 0000000000..fb34250123 --- /dev/null +++ b/components/libwebsockets/examples/server-echo/main/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SRC_FILES "lws-server-echo.c") # Initialize SRC_FILES as an empty list +set(INCLUDE_DIRS ".") # Define include directories +set(EMBED_FILES) # Initialize EMBED_FILES as an empty list + +list(APPEND EMBED_FILES + "certs/server_cert.pem" + "certs/ca_cert.pem" + "certs/server_key.pem") + +idf_component_register(SRCS "${SRC_FILES}" + INCLUDE_DIRS "${INCLUDE_DIRS}" + EMBED_TXTFILES "${EMBED_FILES}") diff --git a/components/libwebsockets/examples/server-echo/main/Kconfig.projbuild b/components/libwebsockets/examples/server-echo/main/Kconfig.projbuild new file mode 100644 index 0000000000..decb720bfc --- /dev/null +++ b/components/libwebsockets/examples/server-echo/main/Kconfig.projbuild @@ -0,0 +1,12 @@ +menu "Example Configuration" + config WEBSOCKET_PORT + int "Websocket endpoint PORT" + default 80 + help + Port of websocket endpoint this example connects to and sends echo + config WS_OVER_TLS + bool "Enable WebSocket over TLS with Server Certificate" + default y + help + Enables WebSocket connections over TLS (WSS) +endmenu diff --git a/components/libwebsockets/examples/server-echo/main/certs/ca_cert.pem b/components/libwebsockets/examples/server-echo/main/certs/ca_cert.pem new file mode 100644 index 0000000000..e9a27099b9 --- /dev/null +++ b/components/libwebsockets/examples/server-echo/main/certs/ca_cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUL04QhbSEt5oNbV4f7CeLLqTCw2gwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA2MjVaFw0zNDAy +MjAwODA2MjVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDjc78SuXAmJeBc0el2/m+2lwtk3J/VrNxHYkhjHa8K +/ybU89VvKGuv9+L3IP67WMguFTaMgivJYUePjfMchtNJLJ+4cR9BkBKH4JnyXDae +s0a5181LxRo8rqcaOw9hmJTgt9R4dIRTR3GN2/VLhlR+L9OTYA54RUtMyMMpyk5M +YIJbcOwiwkVLsIYnexXDfgz9vQGl/2vBQ/RBtDBvbSyBiWox9SuzOrya1HUBzJkM +Iu5L0bSa0LAeXHT3i3P1Y4WPt9ub70OhUNfJtHC+XbGFSEkkQG+lfbXU75XLoMWa +iATMREOcb3Mq+pn1G8o1ZHVc6lBHUkfrNfxs5P/GQcSvAgMBAAGjUzBRMB0GA1Ud +DgQWBBQGkdK2gR2HrQTnZnbuWO7I1+wdxDAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn +ZnbuWO7I1+wdxDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBx +G0hFtMwV/agIwC3ZaYC36ZWiijFzWkJSZG+fqAy32mSoVL2uQvOT8vEfF0ZnAcPc +JI4oI059dBhAVlwqv6uLHyD4Gf2bF4oSLljdTz3X23llF+/wrTC2LLqMrm09aUC0 +ac74Q0FVwVJJcqH1HgemCMVjna5MkwNA6B+q7uR3eQ692VqXk6vjd4fRLBg1bBO1 +hXjasfNxA8A9quORF5+rjYrwyUZHuzcs0FfSClckIt4tHKtt4moLufOW6/PM4fRe +AgdDfiTupxYLJFz4hFPhfgCh4TjQ+f9+uP4IAjW42dJmTVZjLEku/hm5lxCFObAq +RgfaNwH8Ug1r1xswjSZG +-----END CERTIFICATE----- diff --git a/components/libwebsockets/examples/server-echo/main/certs/server_cert.pem b/components/libwebsockets/examples/server-echo/main/certs/server_cert.pem new file mode 100644 index 0000000000..cb1e9dfe74 --- /dev/null +++ b/components/libwebsockets/examples/server-echo/main/certs/server_cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIUUPCOgMA2v09E29fCkogx3RUBRtAwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA2NTlaFw0zNDAy +MjAwODA2NTlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC8WWbDxnLzTSfuQaO+kQnnzbwjhUHWn58s+BIEaO8M +GG6bX+8r/SH9XjMfFS36qAN3qxgRun3YoRTaHc2QByiGjf5IL4EAPDnLN+NzUIL5 +7Gi2QPQP/GksAsOGKWk/nMRPk1vcMptkFVIWSp474SQ0A92Z9z0dUIqBpjRa34kr +HsAIcT59/EG7YBBadMk0fQIxQVLh3Vosky85q+0waFihe47Ef5U2UftexoUx4Vcz +6EtP60Wx+4qN+FLsr+n2B7Oz2ITqfwgqLzjNLZwm9bMjcLZ0fWm1A/W1C989MXwI +w6DAPEZv7pbgp8r9phyrNieSDuuRaCvFsaXh6troAjLxAgMBAAGjQjBAMB0GA1Ud +DgQWBBRJCYAQG2+1FN5P/wyAR1AsrAyb4DAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn +ZnbuWO7I1+wdxDANBgkqhkiG9w0BAQsFAAOCAQEAmllul/GIH7RVq85mM/SxP47J +M7Z7T032KuR3n/Psyv2iq/uEV2CUje3XrKNwR2PaJL4Q6CtoWy7xgIP+9CBbjddR +M7sdNQab8P2crAUtBKnkNOl/na/5KnXnjwi/PmWJJ9i2Cqt0PPkaykTWp/MLfYIw +RPkY2Yo8f8gEiqXQd+0qTuMgumbgkPq3V8Lk1ocy62F5/qUhXxH+ifAXEoUQS6EG +8DlgwdZlfUY+jeM6N56WzYmxD1syjNW7faPio+qXINfpYatROhqphaMQ5SA6TRj6 +jcnLa31TdDdWmWYDcYgZntAv6yGi3rh0MdYqeNS0FKlMKmaH81VHs7V1UUXwUQ== +-----END CERTIFICATE----- diff --git a/components/libwebsockets/examples/server-echo/main/certs/server_key.pem b/components/libwebsockets/examples/server-echo/main/certs/server_key.pem new file mode 100644 index 0000000000..cf2fdadb7a --- /dev/null +++ b/components/libwebsockets/examples/server-echo/main/certs/server_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8WWbDxnLzTSfu +QaO+kQnnzbwjhUHWn58s+BIEaO8MGG6bX+8r/SH9XjMfFS36qAN3qxgRun3YoRTa +Hc2QByiGjf5IL4EAPDnLN+NzUIL57Gi2QPQP/GksAsOGKWk/nMRPk1vcMptkFVIW +Sp474SQ0A92Z9z0dUIqBpjRa34krHsAIcT59/EG7YBBadMk0fQIxQVLh3Vosky85 +q+0waFihe47Ef5U2UftexoUx4Vcz6EtP60Wx+4qN+FLsr+n2B7Oz2ITqfwgqLzjN +LZwm9bMjcLZ0fWm1A/W1C989MXwIw6DAPEZv7pbgp8r9phyrNieSDuuRaCvFsaXh +6troAjLxAgMBAAECggEACNVCggTxCCMCr+RJKxs/NS1LWPkbZNbYjrHVmnpXV6Bf +s460t0HoUasUx6zlGp+9heOyvcYat8maIj6KkOodBu5q0fTUXm/0n+ivlI1ejxz8 +ritupr9GKWe5xrVzd6XA+SBmivWenvt2/Y+jSxica4oQ3vMe3RyVWk4yn15jXu+9 +7B9lNyNeZtOBr6OozHGLYw4dwWcBNv2S6wevRKfHPwn/Ch5yTH1uAskgoMxUuyK2 +ynNVHWUhyS4pFU7Tex5ENDel15VYdbxV/2lQ2W6fHMLtC5GWKJXXbigCX7pfOpzC +BFJEfZl7ze/qptE9AR7DkLFYyMtrS7OlebYbLDOM9wKBgQD+rTdwULZibpKwlI3a +9Y22d4N/EDFvuu8LnuEiVQnXgwg9M+tlaa2liP18j1a7y/FCfoXf5sjUWCsdYR6d +C0TuiOGI59hYGI94NvVLAmOutR+vJ/3jhbv5wyqEQLhJ42Yz9kWBrDCI+V3q3TdO +H7wcH6suUIZpeLEJF4qHzY/1dwKBgQC9U/Pvswiww8sfysmd5shUNo4ofAZnTM1A +ak6pWE3lSyiOkSm+3B2GqxYWLRoo1v+pTyhhXDtRRmxGtMNrKCsmlHef/o3c6kkG +cuC2h/DiSmoITHy3BYKJoDeE54E8ubXUUKqHo41LYUs+D7M/IGxeiO13MUoIrEtF +AwzVWPBU1wKBgH8barD2x6Bm+XWCHy6qIZlxGsMfDN1r2gTdvhWJhcj3D/Sj5heO +X+lfbsxtKee+yOHcDesK3y8D9jjKkSHmTvgSfyX6OML3NxvTqidOwPugUHj2J8QX +qhLk8mJhftj50reacWRf0TV76ADhecnXEuaic6hA7mTTpOAZzL0svm3PAoGBALWF +r6VLX3KzVqZVtLb7FWmAoQ35093pCgXPpznAW3cTd4Axd/fxbTG4CUYb2i/760X2 +ij3Gw2yqe5fTKmYsLisgQA2bb4K28msHa6I2dmNQe5cXVp/X3Y98mJ6JpCSH3ekB +qm7ABfGXCCApx28n9B8zY5JbJKNqJgS15vELA+ojAoGAAkaV2w46+3iQ6gJtQepr +zGNybiYBx/Wo5fDdTS5u0xN+ZdC9fl2Zs0n7sMmUT8bWdDLcMnntHHO+oDIKyRHs +TQh1n68vQ4JoegQv3Z9Z/TLEKqr9gyJC1Ao6M4bZpPhUWQwupfHColtsr2TskcnJ +Nf2FpJZ7z6fQEShGlK1yTXM= +-----END PRIVATE KEY----- diff --git a/components/libwebsockets/examples/server-echo/main/idf_component.yml b/components/libwebsockets/examples/server-echo/main/idf_component.yml new file mode 100644 index 0000000000..959a1dfb33 --- /dev/null +++ b/components/libwebsockets/examples/server-echo/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + espressif/libwebsockets: + version: "*" + override_path: "../../../" + protocol_examples_common: + path: ${IDF_PATH}/examples/common_components/protocol_examples_common diff --git a/components/libwebsockets/examples/server-echo/main/lws-server-echo.c b/components/libwebsockets/examples/server-echo/main/lws-server-echo.c new file mode 100644 index 0000000000..657f45794f --- /dev/null +++ b/components/libwebsockets/examples/server-echo/main/lws-server-echo.c @@ -0,0 +1,274 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* ESP libwebsockets server example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + + +#include +#include + +#include "esp_wifi.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "protocol_examples_common.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_log.h" +#include "esp_task_wdt.h" + +#define RING_DEPTH 4096 + +static int callback_minimal_server_echo(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len); +/* one of these created for each message */ + +struct msg { + void *payload; /* is malloc'd */ + size_t len; +}; + +/* one of these is created for each client connecting to us */ + +struct per_session_data__minimal { + struct per_session_data__minimal *pss_list; + struct lws *wsi; + int last; /* the last message number we sent */ +}; + +/* one of these is created for each vhost our protocol is used with */ + +struct per_vhost_data__minimal { + struct lws_context *context; + struct lws_vhost *vhost; + const struct lws_protocols *protocol; + + struct per_session_data__minimal *pss_list; /* linked-list of live pss*/ + + struct msg amsg; /* the one pending message... */ + int current; /* the current message number we are caching */ +}; + +static struct lws_protocols protocols[] = { + { + .name = "lws-minimal-server-echo", + .callback = callback_minimal_server_echo, + .per_session_data_size = sizeof(struct per_session_data__minimal), + .rx_buffer_size = 128, + .id = 0, + .user = NULL, + .tx_packet_size = 0 + }, + LWS_PROTOCOL_LIST_TERM +}; + +static int options; +static const char *TAG = "lws-server-echo", *iface = ""; + +/* pass pointers to shared vars to the protocol */ +static const struct lws_protocol_vhost_options pvo_options = { + NULL, + NULL, + "options", /* pvo name */ + (void *) &options /* pvo value */ +}; + +static const struct lws_protocol_vhost_options pvo_interrupted = { + &pvo_options, + NULL, + "interrupted", /* pvo name */ + NULL /* pvo value */ +}; + +static const struct lws_protocol_vhost_options pvo = { + NULL, /* "next" pvo linked-list */ + &pvo_interrupted, /* "child" pvo linked-list */ + "lws-minimal-server-echo", /* protocol name we belong to on this vhost */ + "" /* ignored */ +}; + +int app_main(int argc, const char **argv) +{ + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + esp_log_level_set("*", ESP_LOG_INFO); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + /* Configure WDT. */ + esp_task_wdt_config_t esp_task_wdt_config = { + .idle_core_mask = 0, + .timeout_ms = portMAX_DELAY, + .trigger_panic = false + }; + esp_task_wdt_reconfigure(&esp_task_wdt_config); + + TaskHandle_t handle = xTaskGetCurrentTaskHandle(); + esp_task_wdt_add(handle); + + /* Create LWS Context - Server. */ + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + + lws_set_log_level(logs, NULL); + ESP_LOGI(TAG, "LWS minimal ws server echo\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = CONFIG_WEBSOCKET_PORT; + info.iface = iface; + info.protocols = protocols; + info.pvo = &pvo; + info.pt_serv_buf_size = 64 * 1024; + +#ifdef CONFIG_WS_OVER_TLS + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + + /* Configuring server certificates for mutual authentification */ + extern const char cert_start[] asm("_binary_server_cert_pem_start"); // Server certificate + extern const char cert_end[] asm("_binary_server_cert_pem_end"); + extern const char key_start[] asm("_binary_server_key_pem_start"); // Server private key + extern const char key_end[] asm("_binary_server_key_pem_end"); + extern const char cacert_start[] asm("_binary_ca_cert_pem_start"); // CA certificate + extern const char cacert_end[] asm("_binary_ca_cert_pem_end"); + + info.server_ssl_cert_mem = cert_start; + info.server_ssl_cert_mem_len = cert_end - cert_start - 1; + info.server_ssl_private_key_mem = key_start; + info.server_ssl_private_key_mem_len = key_end - key_start - 1; + info.server_ssl_ca_mem = cacert_start; + info.server_ssl_ca_mem_len = cacert_end - cacert_start; +#endif + + context = lws_create_context(&info); + if (!context) { + ESP_LOGE(TAG, "lws init failed\n"); + return 1; + } + + while (n >= 0) { + n = lws_service(context, 0); + } + + lws_context_destroy(context); + + return 1; +} + +static void __minimal_destroy_message(void *_msg) +{ + struct msg *msg = _msg; + + free(msg->payload); + msg->payload = NULL; + msg->len = 0; +} + +static int callback_minimal_server_echo(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + { + struct per_session_data__minimal *pss = + (struct per_session_data__minimal *)user; + struct per_vhost_data__minimal *vhd = + (struct per_vhost_data__minimal *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi)); + int m; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), + sizeof(struct per_vhost_data__minimal)); + vhd->context = lws_get_context(wsi); + vhd->protocol = lws_get_protocol(wsi); + vhd->vhost = lws_get_vhost(wsi); + break; + + case LWS_CALLBACK_ESTABLISHED: + /* add ourselves to the list of live pss held in the vhd */ + lws_ll_fwd_insert(pss, pss_list, vhd->pss_list); + pss->wsi = wsi; + pss->last = vhd->current; + break; + + case LWS_CALLBACK_CLOSED: + /* remove our closing pss from the list of live pss */ + lws_ll_fwd_remove(struct per_session_data__minimal, pss_list, + pss, vhd->pss_list); + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + if (!vhd->amsg.payload) { + break; + } + + if (pss->last == vhd->current) { + break; + } + + /* notice we allowed for LWS_PRE in the payload already */ + m = lws_write(wsi, ((unsigned char *)vhd->amsg.payload) + + LWS_PRE, vhd->amsg.len, LWS_WRITE_TEXT); + if (m < (int)vhd->amsg.len) { + lwsl_err("ERROR %d writing to ws\n", m); + return -1; + } + + pss->last = vhd->current; + break; + + case LWS_CALLBACK_RECEIVE: + if (vhd->amsg.payload) { + __minimal_destroy_message(&vhd->amsg); + } + + vhd->amsg.len = len; + /* notice we over-allocate by LWS_PRE */ + vhd->amsg.payload = malloc(LWS_PRE + len); + if (!vhd->amsg.payload) { + lwsl_user("OOM: dropping\n"); + break; + } + + memcpy((char *)vhd->amsg.payload + LWS_PRE, in, len); + vhd->current++; + + /* + * let everybody know we want to write something on them + * as soon as they are ready + */ + lws_start_foreach_llp(struct per_session_data__minimal **, + ppss, vhd->pss_list) { + lws_callback_on_writable((*ppss)->wsi); + } lws_end_foreach_llp(ppss, pss_list); + break; + + default: + break; + } + + return 0; + } + +} diff --git a/components/libwebsockets/idf_component.yml b/components/libwebsockets/idf_component.yml new file mode 100644 index 0000000000..4253653431 --- /dev/null +++ b/components/libwebsockets/idf_component.yml @@ -0,0 +1,5 @@ +version: "0.1.0" +url: https://github.com/espressif/esp-protocols/tree/master/components/libwebsockets +description: The component provides a simple ESP32 port of libwebsockets server and client. +dependencies: + idf: '>=5.0' diff --git a/components/libwebsockets/libwebsockets b/components/libwebsockets/libwebsockets new file mode 160000 index 0000000000..a74362ffdd --- /dev/null +++ b/components/libwebsockets/libwebsockets @@ -0,0 +1 @@ +Subproject commit a74362ffdd17b7f6293f675edef6d602096a1e29 diff --git a/components/libwebsockets/port/lws_port.c b/components/libwebsockets/port/lws_port.c new file mode 100644 index 0000000000..f5991effef --- /dev/null +++ b/components/libwebsockets/port/lws_port.c @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "stdio.h" +#include + +/* + * External function prototype for the wrapped 'mbedtls_ssl_handshake_step'. + * The "real" function is not being called, this prototype is just to improve + * the code readability. + */ +extern int __real_mbedtls_ssl_handshake_step(mbedtls_ssl_context *ssl); + +int __wrap_mbedtls_ssl_handshake_step( mbedtls_ssl_context *ssl ) +{ + int ret = 0; + + while (ssl->MBEDTLS_PRIVATE(state) != MBEDTLS_SSL_HANDSHAKE_OVER) { + ret = __real_mbedtls_ssl_handshake_step(ssl); + + if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + continue; + } + + if (ret != 0) { + break; + } + } + + return ret; +} + +/* + * External function prototype for the wrapped 'lws_adopt_descriptor_vhost'. + * The "real" function is not being called, this prototype is just to improve + * the code readability. + */ +extern struct lws *__real_lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, lws_sock_file_fd_type fd, const char *vh_prot_name, struct lws *parent); + +struct lws *__wrap_lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, lws_sock_file_fd_type fd, const char *vh_prot_name, struct lws *parent) +{ + lws_adopt_desc_t info; + char nullstr[] = "(null)"; + memset(&info, 0, sizeof(info)); + + info.vh = vh; + info.type = type; + info.fd = fd; + info.vh_prot_name = vh_prot_name; + info.parent = parent; + info.fi_wsi_name = nullstr; + + return lws_adopt_descriptor_vhost_via_info(&info); +} diff --git a/test_app/CMakeLists.txt b/test_app/CMakeLists.txt index bda7a057a9..f2cf76d4be 100644 --- a/test_app/CMakeLists.txt +++ b/test_app/CMakeLists.txt @@ -15,6 +15,7 @@ set(EXTRA_COMPONENT_DIRS ../components/console_simple_init ../components/mbedtls_cxx ../components/sock_utils + ../components/libwebsockets ../components/mdns)