feat(lws): Add initial support libwebsockets component

This commit is contained in:
glmfe
2025-02-07 17:57:56 -03:00
parent 936e43f9d8
commit 5f66f35f5f
27 changed files with 1397 additions and 2 deletions

86
.github/workflows/lws_build.yml vendored Normal file
View File

@ -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", "release-v5.4"]
test: [ { app: example, path: "examples/client" }]
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", "release-v5.4"]
idf_target: ["esp32"]
test: [ { app: example, path: "examples/client" }]
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

View File

@ -102,5 +102,6 @@ jobs:
components/mbedtls_cxx;
components/mosquitto;
components/sock_utils;
components/libwebsockets;
namespace: "espressif"
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}

3
.gitmodules vendored
View File

@ -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

View File

@ -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]

View File

@ -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)

View File

@ -0,0 +1,20 @@
idf_component_register(REQUIRES mbedtls)
set(LWS_WITH_EXPORT_LWSTARGETS OFF CACHE BOOL "Export libwebsockets CMake targets. Disable if they conflict with an outer cmake project.")
set(LWS_WITH_MBEDTLS ON CACHE BOOL "Use mbedTLS (>=2.0) replacement for OpenSSL.")
set(LWS_WITH_JPEG OFF CACHE BOOL "Enable stateful JPEG stream decoder")
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)

View File

@ -0,0 +1,24 @@
# ESP32 libwebsockets Port
This is a lightweight port of the libwebsockets library designed to run on the ESP32. It provides WebSocket client functionalities.
## Supported Options
The ESP-IDF port of libwebsockets supports a set of common WebSocket configurations for clients. These options can be configured through a structure passed to the `lws_create_context()` function.
Key features supported:
- WebSocket with optional SSL/TLS
- HTTP/1.1 client support
## Memory Footprint Considerations
The memory consumption primarily depends on the number of concurrent connections and the selected options for WebSocket frames, protocol handling, and SSL/TLS features. It consumes approximately 300 kB of program memory.
### Client:
The values bellow were extracted from the client example (./examples/client/main/lws-client.c)
- **Initial Memory Usage**: ~8 kB of heap on startup
- **Connected Memory Usage Over TLS**: ~35.3 kB of heap after connected to a server
- **Connected Memory Usage Over TCP (Plain)**: ~4.5 kB of heap after connected to a server
#### When configuring a WebSocket client, 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.

View File

@ -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_example)

View File

@ -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.
```

View File

@ -0,0 +1,87 @@
# 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
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: 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: 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:<ba-add>idx:0 (ifx:0, b4:89:01:63:9d:08), tid:0, ssn:321, winSize:64
I (20768) lws-client: WEBSOCKET_EVENT_CONNECTED
I (20768) lws-client: Sending hello 0000
I (20778) lws-client: WEBSOCKET_EVENT_DATA
W (20778) lws-client: Received=echo.websocket.events sponsored by Lob.com
I (20968) lws-client: WEBSOCKET_EVENT_DATA
W (20968) lws-client: Received=hello 0000
I (22978) lws-client: Sending hello 0001
I (23118) lws-client: WEBSOCKET_EVENT_DATA
W (23118) lws-client: Received=hello 0001
I (23778) lws-client: Sending hello 0002
I (23938) lws-client: WEBSOCKET_EVENT_DATA
W (23938) lws-client: Received=hello 0002
I (25948) lws-client: Sending hello 0003
I (26088) lws-client: WEBSOCKET_EVENT_DATA
W (26088) lws-client: Received=hello 0003
I (26948) lws-client: Sending hello 0004
I (27118) lws-client: WEBSOCKET_EVENT_DATA
W (27118) lws-client: Received=hello 0004
```

View File

@ -0,0 +1,12 @@
set(SRC_FILES "lws-client.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}")

View File

@ -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

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -0,0 +1,6 @@
dependencies:
espressif/libwebsockets:
version: "*"
override_path: "../../../"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common

View File

@ -0,0 +1,323 @@
/*
* SPDX-FileCopyrightText: 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 <libwebsockets.h>
#include <stdio.h>
#include <stdlib.h>
#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 <cJSON.h>
static uint32_t disconnect_timeout = 5000000; // microseconds
static uint8_t message_count = 0;
static const char *TAG = "lws-client";
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[] = {
{
.name = "lws-echo",
.callback = callback_minimal_echo,
.per_session_data_size = 1024,
.rx_buffer_size = 1024,
.id = 0,
.user = NULL,
.tx_packet_size = 0
},
LWS_PROTOCOL_LIST_TERM
};
void 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 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");
} else {
lws_sul_schedule(context, 0, &sul, connect_cb, 100);
/*
* Holds the result of the lws_service call:
* = 0 -> service succeeded and events were processed,
* < 0 -> an error occurred or the event loop should stop
*/
int service_result = 0;
while (service_result >= 0) {
service_result = lws_service(context, 0);
}
lws_context_destroy(context);
}
while (1) {
//Should not get here, Spin undefinitely.
vTaskDelay(10);
taskYIELD();
}
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ESP-IDF port of libwebsockets client.
dependencies:
idf: '>=5.3'

View File

@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "stdio.h"
#include <libwebsockets.h>
/*
* 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);
}

View File

@ -15,6 +15,7 @@ set(EXTRA_COMPONENT_DIRS
../components/console_simple_init
../components/mbedtls_cxx
../components/sock_utils
../components/libwebsockets
../components/mdns)